Download OpenAPI specification:openapi.json
Server-to-server API that the game server (X-Legend) implements and the GAGA backend calls.
Rev 0.2 (2026-10-07) — rebuilt on top of X-Legend's existing /gmtool/* interface
(ELI, Discord 2026-10-06). Paths and field names are X-Legend's. Items marked
🔶 GAGA suggestion are additions/clarifications we ask X-Legend to confirm or counter-propose.
中文摘要:本版直接採用 X-Legend 提供的
/gmtool/*路徑與欄位。 標示 🔶 GAGA suggestion 的部分是我們建議補充或需要確認的項目,請貴方確認或提出替代方案。 本 API 為伺服器對伺服器(GAGA 後端 → 遊戲伺服器),與 GAGA SDK、遊戲客戶端、Google/Apple 內購無關。
| # | Endpoint | Suggestion | Why |
|---|---|---|---|
| S1 | /gmtool/role/query |
Add type=xluserid (+ sid) → returns the character list of that GAGA ID on that server |
Web Shop / Funtap know only the GAGA ID. Player must pick a character. Current query goes the other way (aid/cid → xluserid). |
| S2 | All | Define md5str formula + add time (replay window 300 s) |
Formula not specified yet. Proposal below — if you already have a formula, send it and we adopt yours. |
| S3 | /gmtool/role/orders |
orderid idempotency rules (duplicate = success, no second delivery) |
GAGA retries on timeout; must never double-deliver. |
| S4 | /gmtool/role/orders |
Add items (same format as sendmail); productid = audit only |
GAGA CMS stays the single source of package content → no 3rd place to configure packages (GAGA CMS + Funtap already). |
| S5 | new /gmtool/role/orders/query |
Look up an order by orderid |
Recovery after timeout / reconciliation. Without it GAGA cannot know if a timed-out order was delivered. |
| S6 | /gmtool/role/sendmail |
Use xluserid (not uid) + optional refid for dedupe |
Consistent naming; pre-register launch-day batch rewards use sendmail and also need retry safety. |
| S7 | All | Common response {status, msg, ...} + error code table |
Only status:"1" is defined today. GAGA needs to know which errors to retry. |
| S8 | Ops | Staging + production base URLs, HTTPS, IP allowlist | Funtap requires separate test/live endpoints. gmtool is an admin interface — expose only these endpoints to GAGA. |
| Flow | Calls |
|---|---|
Funtap (VN) top-up — player pays on nap.funtap.games |
Funtap → GAGA BE → server/list, role/query → (payment) → role/orders |
| GAGA Web Shop (TH / ROW) | same: server/list → role/query → (payment) → role/orders |
| Item Code / gift code redeem on GAGA web | role/giftcode |
| Pre-register rewards, ops compensation | role/sendmail |
xluserid = GAGA ID, canonical form without dashes, 16 chars, e.g. VNGA284190375216
(display form VN-GA-284190375216). It is the same value the game receives as gaga_id from GAGA SDK login.
🔶 Please confirm the game stores this value as xluserid unchanged.
🔶 S8
POST, body application/x-www-form-urlencoded, response application/json, UTF-8.
If your gmtool already uses GET + query string, keep it and tell us — the parameters stay the same.gmtool normally has full GM power; please expose only the endpoints in this document to GAGA.| Env | Base URL (provided by X-Legend) | Secret (provided by X-Legend or GAGA, one per env) |
|---|---|---|
| Staging | https://<stg-host> |
<stg secret> |
| Production | https://<prod-host> |
<prod secret> — never shared with staging |
🔶 S2 — md5str rule.
Every request carries time and md5str.
md5str.name=value with &. Use raw values (not URL-encoded). Include empty values as name=.&key=<SECRET>.md5str = MD5 of the UTF-8 string, lowercase hex (32 chars).Reject if |now − time| > 300 seconds (status -2). Compare md5str in constant time.
Worked example (secret = test_secret_123), /gmtool/role/orders:
cid=2356423¤cy=VND&items=1:900001:1100&orderid=GW20261007A1B2C3D4&payment=funtap&price=250000&productid=GCOIN_1000&sid=1&time=1791360000&xluserid=VNGA284190375216&key=test_secret_123
→ md5str = 6a2352e8c0632b43fd45ef28213680a0
/gmtool/server/list with only time=1791360000 → time=1791360000&key=test_secret_123 → 55a7ec1d94c646c71fd63a962474a2b2
🔶 S7
HTTP is always 200 when the request reached the game logic; the result is in status.
{ "status": "1", "msg": "OK", "...": "endpoint data" }
| status | Meaning | GAGA retries? |
|---|---|---|
1 |
Success (also for a duplicate orderid / refid, see Idempotency) |
— |
-1 |
Invalid md5str |
No — alert |
-2 |
time outside ±300 s |
Yes (once, after clock check) |
-3 |
Missing / invalid parameter | No |
-4 |
Server (sid) not found |
No |
-5 |
Character not found / not owned by xluserid |
No |
-6 |
Unknown item / invalid amount / mailbox cannot receive | No — alert |
-7 |
orderid / refid already used with different content |
No — alert |
-8 |
Gift code invalid | No |
-9 |
Gift code already used / limit reached | No |
-10 |
Gift code expired | No |
-98 |
Server under maintenance | Yes |
-99 |
Internal error | Yes |
HTTP 5xx or timeout → GAGA treats as -99 (retry). If you already have your own codes, send the list and we map them.
🔶 S3, S5, S6
orderid (GAGA transaction id) is unique per purchase. Store it with a unique constraint in the same DB
transaction that creates the delivery.orderid + same content again → do not deliver again; return status:"1", duplicate:true.orderid + different xluserid / sid / cid / items → status:"-7".-98 / -99 GAGA first calls /gmtool/role/orders/query.
found:true → done. found:false → resend /orders with the same orderid.
Back-off 30 s, 2 min, 10 min, 1 h, then every 6 h up to 72 h, then manual.orderid records ≥ 90 days. Same rules apply to refid on sendmail.items = <Type>:<ID>:<Amount>, multiple joined with , — e.g. 1:900001:1100,2:500123:1.
🔶 Please send: the list of Type values, the G Coin Type:ID, max items per mail, and mailbox expiry rule.
Type:ID, other item IDs GAGA may send, Type list.GAGA SDK, game client UI, Google Play / App Store IAP, refunds / item removal (agree separately).
X-Legend endpoint, unchanged. GAGA caches the result for 5 minutes.
🔶 GAGA suggestion: add optional status (open / maintenance) so the Web Shop can grey out a server.
| time required | string 🔶 Unix seconds. Rejected if more than 300 s from server time. |
| md5str required | string^[0-9a-f]{32}$ See Signature. |
time=1791360000&md5str=55a7ec1d94c646c71fd63a962474a2b2
{- "status": "1",
- "msg": "OK",
- "servers": [
- {
- "sid": "1",
- "name": "Server1",
- "status": "open"
}, - {
- "sid": "2",
- "name": "Server2",
- "status": "maintenance"
}
]
}X-Legend endpoint. Existing types aid / cid → returns xluserid (kept as is).
🔶 GAGA suggestion S1 — required for Web Shop / Funtap: add type=xluserid with data = GAGA ID and sid.
Returns all characters of that GAGA ID on that server (array, player picks one).
No character on that server → status:"-5".
| time required | string 🔶 Unix seconds. Rejected if more than 300 s from server time. |
| md5str required | string^[0-9a-f]{32}$ See Signature. |
| type required | string Enum: "aid" "cid" "xluserid"
|
| data required | string The ID matching |
| sid | string Server id. 🔶 Required when |
type=xluserid&data=VNGA284190375216&sid=1&time=1791360000&md5str=8b15a7185de7c817ccc46910f2ba36c9
{- "status": "1",
- "msg": "OK",
- "xluserid": "VNGA284190375216",
- "roles": [
- {
- "sid": "1",
- "cid": "2356423",
- "name": "Role ABC",
- "level": 45,
- "os": "android"
}, - {
- "sid": "1",
- "cid": "2356424",
- "name": "小敏",
- "level": 12,
- "os": "ios"
}
]
}X-Legend endpoint. Used for every paid delivery (Web Shop and Funtap), after GAGA has verified the payment.
Return status:"1" only after the delivery is committed.
🔶 S3 — idempotent on orderid (see Idempotency).
🔶 S4 — GAGA sends items (what to deliver). productid is for your reports only.
If the game must deliver by productid, please send the product catalog
(productid → items) and we will use your IDs in GAGA CMS instead.
Delivery to mailbox vs direct to wallet: your choice — please tell us which (affects the orders/query status).
| time required | string 🔶 Unix seconds. Rejected if more than 300 s from server time. |
| md5str required | string^[0-9a-f]{32}$ See Signature. |
| xluserid required | string GAGA ID |
| sid required | string |
| cid required | string |
| orderid required | string <= 64 characters GAGA transaction id. Idempotency key. |
| productid required | string GAGA package id. 🔶 Audit only (S4). |
| items required | string (Items) ^\d+:\d+:\d+(,\d+:\d+:\d+)*$
|
| price required | string Amount paid, decimal string in major units (VND has no decimals; THB/USD up to 2). |
| currency required | string Enum: "VND" "THB" "USD" ISO 4217 |
| payment required | string Enum: "funtap" "chillpay" "razer" Sales channel. |
xluserid=VNGA284190375216&sid=1&cid=2356423&orderid=GW20261007A1B2C3D4&productid=GCOIN_1000&items=1%3A900001%3A1100&price=250000¤cy=VND&payment=funtap&time=1791360000&md5str=6a2352e8c0632b43fd45ef28213680a0
{- "status": "1",
- "msg": "OK",
- "orderid": "GW20261007A1B2C3D4",
- "duplicate": false,
- "delivered_at": 1791360003
}🔶 GAGA suggestion S5 — new endpoint.
Returns the delivery for an orderid. GAGA calls it after a timeout / -98 / -99 on /orders,
and in daily reconciliation. found:false → GAGA resends /orders with the same orderid.
| time required | string 🔶 Unix seconds. Rejected if more than 300 s from server time. |
| md5str required | string^[0-9a-f]{32}$ See Signature. |
| orderid required | string <= 64 characters |
orderid=GW20261007A1B2C3D4&time=1791360000&md5str=bf1118a9437c2bdfc60a797c8f01759f
{- "status": "1",
- "msg": "OK",
- "found": true,
- "order": {
- "orderid": "GW20261007A1B2C3D4",
- "xluserid": "VNGA284190375216",
- "sid": "1",
- "cid": "2356423",
- "items": "1:900001:1100",
- "state": "claimed",
- "delivered_at": 1791360003,
- "claimed_at": 1791360420
}
}X-Legend endpoint. For non-purchase rewards: pre-register rewards (launch-day batch), event rewards, compensation.
Paid orders go through /gmtool/role/orders, not this endpoint.
🔶 S6 — use xluserid instead of uid (same as other endpoints; tell us if uid is a different ID).
Add optional refid: if present, apply the same idempotency rules as orderid
(launch-day batch will be retried on failure).
| time required | string 🔶 Unix seconds. Rejected if more than 300 s from server time. |
| md5str required | string^[0-9a-f]{32}$ See Signature. |
| xluserid required | string GAGA ID. (Original spec field name |
| sid required | string |
| cid required | string |
| title required | string <= 50 characters |
| body required | string <= 500 characters |
| items required | string (Items) ^\d+:\d+:\d+(,\d+:\d+:\d+)*$
|
| refid | string <= 64 characters 🔶 Optional idempotency key, e.g. |
xluserid=VNGA284190375216&sid=1&cid=2356423&title=Pre-register%20reward&body=Thank%20you%20for%20pre-registering%21&items=2%3A500123%3A1%2C2%3A500124%3A5&refid=PREREG-2026-VN-000123&time=1791360000&md5str=5f24801d3214e88f7f09818faed5668c
{- "status": "1",
- "msg": "OK",
- "mailid": "M-88123901",
- "duplicate": false
}X-Legend endpoint. Used when a player redeems a code on the GAGA website (Item Code / marketing serials).
🔶 GAGA suggestion: return the granted items, and use distinct statuses for invalid / used / expired (-8 / -9 / -10)
so the website can show the right message. Please confirm who generates the codes (game or GAGA)
and whether one code is per-account or per-character.
| time required | string 🔶 Unix seconds. Rejected if more than 300 s from server time. |
| md5str required | string^[0-9a-f]{32}$ See Signature. |
| xluserid required | string |
| sid required | string |
| cid required | string |
| code required | string <= 64 characters |
xluserid=VNGA284190375216&sid=1&cid=2356423&code=GAGA-OBT-7K2M9Q&time=1791360000&md5str=bf4f43677753035b4de5d36654fc4d1d
{- "status": "1",
- "msg": "OK",
- "items": "2:500123:1"
}