Authentication
Authenticate every request with an API key (create one on the API Keys page) sent in the header.
The dashboard itself uses short-lived bearer tokens (); the public API described here uses API keys.
Two routes need no key: and . Everything that touches a document, a job or an organization needs one.
x-api-key: sk_live_9f2c… Content-Type: application/json
Translate a document
Send a document and get it back translated, with its layout intact. The call is synchronous by default: the finished document comes back inside the job on a .
A large scan can take longer than a connection should be held. caps the wait — from to milliseconds. Leave it out and our own budget applies: 55 seconds, sized down for a small document. It can lower that budget and never raise it, so a value above the ceiling is refused with a rather than capped. If the wait passes you get a with the job still running — the work is never cancelled, and has the document once it finishes. Send an as well: that is what stops a client with a short timeout from paying twice for the same document.
There are two modes, and the size of the document decides. is the default: it holds the connection and hands back the finished document. answers at once, then posts the finished document to the address you name — see Webhooks below.
Above 2 MB, or for any scan, use . A large document takes minutes, and a 95-page scan takes about a quarter of an hour. A synchronous call gives up before that and answers , so you poll for the result anyway. Nothing is refused for being large: the webhook mode saves you the polling.
decides the shape of the result and defaults to : the document as positioned blocks, each with its source text, its translation, a normalized bounding box and its style. is the plain text, is a rendered PDF, and is the source file rewritten in place. The first two are inlined in the result; the last two arrive as a .
What counts as text follows the format. A CSV header row is text, so it is translated with the table under it — read the output by column position, not by column name. JSON keys are not text: only the values change. Subtitle timings and an AWS Transcribe result's own ids come back exactly as they arrived.
| Name | Notes |
|---|---|
| source.kindrequired | One of |
| source.contentBase64optional | Base64-encoded bytes for binary kinds. Text-ish kinds (including JSON) can use instead. |
| targetLangrequired | BCP-47 code of the target language, e.g. . It must be one carries — over a hundred — or the request is a rather than a translation into something else. A region we do not list still works when its language is one we do (). A tag is a language, optionally with a script and a region, and nothing beyond that: is refused rather than translated into English. |
| outputFormatoptional | One of (the default) · · · . returns the source file rewritten in place (same format). |
| promptIdoptional | Names a saved house style from this organization. Raw instructions in the body are rejected. |
| deliveryoptional | (the default) returns the finished document in the response. answers and posts it to your endpoint instead. |
| syncTimeoutMsoptional | How long to hold the connection, in milliseconds, with . From to , which is the longest this server holds one; a larger value is refused, not capped. Refused with any other mode. |
| webhookUrloptional | Where to post the finished translation. Required with , and refused without it. Must be on port 443, and must resolve to a public address. |
| usePremiumEngineoptional | Redraws every page of a PDF, an image, and the pictures a report, a deck or a spreadsheet carries, with an image model instead of the standard engine. On a PDF the model may move a box, an arrow or a line break so a longer or shorter translation keeps the size of the text it replaces. A page with nothing to translate is not redrawn. Costs credits instead of for each one it redraws, and only for those: the rest of the document is priced exactly as it always was. It needs or (an office file: ). Anything we could not redraw is translated the standard way, charged the standard rate, and reported as in . |
| translateImagesoptional | Reads the words printed inside a picture that stands beside text of its own — a page of a PDF, a report, a deck, a spreadsheet — a diagram, a chart, a screenshot — and puts the translation back over them. when you leave it out, so send to have those pictures returned exactly as they arrived. A logo, a signature block and a QR code are never read whatever you send, and an image sent as the document itself is always read. |
| modeoptional | (the default) or : a draft of a certified translation for a sworn translator to sign. Names, numbers and dates are copied exactly, stamps, seals and signatures are described in brackets, a passage that could not be translated is marked, and your organization's localization settings do not apply. The draft comes back as unless you name . Pro plan and above; a lower plan gets a 403. |
| certificationoptional | Only with . , optional , and to end the document with the translator's statement. The place and the date stay blank for the translator to fill in. |
curl -X POST "https://api.tranly.co/v1/translate/document" \
-H "x-api-key: $TRANLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": {
"kind": "pdf",
"contentBase64": "JVBERi0xLjcKJ...",
"filename": "contract.pdf"
},
"targetLang": "pt-BR",
"outputFormat": "original"
}'HTTP/1.1 200 OK
{
"job": {
"id": "4f8a1c92-6b3e-4d77-9a05-1e2f3b4c5d6e",
"status": "succeeded",
"createdAt": "2026-07-08T12:00:00.000Z",
"completedAt": "2026-07-08T12:00:07.000Z",
"error": null,
"result": {
"outputFormat": "original",
"downloadUrl": "data:application/pdf;base64,JVBERi0...",
"filename": "contract.pt-BR.pdf"
},
"usage": {
"credits": 12,
"pages": 12,
"ocrPages": 0,
"inputChars": 31402,
"outputChars": 33915
},
"webhook": null
}
}{
"outputFormat": "json",
"document": {
"target_lang": "pt-BR",
"pages": [
{
"number": 1,
"width": 612,
"height": 792,
"blocks": [
{
"id": "p1_b0",
"type": "line",
"text": "Invoice",
"translation": "Fatura",
"bbox": { "x": 0.1, "y": 0.1, "width": 0.3, "height": 0.03 },
"style": { "font": "Helvetica", "size": 12, "bold": true },
"confidence": 0.99
}
]
}
],
"metadata": { "source_format": "pdf" }
}
}curl -X POST "https://api.tranly.co/v1/translate/document" \
-H "x-api-key: $TRANLY_API_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{
"source": { "kind": "pdf", "contentBase64": "JVBERi0xLjcKJ..." },
"targetLang": "pt-BR",
"delivery": "sync",
"syncTimeoutMs": 30000
}'Bulk translate
Send one document and get it back in several languages, in one call — is the list. The response is a batch: is keyed by the language code you asked for, and each value is a whole job — its own status, its own result, its own cost. This endpoint is API-only; the dashboard has no screen for it.
A batch always answers , and there is no field to change that: a batch multiplies the work by the number of languages, so a synchronous call could not honour its own name. Poll and read . and mean ask again; , and come back on a . is the one only a batch has — some languages landed and some did not, and each failure is written on the language it belongs to. is the total, and stays until the batch stops.
Give to be called back instead of polling. That is one delivery per language, each carrying that language's finished document, and it needs no field here — unlike the single-document route, where a URL without is refused. The signing secret, the verification rules and the retry schedule are the ones under Webhooks below.
Every language costs a whole document. 5 languages of a 12-page PDF is 60 credits, with no batch discount: the engine runs once per language and reads the document again each time. The credits for the whole batch are held before any language starts, so a batch that does not fit your allowance is refused whole rather than half-accepted.
is accepted here too, and it is the one field on this endpoint that multiplies with the list: a batch of 5 languages redraws the same pages 5 times, at credits a page each. The whole cost is held before any language starts, so a batch that does not fit your allowance is refused whole. It needs or , exactly as on the single-document route.
| Name | Notes |
|---|---|
| sourcerequired | The document, exactly as the single-document route takes it — same , same or . |
| targetLangsrequired | The target languages, as a list. Each one must be a code carries, and naming the same language twice is a rather than two charges. For a single language use on . |
| outputFormatoptional | One of (the default) · · · . returns the source file rewritten in place (same format). |
| promptIdoptional | Names a saved house style from this organization. Raw instructions in the body are rejected. |
| webhookUrloptional | Where to post each finished language. Accepted on its own here, with no field. Same rules as a single webhook: on port 443, a public address, and a signing secret already created. |
| usePremiumEngineoptional | Redraw every page of a PDF, an image, and the pictures an office file carries, in every language, with an image model. credits a page instead of , per language. Refused on an that carries no pixels. |
| translateImagesoptional | Reads the words printed inside a picture that stands beside text of its own — a page of a PDF, a report, a deck, a spreadsheet — a diagram, a chart, a screenshot — and puts the translation back over them. when you leave it out, so send to have those pictures returned exactly as they arrived. A logo, a signature block and a QR code are never read whatever you send, and an image sent as the document itself is always read. |
curl -X POST "https://api.tranly.co/v1/translate/documents" \
-H "x-api-key: $TRANLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "kind": "pdf", "contentBase64": "JVBERi0xLjcKJ..." },
"targetLangs": ["pt-BR", "es", "fr"],
"outputFormat": "original"
}'
# 202, always. Then poll the batch until its status leaves queued/processing:
curl "https://api.tranly.co/v1/translate/batches/b7e1d3a4-9c22-4f18-8e6b-2a5c7d9f0e13" \
-H "x-api-key: $TRANLY_API_KEY"HTTP/1.1 202 Accepted
{
"batch": {
"id": "b7e1d3a4-9c22-4f18-8e6b-2a5c7d9f0e13",
"createdAt": "2026-08-16T09:12:44.031Z",
"status": "processing",
"usage": null,
"jobs": {
"pt-BR": { "id": "4f8a1c92-…", "status": "succeeded", "error": null,
"result": { "outputFormat": "original",
"filename": "contract.pt-BR.pdf",
"downloadUrl": "data:application/pdf;base64,JVBERi0..." },
"usage": { "credits": 12, "pages": 12, "ocrPages": 0,
"inputChars": 31402, "outputChars": 33915 } },
"es": { "id": "9b2f5c81-…", "status": "queued",
"result": null, "usage": null, "error": null },
"fr": { "id": "c41a7e60-…", "status": "processing",
"result": null, "usage": null, "error": null }
}
}
}HTTP/1.1 200 OK
{
"batch": {
"id": "b7e1d3a4-9c22-4f18-8e6b-2a5c7d9f0e13",
"status": "partial",
"usage": { "credits": 24, "pages": 24, "ocrPages": 0,
"inputChars": 62804, "outputChars": 67830 },
"jobs": {
"pt-BR": { "status": "succeeded", "error": null,
"usage": { "credits": 12, "pages": 12, "ocrPages": 0,
"inputChars": 31402, "outputChars": 33915 } },
"es": { "status": "succeeded", "error": null,
"usage": { "credits": 12, "pages": 12, "ocrPages": 0,
"inputChars": 31402, "outputChars": 33915 } },
"fr": { "status": "failed", "usage": null,
"error": "The translation service is temporarily unavailable." }
}
}
}Webhooks
Ask for and we answer straight away, then post the finished translation to your endpoint. The event carries the document itself, in the same shape the synchronous call returns — there is nothing to fetch afterwards.
There are two event types, so branch on rather than assuming: carries the document in , and carries with the reason in . is when nothing was measured, which is most failures, and says what the job cost when the engine had already run before it failed — usually zero, and not always. That is what makes it add up to . Both are signed the same way.
Create a signing secret first, on the API keys page of your dashboard. That page is the only door: the route behind it takes a signed-in session, never an API key, because a key that could read what signs our deliveries could forge one. Without a secret a webhook translation is refused with a rather than delivered unsigned. Rotating takes effect immediately, with no overlap window.
- Compute the HMAC-SHA256 of with your secret, over the body exactly as received. Parsing and re-serializing changes the bytes, and the signature will not match.
- Compare in constant time — , , .
- Refuse a more than 5 minutes old. That is what makes a captured delivery useless to replay.
- is the same on every attempt of one delivery: use it to recognise a retry you already handled. Ignore signature pairs you do not know, so a future cannot break your receiver.
A delivery is accepted on any within 10 seconds. Anything else is retried on the schedule opposite, up to six attempts; stops us for good. We never follow redirects, so a is a failed attempt — give us the final address.
We deliver to on port 443 only, and only to a public address: , and the private ranges are refused, and the address is checked again inside the connection itself. While you develop, that means a public tunnel. Nothing of ours travels with the request except the signature. Track it on the job: carries the status, the attempt count, and why the last attempt failed.
curl -X POST "https://api.tranly.co/v1/translate/document" \
-H "x-api-key: $TRANLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "kind": "pdf", "contentBase64": "JVBERi0xLjcKJ..." },
"targetLang": "pt-BR",
"outputFormat": "original",
"delivery": "webhook",
"webhookUrl": "https://acme.com/hooks/tranly"
}'HTTP/1.1 202 Accepted
{
"job": {
"id": "4f8a1c92-6b3e-4d77-9a05-1e2f3b4c5d6e",
"status": "queued",
"result": null,
"webhook": {
"url": "https://acme.com/hooks/tranly",
"status": "pending",
"attempts": 0,
"lastAttemptAt": null,
"lastError": null,
"deliveredAt": null,
"nextAttemptAt": null
}
}
}POST https://acme.com/hooks/tranly
Tranly-Signature: t=1785499200,v1=5257a869e7...
Tranly-Event-Id: evt_5f4dcc3b5aa765d61d8327deb882cf99
Tranly-Delivery-Attempt: 1
{
"id": "evt_5f4dcc3b5aa765d61d8327deb882cf99",
"type": "translation.succeeded",
"createdAt": "2026-07-31T18:04:19.901Z",
"data": {
"job": {
"id": "4f8a1c92-6b3e-4d77-9a05-1e2f3b4c5d6e",
"status": "succeeded",
"filename": "contract.pdf",
"sourceKind": "pdf",
"targetLang": "pt-BR",
"outputFormat": "original",
"createdAt": "2026-07-31T18:03:52.010Z",
"completedAt": "2026-07-31T18:04:19.884Z",
"error": null,
"usage": { "credits": 12, "pages": 12, "ocrPages": 0,
"inputChars": 31402, "outputChars": 33915 }
},
"result": {
"outputFormat": "original",
"filename": "contract.pt-BR.pdf",
"downloadUrl": "data:application/pdf;base64,JVBERi0..."
}
}
}POST /hooks/tranly HTTP/1.1
Content-Type: application/json
Tranly-Signature: t=1785499200,v1=5257a869e7...
Tranly-Event-Id: evt_9f2c4b...
Tranly-Delivery-Attempt: 1
{
"id": "evt_9f2c4b...",
"type": "translation.succeeded",
"createdAt": "2026-07-31T18:04:19.901Z",
"data": {
"job": { "id": "4f8a1c92-6b3e-4d77-9a05-1e2f3b4c5d6e", "filename": "contract.pdf", "status": "succeeded" },
"result": {
"outputFormat": "original",
"filename": "contract.pt-BR.pdf",
"downloadUrl": "data:application/pdf;base64,JVBERi0..."
}
}
}attempt 1 immediately attempt 2 +30 s attempt 3 +2 min attempt 4 +10 min attempt 5 +1 h attempt 6 +6 h 2xx delivered, we stop 410 Gone we stop for good anything else (incl. 3xx, timeouts) counts as a failed attempt
Retrieve a job
Fetches one translation by id. You need it in two cases: a synchronous call that ran out of time answered , and the document is here once it finishes; or you asked for a webhook and want to see how the delivery went, which reports. Binary results carry a plus a suggested ; text results are inlined.
is a URI, not a link to a file. The document travels inside the response, so there is nothing to fetch and nothing that expires — decode it rather than handing it to a downloader. It also means a big result is a big response: a 95-page scan is several megabytes of JSON.
says why part of a document came back in the language it went in as. The key is absent on a clean run, so its presence is the signal. Each entry carries a and how much it covers: (a signature certificate is an audit record, so we return it verbatim), (passages we tried and did not land), , (passages delivered in neither the source's language nor the one you asked for), and (pages you asked the premium engine to redraw and it did not — translated the standard way, and charged the standard rate). Read it: the page looks finished either way.
The HTTP code says whether the job is finished; job.status says whether it worked. A translation that failed is still a 200.
curl "https://api.tranly.co/v1/translate/jobs/4f8a1c92-6b3e-4d77-9a05-1e2f3b4c5d6e" \ -H "x-api-key: $TRANLY_API_KEY"
{
"job": {
"id": "4f8a1c92-6b3e-4d77-9a05-1e2f3b4c5d6e",
"status": "succeeded",
"createdAt": "2026-07-08T12:00:00.000Z",
"completedAt": "2026-07-08T12:00:07.000Z",
"error": null,
"result": {
"outputFormat": "original",
"downloadUrl": "data:application/pdf;base64,JVBERi0...",
"filename": "contract.pt-BR.pdf"
},
"usage": {
"credits": 12,
"pages": 12,
"ocrPages": 0,
"inputChars": 31402,
"outputChars": 33915
},
"webhook": null
}
}Translate text
Synchronous plain-text translation — no job, the translation comes back in the response.
| Name | Notes |
|---|---|
| textrequired | The text to translate. |
| targetLangrequired | BCP-47 code of the target language, e.g. . It must be one carries — over a hundred — or the request is a rather than a translation into something else. A region we do not list still works when its language is one we do (). A tag is a language, optionally with a script and a region, and nothing beyond that: is refused rather than translated into English. |
| promptIdoptional | Names a saved house style from this organization. Raw instructions in the body are rejected. |
curl -X POST "https://api.tranly.co/v1/translate/text" \
-H "x-api-key: $TRANLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "text": "Hello, world!", "targetLang": "pt-BR" }'{
"translation": "Olá, mundo!",
"targetLang": "pt-BR",
"usage": {
"inputChars": 13,
"outputChars": 11,
"model": "gpt-4o-mini",
"credits": 1
}
}Redact a document
Take personal data out of a PDF or an image. The data leaves the file — the text is removed, not painted over — and the rest of the page stays the same. Two calls: answers with the areas it proposes, page by page, and removes the areas you send back. Read a job with . does both in one call, for an integration with no review.
A detection costs 1 credit a page. An apply that names a detection of the same file from the last 24 hours in costs nothing. The result is always a PDF, and we read it again before we hand it back: a job whose covered text is still readable fails instead. Pro plan and above. A lower plan gets one free test of a short document, and a 403 after it: answers with the page ceiling and whether the test is open. , and work as on .
| Name | Notes |
|---|---|
| sourcerequired | (, or ), and an optional . |
| categoriesoptional | Detect only. What to find: person_name · email · phone · postal_address · date_of_birth · id_number · bank_account · custom_text. |
| customTextsoptional | Detect only. Exact texts to find, one per entry, with the custom_text category. |
| areasoptional | Apply only. The areas to remove: each one an , a , a and a normalized to the page (0 to 1, from the top-left corner). Use for a box you drew yourself. |
| detectionJobIdoptional | Apply only. The detection this apply follows. The same file within 24 hours makes the apply free. |
# 1. Find the data. Each area is a page, a box and a category.
curl -X POST "https://api.tranly.co/v1/redact/detect" \
-H "x-api-key: $TRANLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "kind": "pdf", "contentBase64": "JVBERi0xLjcKJ..." },
"categories": ["person_name", "id_number", "email"]
}'
# 2. Remove the areas you kept. Naming the detection makes this call free.
curl -X POST "https://api.tranly.co/v1/redact/apply" \
-H "x-api-key: $TRANLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "kind": "pdf", "contentBase64": "JVBERi0xLjcKJ..." },
"detectionJobId": "9b1e...",
"areas": [{ "id": "p1_a0", "page": 1, "category": "person_name",
"bbox": { "x": 0.12, "y": 0.2, "width": 0.3, "height": 0.02 } }]
}'{
"job": {
"id": "9b1e…",
"kind": "redaction_detect",
"status": "succeeded",
"result": {
"outputFormat": "json",
"redaction": {
"pages": [{
"number": 1, "width": 595, "height": 842,
"areas": [{
"id": "p1_a0", "page": 1, "category": "person_name",
"bbox": { "x": 0.12, "y": 0.2, "width": 0.3, "height": 0.02 }
}]
}]
}
},
"usage": { "credits": 1, "pages": 1 }
}
}curl -X POST "https://api.tranly.co/v1/translate/document" \
-H "x-api-key: $TRANLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "kind": "pdf", "contentBase64": "JVBERi0xLjcKJ..." },
"targetLang": "en",
"mode": "certified",
"certification": {
"translatorName": "Ana Lima",
"registration": "JUCESP 1234",
"includeStatement": true
}
}'AWS Transcribe
Send an AWS Transcribe result and get the same result file back, translated. Upload it as any other document — a that carries or is recognised on its own — or name it explicitly with . With the response is a Transcribe result your existing pipeline can read without a change.
The translation unit is the speech segment, never the word. , , and every survive byte-exact, segment / are the recognition's own and never move, and , and are rebuilt to keep agreeing with the translated segments.
One limit, stated plainly: word-level times are estimates. A translated sentence has a different number of words, so no word carries the old word's timestamp — we distribute the segment's real span across the new words by length. Segment boundaries are exact; if you build word-highlighted captions off , that is the field to know about. is likewise inherited from the segment, because the recognition was confident about speech we did not re-recognise.
Saved prompts
A prompt is your organization's house style — voice, register and terminology — applied on top of the engine's translation rules. Write and edit them on the page of your dashboard, then name one from a translation with . Writing one takes a signed-in session; naming one, and reading one back, take any credential — so an integration only ever sends the id.
Prompts are named, never inlined: raw in a translation body are rejected, so the text that reached the model is always a record you can look up. An id belonging to another organization is a .
The answer names the style that ran: comes back on and on every read of a job, and is when none was named. Resolve it with — that read, and the list, take your API key as well as a session.
curl -X POST "https://api.tranly.co/v1/translate/document" \
-H "x-api-key: $TRANLY_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"source": { "kind": "pdf", "contentBase64": "JVBERi0xLjcKJ..." },
"targetLang": "pt-BR",
"promptId": "1f3c9a2e-..."
}'Credits & plan limits
Everything is metered in credits. One credit is one page of a document, or 2,500 characters of plain text rounded up, with a minimum of one credit per request. A text translation reports what it cost in ; a document reports it in once the job finishes, with the page count and the OCR page count beside it.
A scanned page costs the same as a native one — there is no OCR surcharge, so a photographed invoice and a text PDF of the same length bill identically. Which model translates is ours to choose, so a page costs the same whichever one we happen to be running. The one exception is the premium image engine: redraws a page — every page of a PDF, an image, or a picture inside an office file — with an image model at credits instead of , and only the pages it really drew are multiplied — counts them.
Running out returns with code . A file larger than your plan allows returns , and the message names both sizes: the ceiling is 12 MB on every plan, Free included, and base64 adds about a third to a file. answers your allowance and what is left of it — with a key or a session.
credits = pages > 0 ? pages : max(1, ceil(chars / 2500))
pages the page count decides wherever the source has pages,
however much text is on them
chars prices only a format that reports no pages at all —
docx, html, markdown, csv, subtitles, plain text
OCR a scanned page costs the same as a native oneErrors
Non-2xx responses share one envelope, a route that does not exist included. Branch on : it is stable and upper-case, while the message is written for a person and may be reworded.
Every response carries the rate limit in force for the route you called, so you can pace yourself without waiting for a : , , and in seconds. A adds . The default is 300 requests per 60 seconds, per address, counted across every route including the public ones; sign-in is deliberately tighter. Read the headers rather than the number — they are the ones that answer for the route you are on.
Each API key also has a ceiling of requests per minute, set by your plan. It counts every request sent with , in a window of 60 seconds per key. Two routes are not counted, so you can poll a result as often as you need: and . A request past the ceiling returns with code and in seconds. A dashboard session is not counted.
applies to and , which leave a job or a batch to replay. refuses the header with a : its answer is the translation itself, so there is nothing to replay, and accepting it would suggest a protection you do not have.
| Code | HTTP | What to do |
|---|---|---|
| UNAUTHORIZED | 401 | The key was revoked or mistyped. |
| BAD_REQUEST | 400 | The body, or the file inside it, was refused. The message names the field, or what about the file. |
| INVALID_TARGET_LANG | 400 | The tag names no language we translate into. Use a code from GET /v1/meta/languages. |
| REDACTION_TRIAL_TOO_LONG | 400 | The document is longer than the free test of redaction takes. The message states the page ceiling. Send a shorter document; the test stays open. |
| NOT_FOUND | 404 | The job or style id does not belong to this organization. |
| CONFLICT | 409 | An you already used, with a different body. Use a new key for a new document. |
| PAYLOAD_TOO_LARGE | 413 | The whole request body is over 30 MB. The per-plan file ceiling is lower, and returns 400. |
| PLAN_FEATURE_REQUIRED | 403 | Your plan does not include this feature (redaction, or the certified mode). The message names the cheapest plan that does. |
| QUOTA_EXCEEDED | 429 | Move up a tier, or wait for the monthly reset. |
| SYNC_LIMIT | 429 | Too many synchronous translations at once for this plan. Retry, or send the document with , which is never capped. |
| API_KEY_RATE_LIMITED | 429 | This API key passed the requests per minute of your plan. Wait the seconds in retry-after, or move up a tier. |
| RATE_LIMITED | 429 | Too many requests from one address. Slow down; this one clears on its own. |
| UPSTREAM_ERROR | 502 | The engine failed on our side. Nothing was charged — retry. |
{
"error": {
"message": "Monthly credit limit reached for the starter plan.",
"code": "QUOTA_EXCEEDED"
}
}