{
  "openapi": "3.1.0",
  "info": {
    "title": "InKhmer Document Translator API",
    "version": "1.0.0",
    "description": "Prepaid AI document translation: upload a PDF, poll the job, download the watermark-free translated PDF. Authentication is a TosKey API key in `Authorization: Bearer <key>` with `translator:*` scopes. Every error body is `{ error, message, requestId, correlationId }` — `requestId` echoes the gateway `x-toskey-request-id`, `correlationId` is inkhmer-minted for support traces. `GET /jobs/:jobId*` returns 404 (not 403) for jobs owned by another key so job existence never leaks."
  },
  "servers": [
    {
      "url": "https://api.inkhmer.com"
    }
  ],
  "security": [
    {
      "BearerAuth": []
    }
  ],
  "tags": [
    {
      "name": "Translation Jobs",
      "description": "Create, poll, download and cancel translation jobs."
    }
  ],
  "paths": {
    "/v1/public/document-translator/jobs": {
      "post": {
        "operationId": "createJob",
        "tags": [
          "Translation Jobs"
        ],
        "summary": "Create a translation job (legacy multipart)",
        "description": "Streams a `multipart/form-data` upload (max 100 MiB file, max 1000 pages), debits the prepaid credits tier price, and returns `202` with a poll URL. Idempotent on `Idempotency-Key` per key within 24 h. Requires scope `translator:translate` (10/min · 100/hour · 1,000/day per key). **Superseded** by the direct-to-storage flow (`POST /jobs/intent` → `PUT <uploadUrl>` → `POST /jobs/{jobId}/finalize`) — retained as the fallback while `UPLOAD_MODE=stream` remains a supported server mode; new integrations should use the intent flow.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "idempotency-key",
            "in": "header",
            "required": false,
            "description": "Replay key. Re-POSTing with the same value within 24 h returns the original response without re-charging. Keys are per-API-key — two different keys using the same value start independent jobs. Replaying the same key with a different payload returns 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            },
            "example": "019a4d5e-6f7a-7b8c-9d0e-1f2a3b4c5d6e"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "target"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "The PDF to translate (max 100 MiB, 1000 pages). Only `application/pdf` parts are accepted."
                  },
                  "source": {
                    "type": "string",
                    "default": "auto",
                    "description": "Source language — `auto` (detected at extract time) or an ISO 639-1 code.",
                    "enum": [
                      "auto",
                      "aa",
                      "ab",
                      "ae",
                      "af",
                      "ak",
                      "am",
                      "an",
                      "ar",
                      "as",
                      "av",
                      "ay",
                      "az",
                      "ba",
                      "be",
                      "bg",
                      "bi",
                      "bm",
                      "bn",
                      "bo",
                      "br",
                      "bs",
                      "ca",
                      "ce",
                      "ch",
                      "co",
                      "cr",
                      "cs",
                      "cu",
                      "cv",
                      "cy",
                      "da",
                      "de",
                      "dv",
                      "dz",
                      "ee",
                      "el",
                      "en",
                      "eo",
                      "es",
                      "et",
                      "eu",
                      "fa",
                      "ff",
                      "fi",
                      "fj",
                      "fo",
                      "fr",
                      "fy",
                      "ga",
                      "gd",
                      "gl",
                      "gn",
                      "gu",
                      "gv",
                      "ha",
                      "he",
                      "hi",
                      "ho",
                      "hr",
                      "ht",
                      "hu",
                      "hy",
                      "hz",
                      "ia",
                      "id",
                      "ie",
                      "ig",
                      "ii",
                      "ik",
                      "io",
                      "is",
                      "it",
                      "iu",
                      "ja",
                      "jv",
                      "ka",
                      "kg",
                      "ki",
                      "kj",
                      "kk",
                      "kl",
                      "km",
                      "kn",
                      "ko",
                      "kr",
                      "ks",
                      "ku",
                      "kv",
                      "kw",
                      "ky",
                      "la",
                      "lb",
                      "lg",
                      "li",
                      "ln",
                      "lo",
                      "lt",
                      "lu",
                      "lv",
                      "mg",
                      "mh",
                      "mi",
                      "mk",
                      "ml",
                      "mn",
                      "mr",
                      "ms",
                      "mt",
                      "my",
                      "na",
                      "nb",
                      "nd",
                      "ne",
                      "ng",
                      "nl",
                      "nn",
                      "no",
                      "nr",
                      "nv",
                      "ny",
                      "oc",
                      "oj",
                      "om",
                      "or",
                      "os",
                      "pa",
                      "pi",
                      "pl",
                      "ps",
                      "pt",
                      "qu",
                      "rm",
                      "rn",
                      "ro",
                      "ru",
                      "rw",
                      "sa",
                      "sc",
                      "sd",
                      "se",
                      "sg",
                      "si",
                      "sk",
                      "sl",
                      "sm",
                      "sn",
                      "so",
                      "sq",
                      "sr",
                      "ss",
                      "st",
                      "su",
                      "sv",
                      "sw",
                      "ta",
                      "te",
                      "tg",
                      "th",
                      "ti",
                      "tk",
                      "tl",
                      "tn",
                      "to",
                      "tr",
                      "ts",
                      "tt",
                      "tw",
                      "ty",
                      "ug",
                      "uk",
                      "ur",
                      "uz",
                      "ve",
                      "vi",
                      "vo",
                      "wa",
                      "wo",
                      "xh",
                      "yi",
                      "yo",
                      "za",
                      "zh",
                      "zu"
                    ]
                  },
                  "target": {
                    "type": "string",
                    "description": "Target language, ISO 639-1 code.",
                    "enum": [
                      "aa",
                      "ab",
                      "ae",
                      "af",
                      "ak",
                      "am",
                      "an",
                      "ar",
                      "as",
                      "av",
                      "ay",
                      "az",
                      "ba",
                      "be",
                      "bg",
                      "bi",
                      "bm",
                      "bn",
                      "bo",
                      "br",
                      "bs",
                      "ca",
                      "ce",
                      "ch",
                      "co",
                      "cr",
                      "cs",
                      "cu",
                      "cv",
                      "cy",
                      "da",
                      "de",
                      "dv",
                      "dz",
                      "ee",
                      "el",
                      "en",
                      "eo",
                      "es",
                      "et",
                      "eu",
                      "fa",
                      "ff",
                      "fi",
                      "fj",
                      "fo",
                      "fr",
                      "fy",
                      "ga",
                      "gd",
                      "gl",
                      "gn",
                      "gu",
                      "gv",
                      "ha",
                      "he",
                      "hi",
                      "ho",
                      "hr",
                      "ht",
                      "hu",
                      "hy",
                      "hz",
                      "ia",
                      "id",
                      "ie",
                      "ig",
                      "ii",
                      "ik",
                      "io",
                      "is",
                      "it",
                      "iu",
                      "ja",
                      "jv",
                      "ka",
                      "kg",
                      "ki",
                      "kj",
                      "kk",
                      "kl",
                      "km",
                      "kn",
                      "ko",
                      "kr",
                      "ks",
                      "ku",
                      "kv",
                      "kw",
                      "ky",
                      "la",
                      "lb",
                      "lg",
                      "li",
                      "ln",
                      "lo",
                      "lt",
                      "lu",
                      "lv",
                      "mg",
                      "mh",
                      "mi",
                      "mk",
                      "ml",
                      "mn",
                      "mr",
                      "ms",
                      "mt",
                      "my",
                      "na",
                      "nb",
                      "nd",
                      "ne",
                      "ng",
                      "nl",
                      "nn",
                      "no",
                      "nr",
                      "nv",
                      "ny",
                      "oc",
                      "oj",
                      "om",
                      "or",
                      "os",
                      "pa",
                      "pi",
                      "pl",
                      "ps",
                      "pt",
                      "qu",
                      "rm",
                      "rn",
                      "ro",
                      "ru",
                      "rw",
                      "sa",
                      "sc",
                      "sd",
                      "se",
                      "sg",
                      "si",
                      "sk",
                      "sl",
                      "sm",
                      "sn",
                      "so",
                      "sq",
                      "sr",
                      "ss",
                      "st",
                      "su",
                      "sv",
                      "sw",
                      "ta",
                      "te",
                      "tg",
                      "th",
                      "ti",
                      "tk",
                      "tl",
                      "tn",
                      "to",
                      "tr",
                      "ts",
                      "tt",
                      "tw",
                      "ty",
                      "ug",
                      "uk",
                      "ur",
                      "uz",
                      "ve",
                      "vi",
                      "vo",
                      "wa",
                      "wo",
                      "xh",
                      "yi",
                      "yo",
                      "za",
                      "zh",
                      "zu"
                    ],
                    "example": "km"
                  },
                  "notes_pdf": {
                    "type": "string",
                    "enum": [
                      "true",
                      "false"
                    ],
                    "default": "false",
                    "description": "When `true`, append a notes page to the output."
                  }
                }
              },
              "encoding": {
                "file": {
                  "contentType": "application/pdf"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Job accepted — debit taken, engine dispatch queued. Poll `pollUrl` until `state` is `done` or `error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobCreated"
                },
                "example": {
                  "jobId": "019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d",
                  "pagesTotal": 72,
                  "tier": "t4",
                  "priceCredits": 999,
                  "walletAfter": 1301,
                  "pollUrl": "/v1/public/document-translator/jobs/019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — missing field or bad value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "`source`/`target` must be ISO 639-1 codes; `file` is required.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unrecognised API key.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "missing_authorization": {
                    "value": {
                      "error": "missing_authorization",
                      "message": "Send `Authorization: Bearer <api-key>`.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  },
                  "invalid_api_key": {
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is not recognised.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "402": {
            "description": "The wallet balance is below the tier price — top up and retry (this error is retryable).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiInsufficientCreditsError"
                },
                "examples": {
                  "insufficient_credits": {
                    "value": {
                      "error": "insufficient_credits",
                      "message": "Wallet balance is below the tier price.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "need": 999,
                      "have": 120,
                      "topUpUrl": "https://inkhmer.com/credits/topup"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but lacks the scope this operation requires.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "scope_missing": {
                    "value": {
                      "error": "scope_missing",
                      "message": "This key lacks the required scope.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "requiredScope": "translator:read"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The same `Idempotency-Key` was replayed with a different payload (different file hash/size).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "idempotency_conflict": {
                    "value": {
                      "error": "idempotency_conflict",
                      "message": "Idempotency-Key was already used with a different payload.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The file exceeds 100 MiB, or the document has more than 1000 pages (contact tier).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiPayloadTooLargeError"
                },
                "examples": {
                  "payload_too_large": {
                    "value": {
                      "error": "payload_too_large",
                      "message": "File exceeds the 100 MiB limit.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "maxBytes": 104857600,
                      "maxPages": 1000,
                      "nextStep": "split_or_contact"
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "The request Content-Type is not `multipart/form-data`, or the `file` part is not `application/pdf`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "unsupported_media_type": {
                    "value": {
                      "error": "unsupported_media_type",
                      "message": "Send `Content-Type: multipart/form-data` with a `file` part.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "accepted": [
                        "multipart/form-data"
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-key or per-account rate limit exhausted. Retry after the `Retry-After` interval (see the rate-limit table).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "rate_limited",
                      "message": "Rate limit exhausted — retry after the interval.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "retryAfter": 42
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Engine failure — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "internal_error": {
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A required upstream (OpenRouter, Bayla, key verification) is unavailable — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "upstream_unavailable": {
                    "value": {
                      "error": "upstream_unavailable",
                      "message": "A required upstream is unavailable — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/public/document-translator/jobs/intent": {
      "post": {
        "operationId": "createJobIntent",
        "tags": [
          "Translation Jobs"
        ],
        "summary": "Create an upload intent (direct-to-storage flow)",
        "description": "Step 1 of the presigned upload flow. Returns a job id in `intent` state plus a presigned PUT `uploadUrl` — PUT the document bytes directly to that URL (they never transit the API), then call `POST /jobs/{jobId}/finalize`. The URL carries a short expiry and a `content-length-range` cap; an intent abandoned past its window is swept to `abandoned`. Requires scope `translator:translate`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "idempotency-key",
            "in": "header",
            "required": false,
            "description": "Replay key. Re-POSTing with the same value within 24 h returns the original response without re-charging. Keys are per-API-key — two different keys using the same value start independent jobs. Replaying the same key with a different payload returns 409 idempotency_conflict.",
            "schema": {
              "type": "string",
              "minLength": 8,
              "maxLength": 255
            },
            "example": "019a4d5e-6f7a-7b8c-9d0e-1f2a3b4c5d6e"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/JobIntentRequest"
              },
              "example": {
                "filename": "report.pdf",
                "contentType": "application/pdf",
                "contentLength": 48122,
                "source": "auto",
                "target": "km"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Intent created — PUT `uploadUrl`, then POST `finalizeUrl`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/JobIntent"
                },
                "example": {
                  "jobId": "019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d",
                  "uploadUrl": "https://storage.example.com/bkt/key?X-Amz-Signature=…",
                  "uploadMethod": "PUT",
                  "uploadHeaders": {
                    "Content-Type": "application/pdf"
                  },
                  "uploadExpiresAt": "2026-10-18T02:00:00Z",
                  "finalizeUrl": "/v1/public/document-translator/jobs/019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d/finalize"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — missing field or bad value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "`filename`, `contentType`, `contentLength` and `target` are required.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unrecognised API key.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "missing_authorization": {
                    "value": {
                      "error": "missing_authorization",
                      "message": "Send `Authorization: Bearer <api-key>`.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  },
                  "invalid_api_key": {
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is not recognised.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but lacks the scope this operation requires.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "scope_missing": {
                    "value": {
                      "error": "scope_missing",
                      "message": "This key lacks the required scope.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "requiredScope": "translator:read"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The declared `contentLength` exceeds the 100 MiB limit.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiPayloadTooLargeError"
                },
                "examples": {
                  "payload_too_large": {
                    "value": {
                      "error": "payload_too_large",
                      "message": "File exceeds the 100 MiB limit.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "maxBytes": 104857600,
                      "nextStep": "split_or_contact"
                    }
                  }
                }
              }
            }
          },
          "415": {
            "description": "The declared `contentType` is not an accepted document type.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "unsupported_media_type": {
                    "value": {
                      "error": "unsupported_media_type",
                      "message": "Only `application/pdf` uploads are supported.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "accepted": [
                        "application/pdf"
                      ]
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-key or per-account rate limit exhausted. Retry after the `Retry-After` interval (see the rate-limit table).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "rate_limited",
                      "message": "Rate limit exhausted — retry after the interval.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "retryAfter": 42
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Engine failure — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "internal_error": {
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A required upstream (OpenRouter, Bayla, key verification) is unavailable — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "upstream_unavailable": {
                    "value": {
                      "error": "upstream_unavailable",
                      "message": "A required upstream is unavailable — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/public/document-translator/jobs/{jobId}": {
      "get": {
        "operationId": "getJob",
        "tags": [
          "Translation Jobs"
        ],
        "summary": "Get a translation job status",
        "description": "State machine: `uploading → detecting → translating → rendering → done | error | cancelled`. `pdfUrl` is set only when `state` is `done`. Requires scope `translator:read` (60/min per key — the documented poll interval is 2 s).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "description": "The job id returned by `POST /jobs` (uuidv7).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d"
          }
        ],
        "responses": {
          "200": {
            "description": "Current job snapshot.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                },
                "example": {
                  "jobId": "019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d",
                  "state": "translating",
                  "pagesTotal": 72,
                  "pagesDone": 24,
                  "source": "zh",
                  "target": "km",
                  "pdfUrl": null,
                  "error": null,
                  "createdAt": "2026-10-16T18:30:00+07:00",
                  "completedAt": null
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — missing field or bad value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "`jobId` must be a UUID.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unrecognised API key.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "missing_authorization": {
                    "value": {
                      "error": "missing_authorization",
                      "message": "Send `Authorization: Bearer <api-key>`.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  },
                  "invalid_api_key": {
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is not recognised.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but lacks the scope this operation requires.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "scope_missing": {
                    "value": {
                      "error": "scope_missing",
                      "message": "This key lacks the required scope.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "requiredScope": "translator:read"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The job does not exist — or exists under a different API key. Ownership misses deliberately return 404 (never 403) so job existence cannot be probed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "job_not_found": {
                    "value": {
                      "error": "job_not_found",
                      "message": "No such job.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-key or per-account rate limit exhausted. Retry after the `Retry-After` interval (see the rate-limit table).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "rate_limited",
                      "message": "Rate limit exhausted — retry after the interval.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "retryAfter": 42
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Engine failure — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "internal_error": {
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A required upstream (OpenRouter, Bayla, key verification) is unavailable — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "upstream_unavailable": {
                    "value": {
                      "error": "upstream_unavailable",
                      "message": "A required upstream is unavailable — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/public/document-translator/jobs/{jobId}/finalize": {
      "post": {
        "operationId": "finalizeJob",
        "tags": [
          "Translation Jobs"
        ],
        "summary": "Finalize a direct-to-storage upload",
        "description": "Step 3 of the presigned upload flow — call it after the PUT to `uploadUrl` succeeds. Confirms the object landed in storage, debits the prepaid credits tier price, and enqueues the translation; returns `202` with the job snapshot. A PUT that never landed answers `400`; an object that beat the `content-length-range` cap answers `413`. Ownership follows the §9 rule — only the key that minted the intent may finalize it. Requires scope `translator:translate`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "description": "The job id returned by `POST /jobs` (uuidv7).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d"
          }
        ],
        "responses": {
          "202": {
            "description": "Upload confirmed — debit taken, engine dispatch queued. Poll `GET /jobs/{jobId}` until `state` is `done` or `error`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Job"
                },
                "example": {
                  "jobId": "019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d",
                  "state": "uploading",
                  "pagesTotal": null,
                  "pagesDone": 0,
                  "source": "auto",
                  "target": "km",
                  "pdfUrl": null,
                  "error": null,
                  "createdAt": "2026-10-16T18:30:00+07:00",
                  "completedAt": null
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — missing field or bad value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "The upload has not landed in storage yet — retry after the PUT completes.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unrecognised API key.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "missing_authorization": {
                    "value": {
                      "error": "missing_authorization",
                      "message": "Send `Authorization: Bearer <api-key>`.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  },
                  "invalid_api_key": {
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is not recognised.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but lacks the scope this operation requires.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "scope_missing": {
                    "value": {
                      "error": "scope_missing",
                      "message": "This key lacks the required scope.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "requiredScope": "translator:read"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The job does not exist — or exists under a different API key. Ownership misses deliberately return 404 (never 403) so job existence cannot be probed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "job_not_found": {
                    "value": {
                      "error": "job_not_found",
                      "message": "No such job.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "413": {
            "description": "The stored object exceeds the 100 MiB cap — the `content-length-range` guard is the last line of defence when a signature lets an oversized PUT through.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiPayloadTooLargeError"
                },
                "examples": {
                  "payload_too_large": {
                    "value": {
                      "error": "payload_too_large",
                      "message": "File exceeds the 100 MiB limit.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "maxBytes": 104857600,
                      "nextStep": "split_or_contact"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-key or per-account rate limit exhausted. Retry after the `Retry-After` interval (see the rate-limit table).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "rate_limited",
                      "message": "Rate limit exhausted — retry after the interval.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "retryAfter": 42
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Engine failure — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "internal_error": {
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A required upstream (OpenRouter, Bayla, key verification) is unavailable — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "upstream_unavailable": {
                    "value": {
                      "error": "upstream_unavailable",
                      "message": "A required upstream is unavailable — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/public/document-translator/jobs/{jobId}/pdf": {
      "get": {
        "operationId": "getJobPdf",
        "tags": [
          "Translation Jobs"
        ],
        "summary": "Download the translated PDF",
        "description": "The watermark-free output PDF (`application/pdf`, `Content-Disposition: attachment; filename=\"translation-<jobId>.pdf\"`). Requires the SAME key the job was created with — any other key gets 404, never 403 (doc §9 existence-leak rule). Scope `translator:read` (10/min per key).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "description": "The job id returned by `POST /jobs` (uuidv7).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d"
          }
        ],
        "responses": {
          "200": {
            "description": "The watermark-free translated PDF.",
            "headers": {
              "Content-Disposition": {
                "schema": {
                  "type": "string"
                },
                "example": "attachment; filename=\"translation-<jobId>.pdf\""
              }
            },
            "content": {
              "application/pdf": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — missing field or bad value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "`jobId` must be a UUID.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unrecognised API key.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "missing_authorization": {
                    "value": {
                      "error": "missing_authorization",
                      "message": "Send `Authorization: Bearer <api-key>`.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  },
                  "invalid_api_key": {
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is not recognised.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but lacks the scope this operation requires.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "scope_missing": {
                    "value": {
                      "error": "scope_missing",
                      "message": "This key lacks the required scope.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "requiredScope": "translator:read"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The job does not exist — or exists under a different API key. Ownership misses deliberately return 404 (never 403) so job existence cannot be probed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "job_not_found": {
                    "value": {
                      "error": "job_not_found",
                      "message": "No such job.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-key or per-account rate limit exhausted. Retry after the `Retry-After` interval (see the rate-limit table).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "rate_limited",
                      "message": "Rate limit exhausted — retry after the interval.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "retryAfter": 42
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Engine failure — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "internal_error": {
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A required upstream (OpenRouter, Bayla, key verification) is unavailable — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "upstream_unavailable": {
                    "value": {
                      "error": "upstream_unavailable",
                      "message": "A required upstream is unavailable — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/public/document-translator/jobs/{jobId}/pdf-url": {
      "get": {
        "operationId": "getJobPdfUrl",
        "tags": [
          "Translation Jobs"
        ],
        "summary": "Mint a presigned download URL (direct-from-storage)",
        "description": "Step 5 of the direct-download flow — returns a short-lived presigned GET `url` the client fetches straight from object storage (the bytes never transit the API). The TTL is tier-scoped (anonymous ≈5 min · free ≈15 min · paid-wallet ≈60 min · paid-api ≈15 min). Requires the SAME key the job was created with — any other key gets 404, never 403 (doc §9 existence-leak rule). Scope `translator:read` (10/min per key). Blocked jobs answer `403`.",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "description": "The job id returned by `POST /jobs` (uuidv7).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d"
          }
        ],
        "responses": {
          "200": {
            "description": "Presigned GET — fetch `url` directly from storage before `expiresAt`.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PdfUrl"
                },
                "example": {
                  "url": "https://storage.example.com/bkt/key?X-Amz-Expires=900&X-Amz-Signature=…",
                  "method": "GET",
                  "expiresAt": "2026-10-18T02:15:00Z",
                  "filename": "translation-019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d-km.pdf"
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — missing field or bad value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "`jobId` must be a UUID.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unrecognised API key.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "missing_authorization": {
                    "value": {
                      "error": "missing_authorization",
                      "message": "Send `Authorization: Bearer <api-key>`.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  },
                  "invalid_api_key": {
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is not recognised.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The job exists but its download is blocked (abuse investigation or tier policy) — or the rolled-back server is in `DOWNLOAD_MODE=stream`, where only the legacy `GET /jobs/{jobId}/pdf` endpoint serves bytes.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "download_blocked": {
                    "value": {
                      "error": "download_blocked",
                      "message": "Downloads are blocked for this job.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "reason": "abuse",
                      "blockedAt": "2026-10-18T02:00:00Z"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The job does not exist — or exists under a different API key. Ownership misses deliberately return 404 (never 403) so job existence cannot be probed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "job_not_found": {
                    "value": {
                      "error": "job_not_found",
                      "message": "No such job.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-key or per-account rate limit exhausted. Retry after the `Retry-After` interval (see the rate-limit table).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "rate_limited",
                      "message": "Rate limit exhausted — retry after the interval.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "retryAfter": 42
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Engine failure — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "internal_error": {
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A required upstream (OpenRouter, Bayla, key verification) is unavailable — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "upstream_unavailable": {
                    "value": {
                      "error": "upstream_unavailable",
                      "message": "A required upstream is unavailable — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/v1/public/document-translator/jobs/{jobId}/cancel": {
      "post": {
        "operationId": "cancelJob",
        "tags": [
          "Translation Jobs"
        ],
        "summary": "Cancel a translation job",
        "description": "Refunds the prepaid debit by the §3.5 rule: full within 60 s of acceptance; `tierPrice × (1 - pagesDone/pagesTotal)` while `uploading`…`translating`; nothing once `rendering` — a finished job answers `409 job_already_done`. Requires scope `translator:cancel` (10/min per key).",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "jobId",
            "in": "path",
            "required": true,
            "description": "The job id returned by `POST /jobs` (uuidv7).",
            "schema": {
              "type": "string",
              "format": "uuid"
            },
            "example": "019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d"
          }
        ],
        "responses": {
          "200": {
            "description": "Job cancelled — credits refunded per the rule.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CancelResult"
                },
                "example": {
                  "state": "cancelled",
                  "refundedCredits": 999
                }
              }
            }
          },
          "400": {
            "description": "Malformed request — missing field or bad value.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "invalid_request": {
                    "value": {
                      "error": "invalid_request",
                      "message": "`jobId` must be a UUID.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Missing or unrecognised API key.",
            "headers": {
              "WWW-Authenticate": {
                "schema": {
                  "type": "string"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "missing_authorization": {
                    "value": {
                      "error": "missing_authorization",
                      "message": "Send `Authorization: Bearer <api-key>`.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  },
                  "invalid_api_key": {
                    "value": {
                      "error": "invalid_api_key",
                      "message": "The API key is not recognised.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "403": {
            "description": "The key is valid but lacks the scope this operation requires.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "scope_missing": {
                    "value": {
                      "error": "scope_missing",
                      "message": "This key lacks the required scope.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "requiredScope": "translator:read"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "description": "The job does not exist — or exists under a different API key. Ownership misses deliberately return 404 (never 403) so job existence cannot be probed.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "job_not_found": {
                    "value": {
                      "error": "job_not_found",
                      "message": "No such job.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "409": {
            "description": "The job already reached `rendering` or a terminal state — it cannot be cancelled and is not refunded.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "job_already_done": {
                    "value": {
                      "error": "job_already_done",
                      "message": "The job is already finished — it cannot be cancelled.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "state": "done"
                    }
                  }
                }
              }
            }
          },
          "429": {
            "description": "Per-key or per-account rate limit exhausted. Retry after the `Retry-After` interval (see the rate-limit table).",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                }
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "rate_limited": {
                    "value": {
                      "error": "rate_limited",
                      "message": "Rate limit exhausted — retry after the interval.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
                      "retryAfter": 42
                    }
                  }
                }
              }
            }
          },
          "500": {
            "description": "Engine failure — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "internal_error": {
                    "value": {
                      "error": "internal_error",
                      "message": "Something went wrong — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          },
          "503": {
            "description": "A required upstream (OpenRouter, Bayla, key verification) is unavailable — the wallet debit is refunded when one was taken.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TranslatorApiError"
                },
                "examples": {
                  "upstream_unavailable": {
                    "value": {
                      "error": "upstream_unavailable",
                      "message": "A required upstream is unavailable — retry shortly.",
                      "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
                      "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "API key",
        "description": "TosKey API key (`sk_live_…` / `sk_test_…`) issued from the developer console. Scopes: `translator:translate`, `translator:read`, `translator:cancel` — or the `translator:*` superset."
      }
    },
    "parameters": {
      "JobId": {
        "name": "jobId",
        "in": "path",
        "required": true,
        "description": "The job id returned by `POST /jobs` (uuidv7).",
        "schema": {
          "type": "string",
          "format": "uuid"
        },
        "example": "019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d"
      },
      "IdempotencyKey": {
        "name": "idempotency-key",
        "in": "header",
        "required": false,
        "description": "Replay key. Re-POSTing with the same value within 24 h returns the original response without re-charging. Keys are per-API-key — two different keys using the same value start independent jobs. Replaying the same key with a different payload returns 409 idempotency_conflict.",
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 255
        },
        "example": "019a4d5e-6f7a-7b8c-9d0e-1f2a3b4c5d6e"
      }
    },
    "schemas": {
      "JobIntentRequest": {
        "type": "object",
        "required": [
          "filename",
          "contentType",
          "contentLength",
          "target"
        ],
        "properties": {
          "filename": {
            "type": "string",
            "maxLength": 512,
            "description": "Original file name — metadata only."
          },
          "contentType": {
            "type": "string",
            "description": "`application/pdf`.",
            "enum": [
              "application/pdf"
            ]
          },
          "contentLength": {
            "type": "integer",
            "minimum": 1,
            "maximum": 104857600,
            "description": "Declared byte size — verified at finalize."
          },
          "source": {
            "type": "string",
            "default": "auto",
            "description": "Source language — `auto` or an ISO 639-1 code.",
            "enum": [
              "auto",
              "aa",
              "ab",
              "ae",
              "af",
              "ak",
              "am",
              "an",
              "ar",
              "as",
              "av",
              "ay",
              "az",
              "ba",
              "be",
              "bg",
              "bi",
              "bm",
              "bn",
              "bo",
              "br",
              "bs",
              "ca",
              "ce",
              "ch",
              "co",
              "cr",
              "cs",
              "cu",
              "cv",
              "cy",
              "da",
              "de",
              "dv",
              "dz",
              "ee",
              "el",
              "en",
              "eo",
              "es",
              "et",
              "eu",
              "fa",
              "ff",
              "fi",
              "fj",
              "fo",
              "fr",
              "fy",
              "ga",
              "gd",
              "gl",
              "gn",
              "gu",
              "gv",
              "ha",
              "he",
              "hi",
              "ho",
              "hr",
              "ht",
              "hu",
              "hy",
              "hz",
              "ia",
              "id",
              "ie",
              "ig",
              "ii",
              "ik",
              "io",
              "is",
              "it",
              "iu",
              "ja",
              "jv",
              "ka",
              "kg",
              "ki",
              "kj",
              "kk",
              "kl",
              "km",
              "kn",
              "ko",
              "kr",
              "ks",
              "ku",
              "kv",
              "kw",
              "ky",
              "la",
              "lb",
              "lg",
              "li",
              "ln",
              "lo",
              "lt",
              "lu",
              "lv",
              "mg",
              "mh",
              "mi",
              "mk",
              "ml",
              "mn",
              "mr",
              "ms",
              "mt",
              "my",
              "na",
              "nb",
              "nd",
              "ne",
              "ng",
              "nl",
              "nn",
              "no",
              "nr",
              "nv",
              "ny",
              "oc",
              "oj",
              "om",
              "or",
              "os",
              "pa",
              "pi",
              "pl",
              "ps",
              "pt",
              "qu",
              "rm",
              "rn",
              "ro",
              "ru",
              "rw",
              "sa",
              "sc",
              "sd",
              "se",
              "sg",
              "si",
              "sk",
              "sl",
              "sm",
              "sn",
              "so",
              "sq",
              "sr",
              "ss",
              "st",
              "su",
              "sv",
              "sw",
              "ta",
              "te",
              "tg",
              "th",
              "ti",
              "tk",
              "tl",
              "tn",
              "to",
              "tr",
              "ts",
              "tt",
              "tw",
              "ty",
              "ug",
              "uk",
              "ur",
              "uz",
              "ve",
              "vi",
              "vo",
              "wa",
              "wo",
              "xh",
              "yi",
              "yo",
              "za",
              "zh",
              "zu"
            ]
          },
          "target": {
            "type": "string",
            "description": "Target language, ISO 639-1 code.",
            "enum": [
              "aa",
              "ab",
              "ae",
              "af",
              "ak",
              "am",
              "an",
              "ar",
              "as",
              "av",
              "ay",
              "az",
              "ba",
              "be",
              "bg",
              "bi",
              "bm",
              "bn",
              "bo",
              "br",
              "bs",
              "ca",
              "ce",
              "ch",
              "co",
              "cr",
              "cs",
              "cu",
              "cv",
              "cy",
              "da",
              "de",
              "dv",
              "dz",
              "ee",
              "el",
              "en",
              "eo",
              "es",
              "et",
              "eu",
              "fa",
              "ff",
              "fi",
              "fj",
              "fo",
              "fr",
              "fy",
              "ga",
              "gd",
              "gl",
              "gn",
              "gu",
              "gv",
              "ha",
              "he",
              "hi",
              "ho",
              "hr",
              "ht",
              "hu",
              "hy",
              "hz",
              "ia",
              "id",
              "ie",
              "ig",
              "ii",
              "ik",
              "io",
              "is",
              "it",
              "iu",
              "ja",
              "jv",
              "ka",
              "kg",
              "ki",
              "kj",
              "kk",
              "kl",
              "km",
              "kn",
              "ko",
              "kr",
              "ks",
              "ku",
              "kv",
              "kw",
              "ky",
              "la",
              "lb",
              "lg",
              "li",
              "ln",
              "lo",
              "lt",
              "lu",
              "lv",
              "mg",
              "mh",
              "mi",
              "mk",
              "ml",
              "mn",
              "mr",
              "ms",
              "mt",
              "my",
              "na",
              "nb",
              "nd",
              "ne",
              "ng",
              "nl",
              "nn",
              "no",
              "nr",
              "nv",
              "ny",
              "oc",
              "oj",
              "om",
              "or",
              "os",
              "pa",
              "pi",
              "pl",
              "ps",
              "pt",
              "qu",
              "rm",
              "rn",
              "ro",
              "ru",
              "rw",
              "sa",
              "sc",
              "sd",
              "se",
              "sg",
              "si",
              "sk",
              "sl",
              "sm",
              "sn",
              "so",
              "sq",
              "sr",
              "ss",
              "st",
              "su",
              "sv",
              "sw",
              "ta",
              "te",
              "tg",
              "th",
              "ti",
              "tk",
              "tl",
              "tn",
              "to",
              "tr",
              "ts",
              "tt",
              "tw",
              "ty",
              "ug",
              "uk",
              "ur",
              "uz",
              "ve",
              "vi",
              "vo",
              "wa",
              "wo",
              "xh",
              "yi",
              "yo",
              "za",
              "zh",
              "zu"
            ]
          }
        }
      },
      "JobIntent": {
        "type": "object",
        "required": [
          "jobId",
          "uploadUrl",
          "uploadMethod",
          "uploadExpiresAt",
          "finalizeUrl"
        ],
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "uploadUrl": {
            "type": "string",
            "format": "uri",
            "description": "Presigned PUT — send the document bytes here, not to the API."
          },
          "uploadMethod": {
            "type": "string",
            "enum": [
              "PUT"
            ]
          },
          "uploadHeaders": {
            "type": "object",
            "additionalProperties": {
              "type": "string"
            },
            "description": "Headers the PUT MUST send verbatim — they are part of the signature."
          },
          "uploadExpiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "finalizeUrl": {
            "type": "string",
            "description": "Relative URL — `POST` it after the PUT lands.",
            "example": "/v1/public/document-translator/jobs/019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d/finalize"
          }
        }
      },
      "PdfUrl": {
        "type": "object",
        "required": [
          "url",
          "method",
          "expiresAt",
          "filename"
        ],
        "properties": {
          "url": {
            "type": "string",
            "format": "uri",
            "description": "Presigned GET — fetch it directly from storage before `expiresAt`."
          },
          "method": {
            "type": "string",
            "enum": [
              "GET"
            ]
          },
          "expiresAt": {
            "type": "string",
            "format": "date-time"
          },
          "filename": {
            "type": "string",
            "description": "Suggested attachment filename."
          }
        }
      },
      "JobCreated": {
        "type": "object",
        "required": [
          "jobId",
          "pagesTotal",
          "tier",
          "priceCredits",
          "walletAfter",
          "pollUrl"
        ],
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "pagesTotal": {
            "type": "integer",
            "minimum": 1
          },
          "tier": {
            "type": "string",
            "description": "Charged price tier (`t1`…`t7`).",
            "enum": [
              "t1",
              "t2",
              "t3",
              "t4",
              "t5",
              "t6",
              "t7"
            ]
          },
          "priceCredits": {
            "type": "integer",
            "minimum": 1
          },
          "walletAfter": {
            "type": "integer",
            "description": "Wallet balance after the prepaid debit."
          },
          "pollUrl": {
            "type": "string",
            "description": "Relative URL to poll — `/jobs/<jobId>`.",
            "example": "/v1/public/document-translator/jobs/019a2c3d-4e5f-7a9b-8c0d-1e2f3a4b5c6d"
          }
        }
      },
      "Job": {
        "type": "object",
        "required": [
          "jobId",
          "state",
          "pagesTotal",
          "pagesDone",
          "source",
          "target",
          "pdfUrl",
          "error",
          "createdAt",
          "completedAt"
        ],
        "properties": {
          "jobId": {
            "type": "string",
            "format": "uuid"
          },
          "state": {
            "type": "string",
            "enum": [
              "uploading",
              "detecting",
              "translating",
              "rendering",
              "done",
              "error",
              "cancelled"
            ]
          },
          "pagesTotal": {
            "type": [
              "integer",
              "null"
            ],
            "minimum": 1
          },
          "pagesDone": {
            "type": "integer",
            "minimum": 0,
            "description": "Pages translated so far — drives progress and the prorated cancel refund."
          },
          "source": {
            "type": [
              "string",
              "null"
            ],
            "description": "Detected (or declared) ISO 639-1 source."
          },
          "target": {
            "type": "string",
            "minLength": 2,
            "maxLength": 2
          },
          "pdfUrl": {
            "type": [
              "string",
              "null"
            ],
            "description": "Relative download path — set only when `state` is `done`."
          },
          "error": {
            "type": [
              "string",
              "null"
            ],
            "description": "Public error code when `state` is `error`, else `null`."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time"
          },
          "completedAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "CancelResult": {
        "type": "object",
        "required": [
          "state",
          "refundedCredits"
        ],
        "properties": {
          "state": {
            "type": "string",
            "enum": [
              "cancelled"
            ]
          },
          "refundedCredits": {
            "type": "integer",
            "minimum": 0,
            "description": "Credits returned to the wallet."
          }
        }
      },
      "TranslatorApiError": {
        "type": "object",
        "required": [
          "error",
          "message",
          "requestId",
          "correlationId"
        ],
        "description": "The §3.6 error envelope. Extra fields ride flat per code (`requiredScope`, `need`/`have`/`topUpUrl`, `maxBytes`/`maxPages`/`nextStep`, `retryAfter`, `state`).",
        "additionalProperties": true,
        "properties": {
          "error": {
            "type": "string",
            "description": "Machine-readable error code.",
            "enum": [
              "invalid_request",
              "missing_authorization",
              "invalid_api_key",
              "insufficient_credits",
              "scope_missing",
              "download_blocked",
              "job_not_found",
              "job_already_done",
              "idempotency_conflict",
              "payload_too_large",
              "unsupported_media_type",
              "rate_limited",
              "internal_error",
              "upstream_unavailable"
            ]
          },
          "message": {
            "type": "string"
          },
          "requestId": {
            "type": [
              "string",
              "null"
            ],
            "description": "TosKey gateway request id (`x-toskey-request-id` echo; `null` off-gateway)."
          },
          "correlationId": {
            "type": "string",
            "format": "uuid",
            "description": "inkhmer-minted trace id for support."
          }
        }
      },
      "TranslatorApiInsufficientCreditsError": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TranslatorApiError"
          },
          {
            "type": "object",
            "required": [
              "need",
              "have",
              "topUpUrl"
            ],
            "properties": {
              "need": {
                "type": "integer",
                "description": "Tier price in credits."
              },
              "have": {
                "type": "integer",
                "description": "Current balance."
              },
              "topUpUrl": {
                "type": "string",
                "format": "uri",
                "description": "Credit top-up page."
              }
            }
          }
        ]
      },
      "TranslatorApiPayloadTooLargeError": {
        "allOf": [
          {
            "$ref": "#/components/schemas/TranslatorApiError"
          },
          {
            "type": "object",
            "properties": {
              "maxBytes": {
                "type": "integer"
              },
              "maxPages": {
                "type": "integer"
              },
              "nextStep": {
                "type": "string",
                "enum": [
                  "split_or_contact",
                  "contact"
                ],
                "description": "`split_or_contact` for the byte cap, `contact` when pages exceed 1000 (sales tier)."
              }
            }
          }
        ]
      }
    }
  }
}
