{
  "openapi": "3.0.3",
  "info": {
    "title": "GAGA Game Server API (gmtool)",
    "version": "0.2",
    "x-logo": {
      "url": "https://gaga.game/assets/gaga%20games-fmCQFINc.png",
      "backgroundColor": "#000000",
      "altText": "GAGA GAMES"
    },
    "description": "Server-to-server API that the **game server (X-Legend) implements** and the **GAGA backend calls**.\n\n**Rev 0.2 (2026-10-07)** — rebuilt on top of X-Legend's existing `/gmtool/*` interface\n(ELI, Discord 2026-10-06). Paths and field names are X-Legend's. Items marked\n**🔶 GAGA suggestion** are additions/clarifications we ask X-Legend to confirm or counter-propose.\n\n> **中文摘要**：本版直接採用 X-Legend 提供的 `/gmtool/*` 路徑與欄位。\n> 標示 **🔶 GAGA suggestion** 的部分是我們建議補充或需要確認的項目，請貴方確認或提出替代方案。\n> 本 API 為伺服器對伺服器（GAGA 後端 → 遊戲伺服器），與 GAGA SDK、遊戲客戶端、Google/Apple 內購無關。\n\n## Summary of GAGA suggestions\n\n| # | Endpoint | Suggestion | Why |\n|---|---|---|---|\n| 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). |\n| 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.** |\n| S3 | `/gmtool/role/orders` | `orderid` idempotency rules (duplicate = success, no second delivery) | GAGA retries on timeout; must never double-deliver. |\n| 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). |\n| 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. |\n| 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. |\n| S7 | All | Common response `{status, msg, ...}` + error code table | Only `status:\"1\"` is defined today. GAGA needs to know which errors to retry. |\n| 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. |\n\n## Where this API is used\n\n| Flow | Calls |\n|---|---|\n| **Funtap (VN) top-up** — player pays on `nap.funtap.games` | Funtap → GAGA BE → `server/list`, `role/query` → (payment) → `role/orders` |\n| **GAGA Web Shop** (TH / ROW) | same: `server/list` → `role/query` → (payment) → `role/orders` |\n| **Item Code / gift code** redeem on GAGA web | `role/giftcode` |\n| **Pre-register rewards**, ops compensation | `role/sendmail` |\n\n## Identity\n\n`xluserid` = **GAGA ID**, canonical form without dashes, 16 chars, e.g. `VNGA284190375216`\n(display form `VN-GA-284190375216`). It is the same value the game receives as `gaga_id` from GAGA SDK login.\n🔶 Please confirm the game stores this value as `xluserid` unchanged.\n\n## Transport\n\n🔶 S8\n\n- Method: **`POST`**, body `application/x-www-form-urlencoded`, response `application/json`, UTF-8.\n  If your gmtool already uses `GET` + query string, keep it and tell us — the parameters stay the same.\n- **HTTPS only** (TLS 1.2+).\n- **IP allowlist**: accept only GAGA backend egress IPs (GAGA sends the list per environment).\n  `gmtool` normally has full GM power; please expose **only the endpoints in this document** to GAGA.\n- Timeout on GAGA side: 10 s.\n\n| Env | Base URL (provided by X-Legend) | Secret (provided by X-Legend or GAGA, one per env) |\n|---|---|---|\n| Staging | `https://<stg-host>` | `<stg secret>` |\n| Production | `https://<prod-host>` | `<prod secret>` — never shared with staging |\n\n## Signature\n\n🔶 S2 — `md5str` rule.\n\nEvery request carries `time` and `md5str`.\n\n1. Take all request parameters **except `md5str`**.\n2. Sort by parameter **name**, ASCII ascending.\n3. Join as `name=value` with `&`. Use raw values (not URL-encoded). Include empty values as `name=`.\n4. Append `&key=<SECRET>`.\n5. `md5str` = MD5 of the UTF-8 string, **lowercase hex** (32 chars).\n\nReject if `|now − time| > 300` seconds (status `-2`). Compare `md5str` in constant time.\n\n**Worked example** (secret = `test_secret_123`), `/gmtool/role/orders`:\n\n```text\ncid=2356423&currency=VND&items=1:900001:1100&orderid=GW20261007A1B2C3D4&payment=funtap&price=250000&productid=GCOIN_1000&sid=1&time=1791360000&xluserid=VNGA284190375216&key=test_secret_123\n→ md5str = 6a2352e8c0632b43fd45ef28213680a0\n```\n\n`/gmtool/server/list` with only `time=1791360000` → `time=1791360000&key=test_secret_123` → `55a7ec1d94c646c71fd63a962474a2b2`\n\n## Response format\n\n🔶 S7\n\nHTTP is always `200` when the request reached the game logic; the result is in `status`.\n\n```json\n{ \"status\": \"1\", \"msg\": \"OK\", \"...\": \"endpoint data\" }\n```\n\n| status | Meaning | GAGA retries? |\n|---|---|---|\n| `1` | Success (also for a duplicate `orderid` / `refid`, see Idempotency) | — |\n| `-1` | Invalid `md5str` | No — alert |\n| `-2` | `time` outside ±300 s | Yes (once, after clock check) |\n| `-3` | Missing / invalid parameter | No |\n| `-4` | Server (`sid`) not found | No |\n| `-5` | Character not found / not owned by `xluserid` | No |\n| `-6` | Unknown item / invalid amount / mailbox cannot receive | No — alert |\n| `-7` | `orderid` / `refid` already used with **different** content | No — alert |\n| `-8` | Gift code invalid | No |\n| `-9` | Gift code already used / limit reached | No |\n| `-10` | Gift code expired | No |\n| `-98` | Server under maintenance | Yes |\n| `-99` | Internal error | Yes |\n\nHTTP `5xx` or timeout → GAGA treats as `-99` (retry). If you already have your own codes, send the list and we map them.\n\n## Idempotency\n\n🔶 S3, S5, S6\n\n- `orderid` (GAGA transaction id) is unique per purchase. Store it with a **unique constraint** in the same DB\n  transaction that creates the delivery.\n- Same `orderid` + same content again → **do not deliver again**; return `status:\"1\"`, `duplicate:true`.\n- Same `orderid` + different `xluserid` / `sid` / `cid` / `items` → `status:\"-7\"`.\n- GAGA retry: on timeout / `-98` / `-99` GAGA first calls `/gmtool/role/orders/query`.\n  `found:true` → done. `found:false` → resend `/orders` with the **same** `orderid`.\n  Back-off 30 s, 2 min, 10 min, 1 h, then every 6 h up to 72 h, then manual.\n- Keep `orderid` records ≥ 90 days. Same rules apply to `refid` on `sendmail`.\n\n## Items format\n\n`items` = `<Type>:<ID>:<Amount>`, multiple joined with `,` — e.g. `1:900001:1100,2:500123:1`.\n🔶 Please send: the list of `Type` values, the **G Coin** `Type:ID`, max items per mail, and mailbox expiry rule.\n\n## What X-Legend provides\n\n1. Staging + production base URLs, and the secret (or we generate it).\n2. G Coin `Type:ID`, other item IDs GAGA may send, `Type` list.\n3. Confirmation / counter-proposal for S1–S8.\n4. A staging server + test accounts with characters for joint testing with Funtap.\n\n## Not in scope\n\nGAGA SDK, game client UI, Google Play / App Store IAP, refunds / item removal (agree separately).\n",
    "contact": {
      "name": "Praneat × GAGA"
    }
  },
  "servers": [
    {
      "url": "https://{host}",
      "description": "Game server gmtool (staging or production) — host provided by X-Legend",
      "variables": {
        "host": {
          "default": "gmtool.example.com"
        }
      }
    }
  ],
  "security": [],
  "tags": [
    {
      "name": "Server & Character",
      "description": "Lookups while the player picks server + character on Web Shop / Funtap."
    },
    {
      "name": "Delivery",
      "description": "Deliver purchased items, check delivery, send reward mail."
    },
    {
      "name": "Gift Code",
      "description": "Redeem promotional / Item Code serials."
    }
  ],
  "paths": {
    "/gmtool/server/list": {
      "post": {
        "tags": [
          "Server & Character"
        ],
        "operationId": "getServerList",
        "summary": "(1) Get server list",
        "description": "X-Legend endpoint, unchanged. GAGA caches the result for 5 minutes.\n\n🔶 GAGA suggestion: add optional `status` (`open` / `maintenance`) so the Web Shop can grey out a server.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "$ref": "#/components/schemas/SignedRequest"
              },
              "example": {
                "time": "1791360000",
                "md5str": "55a7ec1d94c646c71fd63a962474a2b2"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Server list",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Result"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "servers": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Server"
                          }
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "status": "1",
                  "msg": "OK",
                  "servers": [
                    {
                      "sid": "1",
                      "name": "Server1",
                      "status": "open"
                    },
                    {
                      "sid": "2",
                      "name": "Server2",
                      "status": "maintenance"
                    }
                  ]
                }
              }
            }
          }
        }
      }
    },
    "/gmtool/role/query": {
      "post": {
        "tags": [
          "Server & Character"
        ],
        "operationId": "queryRole",
        "summary": "(2) Query character",
        "description": "X-Legend endpoint. Existing types `aid` / `cid` → returns `xluserid` (kept as is).\n\n🔶 **GAGA suggestion S1 — required for Web Shop / Funtap:** add `type=xluserid` with `data` = GAGA ID and `sid`.\nReturns **all characters** of that GAGA ID on that server (array, player picks one).\nNo character on that server → `status:\"-5\"`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/SignedRequest"
                  },
                  {
                    "type": "object",
                    "required": [
                      "type",
                      "data"
                    ],
                    "properties": {
                      "type": {
                        "type": "string",
                        "enum": [
                          "aid",
                          "cid",
                          "xluserid"
                        ],
                        "description": "`aid` / `cid` = existing. `xluserid` = 🔶 new (GAGA ID)."
                      },
                      "data": {
                        "type": "string",
                        "description": "The ID matching `type`."
                      },
                      "sid": {
                        "type": "string",
                        "description": "Server id. 🔶 Required when `type=xluserid`."
                      }
                    }
                  }
                ]
              },
              "examples": {
                "byGagaId": {
                  "summary": "🔶 New — find characters by GAGA ID",
                  "value": {
                    "type": "xluserid",
                    "data": "VNGA284190375216",
                    "sid": "1",
                    "time": "1791360000",
                    "md5str": "8b15a7185de7c817ccc46910f2ba36c9"
                  }
                },
                "byCid": {
                  "summary": "Existing — cid → xluserid",
                  "value": {
                    "type": "cid",
                    "data": "2356423",
                    "time": "1791360000",
                    "md5str": "68ead5f0b3113c7d21f9d3f4d1c78d3d"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Result"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "xluserid": {
                          "type": "string",
                          "description": "Returned for `type=aid|cid` (existing behaviour)."
                        },
                        "roles": {
                          "type": "array",
                          "description": "🔶 Returned for `type=xluserid`.",
                          "items": {
                            "$ref": "#/components/schemas/Role"
                          }
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "byGagaId": {
                    "summary": "🔶 type=xluserid",
                    "value": {
                      "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"
                        }
                      ]
                    }
                  },
                  "byCid": {
                    "summary": "type=cid (existing)",
                    "value": {
                      "status": "1",
                      "msg": "OK",
                      "xluserid": "VNGA284190375216"
                    }
                  },
                  "notFound": {
                    "summary": "No character",
                    "value": {
                      "status": "-5",
                      "msg": "Role not found"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/gmtool/role/orders": {
      "post": {
        "tags": [
          "Delivery"
        ],
        "operationId": "fulfillOrder",
        "summary": "(4) Top-up order fulfillment",
        "description": "X-Legend endpoint. Used for **every paid delivery** (Web Shop and Funtap), after GAGA has verified the payment.\nReturn `status:\"1\"` only after the delivery is committed.\n\n🔶 S3 — idempotent on `orderid` (see [Idempotency](#section/Idempotency)).\n\n🔶 S4 — GAGA sends `items` (what to deliver). `productid` is for your reports only.\nIf the game **must** deliver by `productid`, please send the product catalog\n(`productid` → items) and we will use your IDs in GAGA CMS instead.\n\nDelivery to mailbox vs direct to wallet: your choice — please tell us which (affects the `orders/query` status).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/SignedRequest"
                  },
                  {
                    "$ref": "#/components/schemas/OrderRequest"
                  }
                ]
              },
              "example": {
                "xluserid": "VNGA284190375216",
                "sid": "1",
                "cid": "2356423",
                "orderid": "GW20261007A1B2C3D4",
                "productid": "GCOIN_1000",
                "items": "1:900001:1100",
                "price": "250000",
                "currency": "VND",
                "payment": "funtap",
                "time": "1791360000",
                "md5str": "6a2352e8c0632b43fd45ef28213680a0"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Result"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "orderid": {
                          "type": "string"
                        },
                        "duplicate": {
                          "type": "boolean",
                          "description": "🔶 `true` = this orderid was already delivered; nothing new was sent."
                        },
                        "delivered_at": {
                          "type": "integer",
                          "description": "Unix seconds."
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "ok": {
                    "summary": "First call",
                    "value": {
                      "status": "1",
                      "msg": "OK",
                      "orderid": "GW20261007A1B2C3D4",
                      "duplicate": false,
                      "delivered_at": 1791360003
                    }
                  },
                  "duplicate": {
                    "summary": "Retry with the same orderid",
                    "value": {
                      "status": "1",
                      "msg": "Already delivered",
                      "orderid": "GW20261007A1B2C3D4",
                      "duplicate": true,
                      "delivered_at": 1791360003
                    }
                  },
                  "conflict": {
                    "summary": "Same orderid, different content",
                    "value": {
                      "status": "-7",
                      "msg": "orderid used with different content"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/gmtool/role/orders/query": {
      "post": {
        "tags": [
          "Delivery"
        ],
        "operationId": "queryOrder",
        "summary": "(6) 🔶 Query order — new",
        "description": "🔶 **GAGA suggestion S5 — new endpoint.**\nReturns the delivery for an `orderid`. GAGA calls it after a timeout / `-98` / `-99` on `/orders`,\nand in daily reconciliation. `found:false` → GAGA resends `/orders` with the same `orderid`.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/SignedRequest"
                  },
                  {
                    "type": "object",
                    "required": [
                      "orderid"
                    ],
                    "properties": {
                      "orderid": {
                        "type": "string",
                        "maxLength": 64
                      }
                    }
                  }
                ]
              },
              "example": {
                "orderid": "GW20261007A1B2C3D4",
                "time": "1791360000",
                "md5str": "bf1118a9437c2bdfc60a797c8f01759f"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Result"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "found": {
                          "type": "boolean"
                        },
                        "order": {
                          "$ref": "#/components/schemas/OrderRecord"
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "found": {
                    "value": {
                      "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
                      }
                    }
                  },
                  "notFound": {
                    "value": {
                      "status": "1",
                      "msg": "OK",
                      "found": false
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/gmtool/role/sendmail": {
      "post": {
        "tags": [
          "Delivery"
        ],
        "operationId": "sendMail",
        "summary": "(3) Send mail / items",
        "description": "X-Legend endpoint. For **non-purchase** rewards: pre-register rewards (launch-day batch), event rewards, compensation.\nPaid orders go through `/gmtool/role/orders`, not this endpoint.\n\n🔶 S6 — use `xluserid` instead of `uid` (same as other endpoints; tell us if `uid` is a different ID).\nAdd optional `refid`: if present, apply the same idempotency rules as `orderid`\n(launch-day batch will be retried on failure).\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/SignedRequest"
                  },
                  {
                    "type": "object",
                    "required": [
                      "xluserid",
                      "sid",
                      "cid",
                      "title",
                      "body",
                      "items"
                    ],
                    "properties": {
                      "xluserid": {
                        "type": "string",
                        "description": "GAGA ID. (Original spec field name `uid` — 🔶 please confirm.)"
                      },
                      "sid": {
                        "type": "string"
                      },
                      "cid": {
                        "type": "string"
                      },
                      "title": {
                        "type": "string",
                        "maxLength": 50
                      },
                      "body": {
                        "type": "string",
                        "maxLength": 500
                      },
                      "items": {
                        "$ref": "#/components/schemas/Items"
                      },
                      "refid": {
                        "type": "string",
                        "maxLength": 64,
                        "description": "🔶 Optional idempotency key, e.g. `PREREG-2026-VN-000123`."
                      }
                    }
                  }
                ]
              },
              "example": {
                "xluserid": "VNGA284190375216",
                "sid": "1",
                "cid": "2356423",
                "title": "Pre-register reward",
                "body": "Thank you for pre-registering!",
                "items": "2:500123:1,2:500124:5",
                "refid": "PREREG-2026-VN-000123",
                "time": "1791360000",
                "md5str": "5f24801d3214e88f7f09818faed5668c"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Result"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "mailid": {
                          "type": "string"
                        },
                        "duplicate": {
                          "type": "boolean"
                        }
                      }
                    }
                  ]
                },
                "example": {
                  "status": "1",
                  "msg": "OK",
                  "mailid": "M-88123901",
                  "duplicate": false
                }
              }
            }
          }
        }
      }
    },
    "/gmtool/role/giftcode": {
      "post": {
        "tags": [
          "Gift Code"
        ],
        "operationId": "redeemGiftCode",
        "summary": "(5) Redeem gift / promo code",
        "description": "X-Legend endpoint. Used when a player redeems a code on the GAGA website (Item Code / marketing serials).\n\n🔶 GAGA suggestion: return the granted `items`, and use distinct statuses for invalid / used / expired (`-8` / `-9` / `-10`)\nso the website can show the right message. Please confirm who generates the codes (game or GAGA)\nand whether one code is per-account or per-character.\n",
        "requestBody": {
          "required": true,
          "content": {
            "application/x-www-form-urlencoded": {
              "schema": {
                "allOf": [
                  {
                    "$ref": "#/components/schemas/SignedRequest"
                  },
                  {
                    "type": "object",
                    "required": [
                      "xluserid",
                      "sid",
                      "cid",
                      "code"
                    ],
                    "properties": {
                      "xluserid": {
                        "type": "string"
                      },
                      "sid": {
                        "type": "string"
                      },
                      "cid": {
                        "type": "string"
                      },
                      "code": {
                        "type": "string",
                        "maxLength": 64
                      }
                    }
                  }
                ]
              },
              "example": {
                "xluserid": "VNGA284190375216",
                "sid": "1",
                "cid": "2356423",
                "code": "GAGA-OBT-7K2M9Q",
                "time": "1791360000",
                "md5str": "bf4f43677753035b4de5d36654fc4d1d"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Result",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Result"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "items": {
                          "$ref": "#/components/schemas/Items"
                        }
                      }
                    }
                  ]
                },
                "examples": {
                  "ok": {
                    "value": {
                      "status": "1",
                      "msg": "OK",
                      "items": "2:500123:1"
                    }
                  },
                  "used": {
                    "value": {
                      "status": "-9",
                      "msg": "Code already used"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "SignedRequest": {
        "type": "object",
        "required": [
          "time",
          "md5str"
        ],
        "properties": {
          "time": {
            "type": "string",
            "description": "🔶 Unix seconds. Rejected if more than 300 s from server time.",
            "example": "1791360000"
          },
          "md5str": {
            "type": "string",
            "description": "See [Signature](#section/Signature).",
            "pattern": "^[0-9a-f]{32}$"
          }
        }
      },
      "Result": {
        "type": "object",
        "required": [
          "status",
          "msg"
        ],
        "properties": {
          "status": {
            "type": "string",
            "description": "`1` = success. Negative = error, see [Response format](#section/Response-format).",
            "example": "1"
          },
          "msg": {
            "type": "string",
            "description": "For logs only; GAGA does not parse it.",
            "example": "OK"
          }
        }
      },
      "Server": {
        "type": "object",
        "required": [
          "sid",
          "name"
        ],
        "properties": {
          "sid": {
            "type": "string",
            "example": "1"
          },
          "name": {
            "type": "string",
            "example": "Server1"
          },
          "status": {
            "type": "string",
            "enum": [
              "open",
              "maintenance"
            ],
            "description": "🔶 Optional."
          }
        }
      },
      "Role": {
        "type": "object",
        "required": [
          "sid",
          "cid",
          "name"
        ],
        "properties": {
          "sid": {
            "type": "string"
          },
          "cid": {
            "type": "string"
          },
          "name": {
            "type": "string",
            "description": "UTF-8 — may be CJK / Vietnamese / Thai"
          },
          "level": {
            "type": "integer",
            "description": "Optional — helps the player pick."
          },
          "os": {
            "type": "string",
            "enum": [
              "android",
              "ios",
              "pc"
            ],
            "description": "Required by Funtap find-role."
          }
        }
      },
      "Items": {
        "type": "string",
        "description": "`<Type>:<ID>:<Amount>` joined with `,`.",
        "pattern": "^\\d+:\\d+:\\d+(,\\d+:\\d+:\\d+)*$",
        "example": "1:900001:1100"
      },
      "OrderRequest": {
        "type": "object",
        "required": [
          "xluserid",
          "sid",
          "cid",
          "orderid",
          "productid",
          "items",
          "price",
          "currency",
          "payment"
        ],
        "properties": {
          "xluserid": {
            "type": "string",
            "description": "GAGA ID"
          },
          "sid": {
            "type": "string"
          },
          "cid": {
            "type": "string"
          },
          "orderid": {
            "type": "string",
            "maxLength": 64,
            "description": "GAGA transaction id. **Idempotency key.**"
          },
          "productid": {
            "type": "string",
            "description": "GAGA package id. 🔶 Audit only (S4)."
          },
          "items": {
            "$ref": "#/components/schemas/Items"
          },
          "price": {
            "type": "string",
            "description": "Amount paid, decimal string in major units (VND has no decimals; THB/USD up to 2).",
            "example": "250000"
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217",
            "enum": [
              "VND",
              "THB",
              "USD"
            ]
          },
          "payment": {
            "type": "string",
            "description": "Sales channel.",
            "enum": [
              "funtap",
              "chillpay",
              "razer"
            ]
          }
        }
      },
      "OrderRecord": {
        "type": "object",
        "properties": {
          "orderid": {
            "type": "string"
          },
          "xluserid": {
            "type": "string"
          },
          "sid": {
            "type": "string"
          },
          "cid": {
            "type": "string"
          },
          "items": {
            "$ref": "#/components/schemas/Items"
          },
          "state": {
            "type": "string",
            "enum": [
              "delivered",
              "claimed",
              "expired"
            ],
            "description": "`delivered` = in mailbox/wallet, `claimed` = player took it, `expired` = mail expired unclaimed."
          },
          "delivered_at": {
            "type": "integer"
          },
          "claimed_at": {
            "type": "integer",
            "nullable": true
          }
        }
      }
    }
  },
  "x-tagGroups": [
    {
      "name": "Game Server API (gmtool)",
      "tags": [
        "Server & Character",
        "Delivery",
        "Gift Code"
      ]
    }
  ]
}