Guide

Garda Brief Usage

最后更新于 2026-07-20

Garda API 调用文档

1. 基本信息

生产环境基础地址:

https://api.genarch.top/v1/garda

所有请求和响应均使用 UTF-8。路径中的城市、机厅名称必须进行 URL 编码。建议客户端设置 10 秒超时,并记录响应头 X-Request-ID

除 健康检查 /healthz 外,Garda API 都要求 GAPI Token:

Authorization: Bearer gapi_xxxxxxxxx

也可以使用:

X-API-Token: gapi_xxxxxxxxx

不要把 Token 放在 URL 查询参数中。

2. 权限

Scope能力
garda:read城市、卡数、机厅、统计和公告查询
garda:card:write修改机厅卡数
garda:announcement:write创建、更新和删除公告

一个 Token 可以同时具有多个 Scope。

Garda 当前只使用 mai 卡数。管理端新建或导入机厅时会自动建立 mai 游戏关联;从旧版本升级的数据也会自动补齐。

3. 统一响应

成功:

{
  "success": true,
  "data": {},
  "meta": {
    "request_id": "req_xxx",
    "timestamp": "2026-07-20T10:00:00+08:00"
  }
}

错误:

{
  "success": false,
  "error": {
    "code": "GARDA_PLACE_NOT_FOUND",
    "message": "指定机厅不存在"
  },
  "meta": {
    "request_id": "req_xxx"
  }
}

客户端必须同时检查 HTTP 状态码和 success,不要只根据 message 判断错误类型。

4. 数据约定

机厅状态

含义
open正常营业
temporarily_closed暂停营业
closed已关闭
hidden不在公共 API 中显示

卡数

card_count 或卡数对象中的 count 可能为 null

{
  "count": null,
  "updated_at": "2026-07-20T04:00:00+08:00"
}

null 表示当前卡数未知,不是 0(机厅无人)。

Garda 默认每天按 TZ 指定的时区在 04:00 将所有已知卡数重置为 null。每条重置都会写入日志:

{
  "old_count": 4,
  "new_count": null,
  "source": "scheduled_reset",
  "note": "每日定时重置为未知"
}

任务按日期幂等,同一天不会重复重置。部署方可通过 GARDA_DAILY_RESET_ENABLEDGARDA_DAILY_RESET_TIME 修改规则。

5. 获取城市列表

GET /v1/garda/city

Scope:garda:read

请求示例:

curl --get \
  -H "Authorization: Bearer $GAPI_TOKEN" \
  "https://api.genarch.top/v1/garda/city"

data 示例:

[
  {
    "name": "安徽省利辛县",
    "province": "安徽省",
    "code": "01"
  }
]

只返回已启用城市。

6. 查询城市内机厅卡数

GET /v1/garda/city/{city}/card

Scope:garda:read

查询参数:

参数必填说明
gameName游戏代码,可重复传递

查询全部游戏:

curl -H "Authorization: Bearer $GAPI_TOKEN" \
  "https://api.genarch.top/v1/garda/city/%E4%B8%8A%E6%B5%B7%E5%B8%82/card"

只查询 maichu

curl -H "Authorization: Bearer $GAPI_TOKEN" \
  "https://api.genarch.top/v1/garda/city/%E4%B8%8A%E6%B5%B7%E5%B8%82/card?gameName=mai&gameName=chu"

data 示例:

[
  {
    "id": 12,
    "name": "大玩家利辛县万达广场店",
    "aliases": ["dwj", "大玩家"],
    "status": "open",
    "cards": {
      "mai": 3,
      "chu": null
    }
  }
]

隐藏机厅不会返回。没有配置对应游戏的机厅不会凭空产生该游戏字段。

7. 查询机厅详情

GET /v1/garda/place/{city}/{place}

Scope:garda:read

place 可以是机厅正式名称或别名。

别名与正式名称使用完全相同的匹配规则。通过别名查询时,响应中的 name 始终返回正式名称,并在 aliases 中返回该机厅的全部别名。

查询参数:

queryType返回内容
common基本信息、游戏、卡数、有效公告;默认值
forLogs最近 100 条卡数日志
forAliases别名
forGames支持的游戏

