Developer reference
Handle every success and failure path
Integrations must treat three layers separately: HTTP status, job
status, and per-voucher success /
err_code. This page catalogs them all.
1) Three response layers
Never conflate these. A request can be HTTP 200 with a failed redeem inside.
| Layer | Where | Means |
|---|---|---|
| HTTP | Status line + optional JSON detail |
Auth, validation, rate limits, resource existence. Does not mean UC was credited. |
| Job | GET /v1/jobs/{id} → status / error |
Queue lifecycle: pending → running → done or failed. |
| Outcome | result.success, result.results[].success, err_code, check-code ok |
Whether each voucher redeemed / validated. Inspect even when job status is done. |
status: "done" means the worker finished.
For redeem jobs, check every row’s success (and err_code on failure).
Partial batches are common: some codes succeed, others return REDEEM_CODE_ALREADY_USED.
2) HTTP status codes
FastAPI-style errors use a JSON body with detail (string or object). Prefer reading detail.error when present.
| HTTP | When | Client action |
|---|---|---|
200 |
OK. For jobs: document returned (inspect status). For screenshot: binary WebP. |
Parse body; branch on job / outcome layers. |
400 |
Bad input: invalid player id, missing/invalid codes, purchase choice required, screenshot not applicable. | Fix payload; show detail.message to operators. |
401 |
Missing or invalid X-API-Key. |
Re-copy key from Telegram bot. |
403 |
No active plan + HTTP API add-on; or player banned on lookup. | Renew subscription, or block that Player ID. |
404 |
Unknown job; player not found (lookup); missing docs asset. | Verify id / Player ID. |
409 |
JOB_NOT_READY — screenshot while job still pending/running. |
Poll until done, then retry. |
429 |
Per-minute throttle (RATE_LIMIT / SCREENSHOT_RATE_LIMIT) or daily/trial quota_exhausted. |
Honor Retry-After: 60; call GET /v1/quota. |
500 |
Unexpected server error (e.g. ORDER_SCREENSHOT_FAILED). |
Retry with backoff; contact support if persistent. |
501 |
WEBP_ENCODE_FAILED. |
Server WebP support issue — use JSON order_details instead. |
502 |
Upstream lookup failure (LOOKUP_FAILED). |
Retry later; no quota charged for failed lookup. |
503 |
SERVICE_UNAVAILABLE (temporary capacity / account path). |
Retry in a few minutes. |
3) Structured detail.error codes
Returned on many non-2xx responses as {"detail":{"error":"…","message":"…"}} (or top-level error on some 429 bodies).
| error | HTTP | Endpoints | Meaning |
|---|---|---|---|
INVALID_PLAYER_ID | 400 | lookup, manual/stock redeem | Player id failed format validation. |
MISSING_CODES | 400 | check-code, manual-redeem | All codes empty after trim. |
TOO_MANY_CODES | 400 | check-code, manual-redeem | More than 10 vouchers in one job. |
INVALID_CODE | 400 | check-code, manual-redeem | Voucher at index not 18 alphanumeric chars. |
SAME_PLAN_CHOICE_REQUIRED | 400 | subscription/purchase | Re-buying active plan needs same_plan_action: extend | stack. |
PLAYER_NOT_FOUND | 404 | player/lookup | No character for that Player ID. |
PLAYER_BANNED | 403 | player/lookup | Account restricted; UC cannot be sent. |
SERVICE_UNAVAILABLE | 503 | player/lookup | Temporary upstream/account shortage. |
LOOKUP_FAILED | 502 | player/lookup | Generic upstream failure. |
RATE_LIMIT | 429 | audited API routes | Per-minute throttle; Retry-After: 60. |
SCREENSHOT_RATE_LIMIT | 429 | order/screenshot | Extra screenshot-only throttle. |
JOB_NOT_FOUND | 404 | order/screenshot | No job for this API key. |
JOB_NOT_READY | 409 | order/screenshot | Job still pending/running. |
JOB_NOT_SUCCESSFUL | 400 | order/screenshot | Job not done (e.g. failed). |
NO_ORDER_DETAILS | 400 | order/screenshot | No success row / check-code / failed redeem. |
WEBP_ENCODE_FAILED | 501 | order/screenshot | Server cannot encode WebP. |
ORDER_SCREENSHOT_FAILED | 500 | order/screenshot | Unexpected render error. |
Auth failures often use a plain string detail (Missing X-API-Key header, Invalid or revoked API key, subscription text). Quota on sync lookup may return detail: "quota_exhausted".
4) Job lifecycle
Create → poll → branch. Applies to check_code, manual_redeem, stock_redeem.
Create response (HTTP 200)
{
"job_id": "674a1b2c3d4e5f60718293a4",
"status": "pending"
}
Poll fields
| Field | Values / notes |
|---|---|
status | pending · running · done · failed |
type | check_code · manual_redeem · stock_redeem |
result | Present when worker finished with a result payload (typically done). Sanitized — no Midas emails/sessions. |
error | String when status is failed (job never produced a usable redeem result). |
Job-level error strings (status: failed)
| error | Meaning | Client action |
|---|---|---|
quota_exhausted / quota_exhausted:… | Daily/trial requests insufficient when worker ran. | GET /v1/quota; wait for UTC reset or upgrade. |
invalid_payload | Empty codes / bad picks after queue. | Fix request body. |
invalid_player_id | Player id rejected by worker. | Validate digits before POST. |
no_codes / inventory messages | Stock redeem could not reserve requested denominations. | GET /v1/stock/summary; lower picks. |
unknown_job_type:… | Internal mismatch (rare). | Contact support. |
5) Redeem outcome — err_code catalog
Used on manual_redeem / stock_redeem when job is
done. Inspect result.results[] (batch) or top-level
success / msg / err_code (single).
| err_code | success | Meaning | Recommended UX |
|---|---|---|---|
| (empty / omitted) | true | UC credited. Row includes order_details (receipt fields). |
Show success + optional screenshot via job_id. |
REDEEM_CODE_ALREADY_USED |
false | Voucher already redeemed elsewhere. | Mark code dead; do not retry same code. |
CODE_EXPIRED |
false | Voucher past expiry. | Mark expired; do not retry. |
PLAYER_NOT_FOUND |
false | No PUBG character for Player ID. Public msg is human-readable English. |
Ask buyer to re-check Player ID; run lookup first. |
PLAYER_BANNED |
false | Account restricted in-game; UC cannot be sent. | Refuse order / refund policy as you choose. |
PLAYER_RISK_LIMITED |
false | Player ID blocked from receiving UC (risk controls). | Advise buyer to contact PUBG support. |
ROLE_INACTIVE_LONG_TIME |
false | Wrong ID or account inactive for a very long time. | Ask player to log into PUBG Mobile, then retry. |
REDEEM_FAILED |
false | Neutral public failure (internal capacity/routing mapped away). | Retry later; verify Player ID; contact support if repeated. |
CDKEY_STATUS_INVALID |
false | Upstream rejected voucher state (not a clean unused code). | Run check-code; do not assume unused. |
MISSING_PRODUCT_INFO |
false | Could not map voucher to a product SKU. | Retry once; escalate if persistent. |
| other / empty on failure | false | Transport or transient upstream issues (may include gateway-style codes internally). | Show msg; retry with backoff. |
msg + err_code values above.
6) Check-code outcomes
Job usually ends done with a result object. Use ok, cdkey_status, and mapped err_code.
| Signal | Meaning |
|---|---|
ok: true + cdkey_status: 1 | Unused / available voucher (typical “safe to sell”). |
ok: true + cdkey_status: 2 | Already used (treat as dead inventory). |
err_code: INVALID_OR_EXPIRED_CODE | No usable code info from upstream (reason was no_code_info). |
err_code: CODE_EXPIRED | Confirmed expired; may include expired_confirmed: true. |
err_code: PLAYER_NOT_FOUND | Player context missing / not found during query path. |
err_code: ROLE_INACTIVE_LONG_TIME | Inactive / stale role for linked player context. |
err_code: SERVICE_UNAVAILABLE | Temporary service/account shortage (neutral message). |
cdkey_expires_at_unix / cdkey_expires_at_utc | Present when Midas returns parseable expiry. |
batch: true + results[] | Multi-code job; evaluate each row independently. |
7) Player lookup (synchronous)
GET /v1/player/lookup?player_id=… — no job. Quota 0.25 only on HTTP 200.
| HTTP | Body highlight | Notes |
|---|---|---|
| 200 | ok, player_id, player_name, quota_requests_charged |
Show nickname at checkout. |
| 400 | INVALID_PLAYER_ID or player_id is required |
Client validation failed. |
| 404 | PLAYER_NOT_FOUND |
No character — do not redeem. |
| 403 | PLAYER_BANNED |
Blocked account. |
| 429 | quota_exhausted |
Not enough remaining requests. |
| 502 | LOOKUP_FAILED |
No quota charged. |
| 503 | SERVICE_UNAVAILABLE |
Retry shortly. |
8) Order screenshot
POST /api/v1/order/screenshot with {"job_id":"…"}. Success = Content-Type: image/webp bytes.
| HTTP | error | When |
|---|---|---|
| 200 | — | WebP body written. |
| 404 | JOB_NOT_FOUND | Wrong id / other account. |
| 409 | JOB_NOT_READY | Still pending/running. |
| 400 | JOB_NOT_SUCCESSFUL | Job failed / not done. |
| 400 | NO_ORDER_DETAILS | No successful redeem row (or check-code job). |
| 429 | RATE_LIMIT / SCREENSHOT_RATE_LIMIT | Throttle; wait 60s. |
| 501 | WEBP_ENCODE_FAILED | Encoder unavailable. |
| 500 | ORDER_SCREENSHOT_FAILED | Unexpected render failure. |
9) JSON examples
Representative shapes your client should parse.
Redeem success (batch row)
{
"status": "done",
"type": "manual_redeem",
"error": null,
"result": {
"player_id": "5123456789",
"results": [
{
"success": true,
"msg": "",
"cdkey_name": "60 UC",
"charac_name": "Nickname",
"order_details": {
"status": "Success",
"product": "UC",
"rewards": "UC* 60",
"player_id": "5123456789",
"charac_name": "Nickname"
}
}
]
}
}
Redeem failure row (already used)
{
"status": "done",
"type": "manual_redeem",
"error": null,
"result": {
"player_id": "5123456789",
"results": [
{
"success": false,
"err_code": "REDEEM_CODE_ALREADY_USED",
"msg": "…"
}
]
}
}
Job failed (quota)
{
"status": "failed",
"type": "manual_redeem",
"result": null,
"error": "quota_exhausted:daily"
}
HTTP validation error
{
"detail": {
"error": "INVALID_CODE",
"message": "Invalid voucher format at index 0.",
"code_sample": "short"
}
}
Rate limit
{
"detail": "rate_limit_exceeded",
"error": "RATE_LIMIT"
}
10) Client checklist
Minimum branching for a production panel.
After every authenticated call
Handle 401 / 403 / 429 before parsing business data. Surface subscription problems separately from bad Player IDs.
After creating a job
Poll GET /v1/jobs/{id} until done or failed (2–3s interval). Cap wait time and show “still processing” UI.
When status is done (redeem)
Iterate results[]. Credit buyer only for success: true. Map err_code to clear storefront messages (used / expired / banned / not found).
When status is failed
Read error. If it starts with quota_exhausted, pause the queue and refresh quota. Inventory errors → restock.
Before redeem
Optional: lookup nickname; optional: check-code for inventory. Reduces refunds and support tickets.