Garda Brief Usage
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_ENABLED 和 GARDA_DAILY_RESET_TIME 修改规则。
5. 获取城市列表
GET /v1/garda/cityScope: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}/cardScope: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"只查询 mai 和 chu:
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卡新客户端直接赋值时应优先发送 cardCount。updateExpression: "=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/announcementScope: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/announcementScope: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_id、content 必填。时间使用 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 有误,不要原样重试 |
| 401 | Token 缺失、无效或过期 |
| 403 | Scope 不足 |
| 404 | 城市、机厅、游戏或公告不存在 |
| 409 | 当前状态不允许操作,例如未知卡数执行增减 |
| 429 | 已限流,等待下一分钟并使用指数退避 |
| 500 | 服务端或数据库异常,可有限次数重试 |
限流响应包含 X-RateLimit-Limit。建议对 429、500 使用带随机抖动的指数退避,不要无限重试写请求。
常用业务错误码:
| 错误码 | 含义 |
|---|---|
TOKEN_REQUIRED | 未提供 Token |
TOKEN_INVALID | Token 无效 |
TOKEN_EXPIRED | Token 已过期 |
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"])