普通查询:

curl -H "Authorization: Bearer $GAPI_TOKEN" \
  "https://api.genarch.top/v1/garda/place/%E4%B8%8A%E6%B5%B7%E5%B8%82/%E7%A4%BA%E4%BE%8B%E6%9C%BA%E5%8E%85?queryType=common"

data 示例:

{
  "id": 01,
  "city": "安徽省利辛县",
  "name": "大玩家利辛万达广场店",
  "aliases": ["dwj", "大玩家"],
  "address": "利辛县万达广场三楼",
  "status": "open",
  "description": "",
  "games": [
    {
      "id": 1,
      "code": "mai",
      "name": "舞萌DX"
    }
  ],
  "cards": {
    "mai": {
      "count": 3,
      "updated_at": "2026-07-20T10:30:00+08:00"
    }
  },
  "announcements": []
}

日志查询:

curl -H "Authorization: Bearer $GAPI_TOKEN" \
  "https://api.genarch.top/v1/garda/place/%E4%B8%8A%E6%B5%B7%E5%B8%82/%E7%A4%BA%E4%BE%8B%E6%9C%BA%E5%8E%85?queryType=forLogs"

日志的 source 当前可能为:

  • api:开发者 API 更新
  • admin:WebUI 管理员修正
  • scheduled_reset:每日定时重置

无效 queryType 返回 HTTP 400、GARDA_QUERY_TYPE_INVALID

8. 更新机厅卡数

PUT /v1/garda/card/{city}/{place}

Scope:garda:card:write

路径中的 place 可以发送正式名称或任一别名。

直接设置

{
  "gameName": "mai",
  "cardCount": 3,
  "note": "现场确认"
}
curl -X PUT \
  -H "Authorization: Bearer $GAPI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"gameName":"mai","cardCount":3,"note":"现场确认"}' \
  "https://api.genarch.top/v1/garda/card/%E4%B8%8A%E6%B5%B7%E5%B8%82/%E7%A4%BA%E4%BE%8B%E6%9C%BA%E5%8E%85"

增减表达式

{
  "gameName": "mai",
  "updateExpression": "+1",
  "note": "新增一张卡"
}

允许的表达式:

+1
-2
3
=3
3张
3卡

新客户端直接赋值时应优先发送 cardCountupdateExpression: "=3" 仅作为旧客户端兼容写法,效果与 cardCount: 3 相同。

省略 gameName 时默认为 mai。不能把卡数更新为负数;当前卡数为 null 时不能使用加减表达式,应改为直接设置 cardCount

成功的 data

{
  "place_id": 12,
  "game": "mai",
  "old_count": 2,
  "new_count": 3
}

卡状态和卡数日志在同一个数据库事务中写入。

9. 查询机厅统计

GET /v1/garda/stat/{city}/{place}

Scope:garda:read

路径中的 place 可以发送正式名称或任一别名。

curl -H "Authorization: Bearer $GAPI_TOKEN" \
  "https://api.genarch.top/v1/garda/stat/%E4%B8%8A%E6%B5%B7%E5%B8%82/%E7%A4%BA%E4%BE%8B%E6%9C%BA%E5%8E%85"

data 按游戏聚合:

[
  {
    "game": "mai",
    "updates": 18,
    "average": 2.7,
    "max": 6,
    "min": 0
  }
]

统计来自历史 card_logs。每日自动重置日志的新值为 null,不会作为有效新卡数参与平均值。

10. 查询公告

GET /v1/garda/announcement

Scope:garda:read

可选查询参数:

参数说明
place_id只查询指定机厅
curl -H "Authorization: Bearer $GAPI_TOKEN" \
  "https://api.genarch.top/v1/garda/announcement?place_id=12"

data 示例:

[
  {
    "id": 30,
    "place_id": 01,
    "place": "大玩家利辛万达广场店",
    "title": "设备维护",
    "content": "p1维护中",
    "starts_at": "2026-07-20T10:00:00+08:00",
    "expires_at": "2026-07-21T10:00:00+08:00"
  }
]

11. 创建公告

POST /v1/garda/announcement

Scope:garda:announcement:write

