Inkhmer LogoInKhmerBuilding the digital future
  • About
  • Projects
  • Blog
  • Tools
  • Learn
  • Contact
KMខ្មែរENEnglish

InKhmer.com — a personal Khmer Digital Hub curated by our team. Tools, Cambodia resources, Khmer culture, and digital projects. Practical, approachable, and built for everyone.

Social

FacebookTelegram

Quick Links

  • About
  • Projects
  • Blog
  • Tools
  • Learn
  • Contact

Explore

  • What We Build
  • How We Build
  • Roadmap
  • Experiments
  • Sitemap

Subscribe to our newsletter

Get the latest updates about Cambodia, new tools, and exclusive content delivered to your inbox.

  • Terms of Service
  • Privacy Policy
  • Cookie Policy

© 2026 InKhmer. All rights reserved.

  1. Home
  2. Developers
  3. Document Translator API
  4. createJobIntent

Create an upload intent (direct-to-storage flow)

POST/v1/public/document-translator/jobs/intent

← All operations
translator:translaterequired scope

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`.

Parameters

NameInRequiredDescription
idempotency-keyheader—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.e.g. 019a4d5e-6f7a-7b8c-9d0e-1f2a3b4c5d6e

Request & response

application/json

{
  "filename": "report.pdf",
  "contentType": "application/pdf",
  "contentLength": 48122,
  "source": "auto",
  "target": "km"
}

Error responses

200Intent created — PUT `uploadUrl`, then POST `finalizeUrl`.

application/json

{
  "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"
}
400Malformed request — missing field or bad value.

application/json

{
  "error": "invalid_request",
  "message": "`filename`, `contentType`, `contentLength` and `target` are required.",
  "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
  "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
}
401Missing or unrecognised API key.

application/json

{
  "error": "missing_authorization",
  "message": "Send `Authorization: Bearer <api-key>`.",
  "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
  "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
}
403The key is valid but lacks the scope this operation requires.

application/json

{
  "error": "scope_missing",
  "message": "This key lacks the required scope.",
  "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
  "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
  "requiredScope": "translator:read"
}
413The declared `contentLength` exceeds the 100 MiB limit.

application/json

{
  "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"
}
415The declared `contentType` is not an accepted document type.

application/json

{
  "error": "unsupported_media_type",
  "message": "Only `application/pdf` uploads are supported.",
  "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
  "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
  "accepted": [
    "application/pdf"
  ]
}
429Per-key or per-account rate limit exhausted. Retry after the `Retry-After` interval (see the rate-limit table).

application/json

{
  "error": "rate_limited",
  "message": "Rate limit exhausted — retry after the interval.",
  "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
  "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c",
  "retryAfter": 42
}
500Engine failure — the wallet debit is refunded when one was taken.

application/json

{
  "error": "internal_error",
  "message": "Something went wrong — retry shortly.",
  "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
  "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
}
503A required upstream (OpenRouter, Bayla, key verification) is unavailable — the wallet debit is refunded when one was taken.

application/json

{
  "error": "upstream_unavailable",
  "message": "A required upstream is unavailable — retry shortly.",
  "requestId": "trk_01HKA1B2C3D4E5F6G7H8J9K0M1",
  "correlationId": "019a1b2c-3d4e-7f8a-9b0c-1d2e3f4a5b6c"
}