请求体:

{
  "place_id": 12,
  "title": "设备维护",
  "content": "p1维护中",
  "starts_at": "2026-07-20T10:00:00+08:00",
  "expires_at": "2026-07-21T10:00:00+08:00"
}

place_idcontent 必填。时间使用 RFC 3339;时间字段可以省略或传空字符串。

curl -X POST \
  -H "Authorization: Bearer $GAPI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"place_id":12,"title":"设备维护","content":"p1维护中","expires_at":"2026-07-21T10:00:00+08:00"}' \
  "https://api.genarch.top/v1/garda/announcement"

成功返回 HTTP 201:

{
  "success": true,
  "data": {
    "id": 30
  },
  "meta": {
    "request_id": "req_xxx",
    "timestamp": "2026-07-20T10:00:00+08:00"
  }
}

12. 更新公告

PUT /v1/garda/announcement/{announcement_id}

Scope:garda:announcement:write

请求体字段与创建公告相同;更新时应提交完整的标题、内容和时间字段。

curl -X PUT \
  -H "Authorization: Bearer $GAPI_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"title":"维护完成","content":"p1已恢复","starts_at":"","expires_at":""}' \
  "https://api.genarch.top/v1/garda/announcement/30"

公告不存在返回 HTTP 404、GARDA_ANNOUNCEMENT_NOT_FOUND

13. 删除公告

DELETE /v1/garda/announcement/{announcement_id}

Scope:garda:announcement:write

curl -X DELETE \
  -H "Authorization: Bearer $GAPI_TOKEN" \
  "https://api.genarch.top/v1/garda/announcement/30"

成功的 data

{
  "deleted": true
}

14. 状态码与重试

HTTP处理建议
200/201成功
400请求参数或 JSON 有误,不要原样重试
401Token 缺失、无效或过期
403Scope 不足
404城市、机厅、游戏或公告不存在
409当前状态不允许操作,例如未知卡数执行增减
429已限流,等待下一分钟并使用指数退避
500服务端或数据库异常,可有限次数重试

限流响应包含 X-RateLimit-Limit。建议对 429、500 使用带随机抖动的指数退避,不要无限重试写请求。

常用业务错误码:

错误码含义
TOKEN_REQUIRED未提供 Token
TOKEN_INVALIDToken 无效
TOKEN_EXPIREDToken 已过期
SCOPE_REQUIRED缺少 Scope
GARDA_PLACE_NOT_FOUND机厅不存在
GARDA_PLACE_OR_GAME_NOT_FOUND机厅或游戏关联不存在
GARDA_CARD_UNKNOWN未知卡数不能增减
GARDA_CARD_COUNT_INVALID卡数为负数
GARDA_CARD_EXPRESSION_INVALID更新表达式无效
GARDA_ANNOUNCEMENT_NOT_FOUND公告不存在
RATE_LIMITED触发限流

15. JavaScript 示例

const baseURL = "https://api.genarch.top";
const token = process.env.GAPI_TOKEN;

async function garda(path, options = {}) {
  const response = await fetch(`${baseURL}/v1/garda${path}`, {
    ...options,
    headers: {
      Authorization: `Bearer ${token}`,
      "Content-Type": "application/json",
      ...options.headers,
    },
  });
  const body = await response.json();
  if (!response.ok || !body.success) {
    throw new Error(`${body.error?.code}: ${body.error?.message}`);
  }
  return body.data;
}

const places = await garda(
  `/city/${encodeURIComponent("上海市")}/card?gameName=mai`,
);
console.log(places);

16. Python 示例

import os
from urllib.parse import quote

import requests

BASE_URL = "https://api.genarch.top"
TOKEN = os.environ["GAPI_TOKEN"]

city = quote("上海市", safe="")
place = quote("示例机厅", safe="")
response = requests.get(
    f"{BASE_URL}/v1/garda/place/{city}/{place}",
    params={"queryType": "common"},
    headers={"Authorization": f"Bearer {TOKEN}"},
    timeout=10,
)
response.raise_for_status()
payload = response.json()
if not payload["success"]:
    raise RuntimeError(payload["error"])
print(payload["data"])