TBTS — Trinity Beast Translation Service The Trinity Beast – Translation Service

Technical Documents Translated Perfectly — Code, Diagrams & All

"We use Amazon Bedrock to do what generic translation services can't."

Status: Production Version: v3.4 October 10, 2026

1. Service Overview

1.1 The Problem We Solved

Technical documentation is hard to translate. Generic translation services — including AWS Translate — don't understand the boundary between human language and machine language. They translate code blocks, corrupt variable names, localize version numbers, and hallucinate brand names.

We learned this the hard way. AWS Translate cost us over $800 in worthless translations before we caught it corrupting technical content across 11 languages. Silent failures. No warnings. No way to know without reading every translated document.

Real failure modes we caught:

  • function getName() → función obtenerNombre() (code no longer executes)
  • PostgreSQL 17.7 → PostgreSQL (version number dropped)
  • Brand names hallucinated or substituted with unrelated terms
  • 32 GB → 32 Go (unit localized in French)
  • AutoOps → آٹو آپس (brand name transliterated to Urdu phonetic)

So we built our own translation engine. It runs on Amazon Bedrock with sentinel preprocessing and multi-layer validation. Before an AI agent ever sees your document, the engine lifts out everything that must not change — code, diagrams, version numbers, brand names, technical identifiers — and puts it back untouched afterwards. The agent cannot corrupt what it never touches, and every translated section is checked before it is delivered to you.

1.2 Choose Your Agent

Six AI agents. Same protection. A price you see before you pay.

You pick the agent that suits your document. Five of the six cost you the same flat $3.00 per document-language pair whatever the size, and Claude Opus 4.8 starts at $3.00 and follows its real cost on larger documents, so the choice is about the kind of translation you want, never a surprise on the bill. The engine is agent-agnostic by design: any model reachable through Amazon Bedrock can serve as a translation agent, and these six were chosen for quality and for their distinct strengths.

Your default is Auto — best agent per language. Leave the agent out of your quote and Auto picks for you: Qwen 3 235B for every language Qwen supports, and Claude Sonnet 4.6 only for the five languages outside Qwen's published list (Amharic, Hausa, Mongolian, Pashto and Somali), at the same flat price. Choose an agent yourself and that choice overrides Auto for every language, in every attempt, including retries.

AgentAgent IDSpeedStrengthsYour Price
Qwen 3 235B AUTO (default) qwen3-235b Fast Excellent with Chinese, Japanese and Korean, and strong across every script we support. The everyday choice. $3.00 / pair
DeepSeek V3 deepseek-v3 Fast Strong with European languages and dense technical content such as API references. $3.00 / pair
Mistral Large mistral-large Fast Native-quality Romance languages: French, Spanish, Portuguese and Italian, idioms and formal register included. $3.00 / pair
Claude Haiku 5.5 claude-haiku-5.5 Fast A versatile all-rounder with excellent instruction-following, well suited to high-volume jobs. $3.00 / pair
Claude Sonnet 4.6 claude-sonnet-4.6 Moderate Deep reasoning and nuanced output. Handles complex scripts such as Arabic, Hindi and Japanese, and demanding technical documents. $3.00 / pair
Claude Opus 4.8 claude-opus-4.8 Slow Maximum fidelity for critical documents, nuanced content, and languages with complex grammar. From $3.00 / pair

Integrations written against earlier agent IDs keep working: claude-haiku-3.5 and claude-opus-4 are still accepted and converted quietly to claude-haiku-5.5 and claude-opus-4.8.

All six agents share the same protection: sentinel preprocessing removes code blocks, brand terms, version numbers and technical identifiers before the agent sees your document, and the same validators check every section no matter which agent produced it. What changes between agents is the depth of linguistic reasoning and the speed, never the safety of your content.

1.3 The Pitch

AI translation that understands the difference between human language and machine language.

Your code blocks stay code. Your version numbers stay exact. Your brand names stay yours. Validated before delivery — not after you find the bug in production.

See It In Action

Want to see the quality? You're looking at it.

This website — cpmp-site.org — is published in 12 languages, and its technical documentation library was translated by the same engine we're offering you: 473 translated documents (43 documents in each of 11 languages) in production today.

Switch languages in the header. Read the Document Library in Japanese. Check the API Reference in German. Browse the Infrastructure Specification in Arabic, right to left. Code blocks intact. Version numbers exact. Brand names preserved.

This isn't a demo. This is production proof at scale.

1.4 What We Translate

The service translates HTML documents that you publish at a public URL. HTML is where technical documentation lives, and it is the format the engine understands most deeply: it knows a code block from a paragraph, a diagram from a table, and a translate="no" zone from prose.

ContentHow It Is Handled
Prose, headings, lists, table textTranslated
Code blocks and inline codeProtected — never shown to the agent, restored exactly
Mermaid diagramsProtected structure; only human-readable labels are translated
URLs, email addresses, file paths, version numbersProtected and verified after translation
Anything inside translate="no"Left exactly as written
Your brand terms (up to 150)Never translated or transliterated (see 5.3)

Working in Markdown, PDF or another format? Convert it to HTML first and the engine takes it from there.

1.5 Supported Languages

You can translate into any of 127 languages: every language our agents on Amazon Bedrock can translate well, English included. Any of them can also be the source, and the engine translates directly from one to the other, so an Italian manual can go straight to English, or a Japanese guide straight to Spanish, with no pivot through a third language.

The list below is generated from the same catalog the service checks on every quote, and GET /translate/languages (3.11) returns it live. A language that is not on it is refused before anything is priced or charged, so you are never billed for a language we cannot translate.

CodeLanguageCodeLanguageCodeLanguage
afAfrikaanssqAlbanianamAmharic
arArabicarzArabic (Egyptian)apcArabic (Levantine)
acmArabic (Mesopotamian)aryArabic (Moroccan)arsArabic (Najdi)
acqArabic (Ta'izzi-Adeni)aebArabic (Tunisian)hyArmenian
asAssameseastAsturianawaAwadhi
azAzerbaijanibanBalinesebjnBanjar
baBashkireuBasquebeBelarusian
bnBengalibhoBhojpuribsBosnian
bgBulgarianmyBurmeseyueCantonese
caCatalancebCebuanohneChhattisgarhi
zhChinese (Simplified)zh-TWChinese (Traditional)hrCroatian
csCzechdaDanishfa-AFDari
nlDutchenEnglishetEstonian
foFaroesefiFinnishfrFrench
fr-CAFrench (Canada)furFriulianglGalician
kaGeorgiandeGermanelGreek
guGujaratihtHaitian CreolehaHausa
heHebrewhiHindihuHungarian
isIcelandiciloIlocano (Iloko)idIndonesian
gaIrishitItalianjaJapanese
jvJavanesekeaKabuverdianuknKannada
kkKazakhkmKhmerkoKorean
loLaolvLatvianlijLigurian
liLimburgishltLithuanianlmoLombard
lbLuxembourgishmkMacedonianmagMagahi
maiMaithilimsMalaymlMalayalam
mtMaltesemrMarathiminMinangkabau
mnMongolianneNepalinoNorwegian (Bokmål)
nnNorwegian (Nynorsk)ocOccitanorOdia (Oriya)
pagPangasinanpapPapiamentopsPashto
faPersianplPolishptPortuguese (Brazil)
pt-PTPortuguese (Portugal)paPunjabiroRomanian
ruRussianscSardiniansrSerbian
scnSicilianszlSilesiansdSindhi
siSinhalaskSlovakslSlovenian
soSomaliesSpanishes-MXSpanish (Mexico)
suSundaneseswSwahilisvSwedish
tlTagalog (Filipino)tgTajiktaTamil
ttTatarteTeluguthThai
tpiTok PisintrTurkishukUkrainian
urUrduuzUzbekvecVenetian
viVietnamesewarWaraycyWelsh
yiYiddish (Eastern)

2. Pricing

2.1 Pricing Model

One flat rate

$3.00
per document-language pair, on five of our six agents

One document translated into one language is one pair. Ten documents into eleven languages is 110 pairs, which is 330.00. Because the rate is flat, you can work out the price yourself before you ever ask for a quote, and that predictability is the point.

2.2 What the AI Costs Us

For full transparency, here is the underlying Amazon Bedrock cost behind each agent for a typical 50 KB document in one language. This is our cost, not your price, and the figures below load live from GET /translate/models. The difference between these figures and the flat $3.00 is what keeps the engine running, the validators thorough, and the mission funded.

AgentUnderlying AI Cost (50 KB document, one language)
Qwen 3 235B~$0.02
DeepSeek V3~$0.04
Mistral Large~$0.04
Claude Haiku 5.5~$0.01
Claude Sonnet 4.6~$0.34
Claude Opus 4.8~$0.57

2.3 Volume Pricing

Translating more than 1,000 pairs a month? Contact us and we will work out a rate with you.

2.4 How We Compare

A typical technical document translated into 11 languages with us is 11 pairs, which is 33.00, delivered in minutes.

OptionWhat You Get
Trinity Beast TranslationCode, diagrams, versions and brand names protected and validated before delivery. A fixed price you see before you pay. Minutes, not weeks.
Generic machine translation (AWS Translate, Google Cloud Translation, DeepL)Cheaper per word and fast, but built for prose. Code blocks, identifiers and version numbers get translated or dropped, and nothing tells you it happened.
Human translationHigh-quality prose, but priced per word, measured in weeks, and still needs technical review to catch damaged code.

The value proposition: you are not paying for translation alone. You are paying for translation that does not break your documentation, with a price you know in advance and validation you never have to do yourself.

2.5 Service Limits

Each limit exists to keep quality consistent and every job on time. Need more than one quote allows? Create another quote, there is no limit on how many you can make.

LimitValueWhat It Means
Document size300 KBMaximum HTML size per document (307,200 bytes). We check it when you ask for the quote and again when you accept, and a larger document is refused with tbts_doc_too_large, never cut short, so you are never charged for half a page. Split larger documents into smaller pages.
Languages per quote12Up to 12 target languages in one quote. The engine runs one container per language, so all of them translate in parallel.
Documents per quote42Up to 42 documents in one quote.
Quotes per batch9Accept up to 9 quotes together: one charge, one receipt, one completion email.
Quote validity72 hoursA quote can be accepted for 72 hours after it is created. After that, request a fresh one. If a document's content changed in the meantime, see 3.4.
Retry for failed pairs1 free retryOne retry per quote covers every retryable pair that didn't deliver, at no charge. A pair skipped because the document was already in that language is never retried; it is refunded instead (3.2).
Failed-pair refund window72 hoursA pair that didn't deliver is refunded automatically 72 hours after the job finishes, unless you retry it first. You can also ask for the refund straight away (3.9).
Price per pair$3.00On five of the six agents, regardless of size. Claude Opus 4.8 is priced at its actual cost with a $3.00 floor, so a larger document can cost more per pair: on today's rates that starts at about 200 KB, and a 300 KB document comes to about $4.47 per pair. Your quote always shows the exact amount.
DeliveryExpressEvery job runs in real time from the moment you accept.
Parallel workers12Every language in a job gets its own worker, up to 12 at once, so a 12-language quote never leaves a language waiting. Accepted jobs are never turned away: they wait in the queue and start as workers free up.
Daily spend cap$600Engine-wide cost protection on AI spend. It is checked before your card is charged: if it, or the service's safety switch, is ever tripped, accepting answers 503 tbts_service_paused with nothing charged or queued, and you can try again later.
File availability93 daysTranslated files are kept for 93 days after the job. Download links last 7 days, and you can request fresh ones any time in that window.

3. Using the API

The flow has three steps and you are in control at every one: quote (see the exact price), accept (pay and start), download (collect your files). You can do all of it from the Account Dashboard without writing any code, or automate it with the endpoints below.

3.1 Authentication

Every request uses your translation API key in the X-API-Key header:

curl -H "X-API-Key: your-api-key-here" \
     https://api.cpmp-site.org/translate/quotes

Your key lives in your Trinity Beast Account Dashboard. Log in at cpmp-site.org/dashboard with the email you used to sign up. Translation-only customers are first-class citizens: no Price API subscription is required, and if your email also has other products you will see everything in one view.

Response Format — Unified Messaging Envelope (UME)

Every response, success or error, uses the Unified Messaging Envelope. All 12 fields are always present.

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/translate/quote] [200]",
  "status_code": 200,
  "endpoint": "/translate/quote",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ak_demo123",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "timestamp": "2026-10-09T18:30:00Z",
  "data": { ... },
  "error": ""
}

The language field reflects your api_lang preference, and error messages arrive in that language. Envelope metadata stays English always, and every response tells you which container handled it, in which region, and exactly when.

3.2 Request a Quote

POST /translate/quote

Send the documents and the languages you want. The engine fetches each document, analyzes it, works out what language it is written in, and returns a fixed price. Nothing is charged at this step.

{
  "docs": ["https://example.com/docs/guide.html", "https://example.com/docs/faq.html"],
  "langs": ["en", "es", "zh-TW"],
  "source_lang": "it",
  "model": "qwen3-235b"
}
FieldTypeRequiredDescription
docsarrayYesPublic URLs of the HTML documents to translate (up to 42, each up to 300 KB). A single doc_url string is also accepted. Each document's name is the last part of its URL path, and two documents in one quote can't share a name.
langsarrayYesTarget language codes from 1.5, up to 12. English is a target like any other. Duplicates and alternate codes are tidied up for you.
source_langstringNoThe language your documents are written in, if you know it. Applies to every document without its own entry in source_langs. Use "auto", or leave it out, to let us work it out.
source_langsobjectNoPer-document source language: {"<doc url>": "uk"}. Beats source_lang for that document.
modelstringNoAgent ID from 1.2. Leave it out and the engine picks per language (1.2).
supersedes_quote_idstringNoThe ID of one of your pending quotes that this new quote replaces, for example after you add a source language. The old quote is marked superseded and the response's superseded_quote_id names it. Any other ID is ignored and the new quote still stands.

Knowing Your Source Language

Two things depend on knowing what language a document is written in: the agent is told to translate from that language, and we can stop you paying to translate a document into the language it is already in. So the engine takes the source from the most reliable place available:

  1. What you tell us (source_lang or source_langs) always comes first.
  2. What the page declares in <html lang="..."> comes next. If what we can tell for certain from the text (step 3) contradicts it, for example a Japanese page that declares English, the document is marked auto rather than trusted.
  3. What we can tell for certain from the text itself: English, Japanese, Korean and Thai have signals no neighbouring language shares.
  4. Otherwise we say we're unsure, rather than guess. Closely related languages that share a script, such as Ukrainian and Russian or Persian and Arabic, look too alike to tell apart reliably, and a wrong guess would put the wrong language in the prompt.

An unsure source never blocks your order. The quote still succeeds, marks that document source_lang_confirm_needed: true, and includes a hint in your language. Add source_langs and request the quote again if you know the answer; otherwise the agent translates from the document's original language as written. In the Account Dashboard you're simply asked "What language is this document written in?", with an "I'm not sure" option that always lets you continue.

A document can't be translated into its own language: an Italian page with target it is refused with a clear message, before anything is charged. Related variants are real conversions and always allowed, such as Spanish to Mexican Spanish, Simplified to Traditional Chinese, or Modern Standard Arabic to Egyptian.

There is one more safety net behind the quote. If we couldn't tell a document's language (auto) and the worker later recognizes for certain that it is already in one of your targets, it does not translate that pair. The pair is refunded automatically at its own price, so you never pay to receive your own page back.

Response (200 OK)

"data": {
  "quote_id": "qt-a1b2c3d4-e5f",
  "docs": [
    {
      "doc_url": "https://example.com/docs/guide.html",
      "doc_name": "guide.html",
      "analysis": { "size_bytes": 63488, "estimated_chunks": 4, "difficulty": "moderate", "code_blocks": 12, "diagrams": 2 },
      "total_price": 9.00
    }
  ],
  "sources": [
    { "doc_url": "https://example.com/docs/guide.html", "doc_name": "guide.html", "source_lang": "it", "source_provenance": "stated", "source_lang_confirm_needed": false, "source_hint": "" }
  ],
  "doc_count": 1,
  "languages": ["en", "es", "zh-TW"],
  "lang_count": 3,
  "total_pairs": 3,
  "model": "qwen3-235b",
  "auto_routed": false,
  "model_by_lang": {},
  "delivery": { "mode": "express", "label": "Express — real-time translation", "estimated_time": "~6 minutes", "qualified_reason": "3 translated documents (1 docs × 3 languages)" },
  "pricing": { "price_per_pair": 3.00, "total_pairs": 3, "total_price": 9.00, "currency": "USD" },
  "expires_at": "2026-10-13T18:30:00Z",
  "accept_url": "/translate/accept/qt-a1b2c3d4-e5f",
  "superseded_quote_id": ""
}

The analysis block shows what the engine found in each document: its size, how many sections it will translate, how many code blocks and diagrams it will protect. sources shows the source language for every document and where it came from (stated, declared, detected or auto). You see exactly what you are buying before you buy it.

3.3 Look Up Your Quotes

GET /translate/quote/{quote_id} returns one quote: its state (pending, accepted, expired, superseded when a newer quote replaced it, refunded, or partially_refunded when part of the amount came back to you after a cancel or a failed pair), every document, the pair count, each document's source language, the price, the expiry, and the job_id once it has been accepted.

GET /translate/quotes returns your 30 most recent quotes, newest first, each with quote_id, doc_name, lang_count, total_price, state, expires_at, job_id and model.

3.4 Accept a Quote

POST /translate/accept/{quote_id}

Accepting charges the card on file for the quoted total and starts translation immediately. No request body is needed.

"data": {
  "status": "accepted",
  "quote_id": "qt-a1b2c3d4-e5f",
  "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980",
  "model": "qwen3-235b",
  "payment_id": "pi_3abc123def456",
  "amount_usd": 9.00,
  "status_url": "/translate/status/019e4b8a37bc-1f2e3d4c5b6a7980",
  "notice": "",
  "notice_key": "",
  "updated_docs": [],
  "key": "",
  "message": "",
  "refunded": false,
  "refund_amount": 0
}

Keep the job_id: status_url follows it live (3.6), and it is what you use to download the result. Every field is always present, so your code can read the same shape whatever happens.

A charge and a start are two separate events, and the response tells you honestly which ones happened:

We Check Your Documents Again Before Charging

A quote can be accepted for up to 72 hours, and a web page can change in that time. So before your card is touched, the engine fetches every document again and compares its readable content with what was priced. Invisible markup such as scripts, styles, tracking tags and timestamps is ignored, so a page that reloads slightly differently every time doesn't count as changed.

Two accepts of the same quote at the same moment, from a double-click or a client retry, can never charge you twice. The first accept claims the quote, and the second answers at once with 409 tbts_quote_accept_in_progress ("Quote [quote_id] is already being accepted. Please wait a minute, then check it before trying again."), with nothing staged or charged. If a paid job ever fails to start, you're refunded in full automatically, and GET /translate/status works with the quote ID so you can follow it.

3.5 Accept a Batch

POST /translate/accept-batch with {"quote_ids": [ ... ]} accepts up to 9 quotes in one step: one charge, one receipt, and one completion email when the whole batch is done. Every quote is checked, and every document re-fetched, before your card is touched (see 5.5). If any quote needs a fresh price, the whole batch is held with nothing charged, and the response lists each replacement quote so you can resubmit in one go. Each quote becomes its own job inside one Task (see 5.6). The response's task_status_url (/translate/task/{task_id}) follows the whole batch, and each job carries its own status_url (3.6).

3.6 Track Progress

Follow a job from the Account Dashboard, where every document-language pair has its own chip that moves from queued to translating to delivered, or from your own code:

GET /translate/status/{job_id} (your translation key; a quote ID also works)

"data": {
  "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980", "quote_id": "qt-a1b2c3d4-e5f", "parent_job_id": "019e4b8a37bc-1f2e3d4c5b6a7980",
  "state": "partial", "job_state": "partial",
  "model": "qwen3-235b", "model_by_lang": {}, "model_display": "Qwen 3 235B",
  "submitted_at": "2026-10-10T18:31:02Z", "started_at": "2026-10-10T18:31:09Z", "completed_at": "2026-10-10T18:40:00Z",
  "docs": ["guide.html"], "langs": ["en", "zh-TW"], "total_pairs": 2,
  "counts": { "queued": 0, "running": 0, "succeeded": 1, "failed": 1, "cancelled": 0 },
  "pairs": [
    { "doc": "guide.html", "lang": "en", "state": "succeeded", "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980", "attempts": 1 },
    { "doc": "guide.html", "lang": "zh-TW", "state": "failed", "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980", "attempts": 1 }
  ],
  "eta": null,
  "retry": { "allowed": true, "state": "open", "retry_job_id": "", "window_ends_at": "2026-10-13T18:40:00Z" },
  "cancel_refund": { "status": "", "amount_due": 0, "amount_refunded": 0 },
  "failed_pair_refund": { "status": "window_open", "amount_due": 3.00, "amount_refunded": 0, "pairs": [{ "doc": "guide.html", "lang": "zh-TW" }], "window_ends_at": "2026-10-13T18:40:00Z", "refunded_at": "" },
  "refund_status": "window_open", "refund_due": 3.00, "total_refunded": 0,
  "downloads_ready": true, "download_url": "/translate/download/019e4b8a37bc-1f2e3d4c5b6a7980",
  "message_key": "tbts_status_partial_retry_open",
  "message": "1 of 2 translations couldn't be completed. You can retry them at no charge until 2026-10-13 18:40 UTC. If you don't, we'll refund 3.00 automatically."
}

Every field is always present. state is one of queued, running, succeeded, partial, failed or cancelled; eta is filled while the job runs; message explains where things stand in your api_lang. You only ever see your own jobs, from any of your keys, and anything else answers 404.

GET /translate/task/{task_id} does the same for a whole batch: overall state, totals across every job, and each job's full status.

When a job finishes you also receive one email with your download links (see 5.2).

3.7 Download Your Translations

GET /translate/download/{job_id}

Once the job has finished, this returns a download link for every translated file:

"data": {
  "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980",
  "quote_id": "qt-a1b2c3d4-e5f",
  "state": "succeeded",
  "doc_name": "api-reference.html",
  "doc_names": ["api-reference.html", "faq.html"],
  "doc_count": 2,
  "expires_in": "7 days",
  "files_available": "93 days from job completion — request a fresh link anytime within that window",
  "downloads": [
    { "doc": "api-reference.html", "language": "es", "url": "https://trinity-beast-translations.s3...", "key": "jobs/qt-a1b2c3d4-e5f/output/es/api-reference.html", "filename": "es-api-reference.html" },
    { "doc": "api-reference.html", "language": "ja", "url": "https://trinity-beast-translations.s3...", "key": "jobs/qt-a1b2c3d4-e5f/output/ja/api-reference.html", "filename": "ja-api-reference.html" },
    { "doc": "faq.html", "language": "es", "url": "https://trinity-beast-translations.s3...", "key": "jobs/qt-a1b2c3d4-e5f/output/es/faq.html", "filename": "es-faq.html" },
    { "doc": "faq.html", "language": "ja", "url": "https://trinity-beast-translations.s3...", "key": "jobs/qt-a1b2c3d4-e5f/output/ja/faq.html", "filename": "ja-faq.html" }
  ]
}

Every document in the quote gets its links, and so does every language that delivered, even when another failed or the job was cancelled partway. Each file saves as {lang}-{doc}, such as es-faq.html, so a folder of downloads sorts itself by language. Each link is private to you and lasts 7 days. Your files are kept for 93 days, so you can ask for fresh links whenever you need them. A job that is still running returns 409 with its current state, and the same links are always one click away in the dashboard.

3.8 Cancel a Translation

POST /translate/cancel/{quote_id}

You stay in control after you pay. Cancel any time before the job finishes, from the API or with the Cancel Translation button in your Account Dashboard, and you pay only for what you received. Every document-language pair already delivered is yours to keep. Every pair that was not delivered is refunded at its full per-pair price.

curl -X POST -H "X-API-Key: your-api-key-here" \
     https://api.cpmp-site.org/translate/cancel/qt-a1b2c3d4-e5f

Response (200 OK)

"data": {
  "quote_id": "qt-a1b2c3d4-e5f",
  "job_id": "019e4b8a37bc-1f2e3d4c5b6a7980",
  "state": "cancelled",
  "refund_status": "calculating",
  "message": "Translation cancelled. Pages already delivered are yours to keep. The refund for the rest is being calculated."
}

Why a Cancel Takes a Moment

The engine never cuts a language off mid-sentence. Each language finishes the section it is working on and stops at that boundary, so every page you receive is complete and every page you do not receive is refunded. You will never be charged for, or handed, half a translation. On a large document one section can take up to about three minutes, and the dashboard tells you it is finishing rather than leaving you to wonder.

When the last language stops, the refund is counted from what was actually delivered and moves through calculating → pending_approval → approved. A person reviews every refund before it is issued, and you get one email, in your language, showing what was delivered, what was stopped, and the amount coming back to you. If every pair had already been delivered, the state is none: there is nothing to refund because all of it is yours.

Cancelling a Free Retry

If your free retry (3.9) is the job that is running, cancelling stops only the retry. You get 200 with tbts_cancel_retry_accepted ("The retry was cancelled. Anything it didn't finish will be refunded automatically."), refund_status stays empty, and there is no approval step: the retry was free, so whatever neither the job nor the retry delivered is simply refunded automatically, the same way as in 3.9.

Fair in both directions: refund = total paid − (delivered pairs × per-pair price), counted in whole cents with any rounding going to you. If a language was partway through a section when you cancelled, we absorb the AI cost of that partial work. A cancelled job stays in your Translation History with its refunded amount, so your records always tell the whole story.

Returns 409 Conflict if the translation has already finished or was already cancelled, and 404 if the quote is not on your account. The full contract, including every error, is in the API Reference §15.7.

3.9 When a Pair Fails: Retry or Refund

Every section is checked before it ships, and a pair that can't pass is reported as failed instead of being delivered damaged. When that happens you never pay for it:

  1. Retry it free. POST /translate/retry/{quote_id}, or the Retry button in the dashboard, runs just the failed pairs again, on the agent you chose, at no charge. One retry covers every retryable failed pair in the quote. A pair skipped because the document was already in that language (3.2) is never retried, only refunded.
  2. Or ask for your money back now. POST /translate/refund-failed/{quote_id}, or "Refund instead" in the dashboard, refunds the failed pairs straight away. Until automatic refunds for incomplete translations are switched on, this one endpoint answers 404 tbts_refund_failed_unavailable; the free retry is always available.
  3. Or do nothing. 72 hours after the job finishes, any pair still undelivered is refunded automatically. If your free retry also fails, the refund happens as soon as it finishes.

Each failed pair is refunded at exactly the price you paid for it, counted in whole cents with any rounding going to you. A pair the retry delivers is yours and isn't refunded, and once a pair is refunded its retry is closed, so nothing is ever both delivered and refunded, or refunded twice. You get one email in your language when the refund is issued, and failed_pair_refund in the status (3.6) shows each step. Unlike a cancel (3.8), these refunds need no approval: the pair didn't deliver, so the money comes back.

3.10 List Agents

GET /translate/models (public, no key needed) lists the six agents with their IDs, names, speed and the underlying AI cost from 2.2. Use an id as the model in your quote; the response's default field names the agent used when you leave it out.

"data": {
  "models": [
    {
      "id": "qwen3-235b",
      "name": "Qwen 3 235B",
      "tier": "Fast & Capable",
      "input_rate_per_1m": 0.22,
      "output_rate_per_1m": 0.88,
      "estimated_cost_per_pair": 0.019294,
      "speed": "fast",
      "quality": "good",
      "description": "Fast and capable. Excellent with Chinese, Japanese, and Korean. The default choice."
    }
  ],
  "default": "qwen3-235b",
  "pricing_note": "Rates are per 1M tokens. estimated_cost_per_pair is for a typical 50KB document (one language). Actual cost depends on document size and content density."
}

The rates and estimated_cost_per_pair are our AI cost, shown for transparency. Your price is the flat $3.00 per pair in 2.1.

3.11 List Languages

GET /translate/languages (public, no key needed) returns the live catalog behind 1.5: every language with its code, English and native name, script, and whether it is right to left, plus the current limits and the alternate codes we accept. count is the number of languages you can order today, so your integration never has to hard-code the list.

"data": {
  "count": 127,
  "languages": [
    { "code": "af", "english_name": "Afrikaans", "native_name": "Afrikaans", "script": "Latn", "rtl": false, "word_spaced": true, "base_lang": "", "status": "supported" }
  ],
  "limits": { "max_langs_per_quote": 12, "max_docs_per_quote": 42, "max_doc_kb": 300, "max_quotes_per_batch": 9 },
  "aliases": { "nb": "no", "zh-Hant": "zh-TW", "ar-EG": "ar" },
  "source": "aurora"
}

4. Architecture

4.1 Request Flow

The service runs on the same engine that translates our own documentation library every day, so the path your document takes has been exercised hundreds of times before it reaches you. The journey has two halves, and each has its own diagram. Diagram 4.1 is everything that happens before and at the moment you pay: the quote, the source language, and the second look at every document before your card is touched. Diagram 4.2 is everything after: the translation itself, live status, the free retry and the automatic refunds that make sure you only pay for what you receive.

Diagram 4.1 — Translation Request Flow: Quote and Accept
sequenceDiagram
    participant C as Customer
    participant L as LPO Server (Go)
    participant S3 as S3 (Your Files)
    participant St as Stripe
    participant Q as SQS Queue

    rect rgba(96, 165, 250, 0.16)
    C->>L: POST /translate/quote (API key)
    L->>L: Check limits and the 127-language catalog
    L->>L: Fetch each document (up to 300 KB) and fingerprint it
    L->>L: Source language: yours, the page lang, a sure detection, or auto
    L-->>C: Quote (fixed price, valid 72h) with every source language
    end

    rect rgba(251, 191, 36, 0.14)
    C->>L: POST /translate/accept/{quote_id}
    L->>L: Claim the quote (a second accept gets 409)
    L->>L: Re-check limits and service guards (503, no charge)
    L->>L: Fetch every document again and compare
    end

    alt Price or source result changed
        L-->>C: 409 with a new quote attached, nothing charged
    else Page now breaks a rule (over 300 KB, or target is source)
        L-->>C: 400, no new quote, nothing charged
    else Same content, or same price and rules
        rect rgba(52, 211, 153, 0.14)
        L->>S3: Store the checked source documents
        L->>St: Charge the card on file
        St-->>L: Payment succeeded
        L->>Q: One message per language
        end
        alt Job started
            L-->>C: 200 with status_url (and a notice if content changed)
        else Not confirmed yet
            L-->>C: 202 pending, full refund if it never starts
        else Job could not start
            L->>St: Full refund, automatic
            L-->>C: 502 refunded
        end
    end
        
Diagram 4.2 — Translation Request Flow: Delivery, Retry and Refund
sequenceDiagram
    participant C as Customer
    participant L as LPO Server (Go)
    participant Q as SQS Queue
    participant W as BeastTranslate Worker
    participant S3 as S3 (Your Files)
    participant St as Stripe

    rect rgba(167, 139, 250, 0.16)
    Q->>W: Each worker takes one language
    W->>W: Protect, translate, validate (every language in parallel)
    W->>S3: Write translated files
    W->>L: Job complete
    L-->>C: Completion email with download links
    end

    rect rgba(96, 165, 250, 0.16)
    loop While you want to know
        C->>L: GET /translate/status/{job_id} or /translate/task/{task_id}
        L-->>C: Every pair, the ETA and any refund
    end
    end

    opt Some pairs failed
        rect rgba(251, 191, 36, 0.14)
        C->>L: POST /translate/retry/{quote_id} (one free retry)
        L->>Q: Only the retryable failed pairs, same agent
        end
        rect rgba(52, 211, 153, 0.14)
        Note over L,St: Refund sweeper, every 15 minutes
        L->>St: Refund what is still undelivered (after the retry, Refund instead, or 72h)
        St-->>L: Refund issued once (idempotency key txfr-quote_id)
        L-->>C: Refund email
        end
    end

    Note over L,St: The same sweeper refunds in full a paid quote that never started (202)

    C->>L: GET /translate/download/{job_id}
    L-->>C: Private download links (7 days)
        

Key components:

4.2 Validator System

Every translated section passes these checks before it is accepted:

ValidatorWhat It Checks
Protected TermsBrand and product names survive unchanged
Term SpacingA protected term has not been glued onto the next word (skipped for the 8 languages that do not separate words with spaces: Japanese, Chinese, Traditional Chinese, Cantonese, Thai, Lao, Khmer and Burmese)
Version Numbers17.7, 4.6, v2.0 come through exactly
Preserved ValuesURLs, IP addresses, ARNs, email addresses and other technical identifiers are unchanged
Tag CountsThe HTML structure matches the source, so nothing is dropped or doubled
Protected ZonesContent inside translate="no" is byte-for-byte unchanged
DegenerationThe output has not collapsed into repetition or truncation

A section that fails is retried, with a directed correction when the engine knows exactly which rule was broken. A pair that still cannot pass is reported as failed rather than delivered damaged. You can retry just the failed pairs at no extra charge, and whatever still can't be delivered is refunded automatically (see 3.9).

The key insight: AI models make the right kind of mistakes — visible and recoverable. The validators catch them before anything ships. Generic translation services fail silently and structurally, and you only find out by reading every page yourself.

No pattern-matching guesswork: the structural validators walk the text character by character with exact boundary matching. We control these tags and markers, so the checks know precisely where each one starts and ends. No partial matches, and no false alarms from text that merely looks similar.

4.3 Output Storage

Each accepted quote gets its own private folder:

s3://trinity-beast-translations/
  jobs/{quote_id}/
    source/api-reference.html          (your original, as checked at accept)
    source/faq.html
    output/es/api-reference.html       (Spanish)
    output/es/faq.html
    output/ja/api-reference.html       (Japanese)
    output/ja/faq.html

Your source documents are written here when you accept, after the second check and before your card is charged, so the job translates exactly the content whose price was verified, and nothing else. A free retry writes into the same folder, so a pair it delivers sits right beside the ones that delivered the first time. When you download, each file is named {lang}-{doc}, such as ja-faq.html.

Files are kept for 93 days and removed automatically after that. Download links are signed, private to you, and valid for 7 days, the longest a signed link can last, and you can request fresh ones any time within the 93 days.

4.4 What We Check Before You Pay

Everything that could make a job fail is checked when you ask for the quote, and checked again when you accept, so you never pay for work that cannot run. The quote and the job read the same limits from one place in the code, so a quote can never promise something the job would refuse.

The quote also carries the engine's analysis of each document: its size, the number of sections it will be translated in, its difficulty, and how many code blocks and diagrams it will protect. Splitting happens at natural boundaries such as headings, tables and code blocks, never in the middle of a paragraph, which gives the agent whole thoughts to work with and you better translations.

4.5 Observability & Control

We built this to be watched and steered, not to disappear for ten minutes behind a job ID.

The difference: generic batch services give you a job ID and say "check back later." We give you live progress, mid-job cancellation with a fair refund, free retry and automatic refunds for anything that couldn't be delivered, and a complete history. Observable. Controllable. Resilient.

4.6 Internal Efficiency — Delta Translation

We maintain a technical library of more than 40 documents translated into 11 languages, and those documents change as we ship. Rather than re-translating a whole document on every update, our internal pipeline identifies exactly which sections changed and processes only those.

This is an internal optimization, not a customer feature: you always submit a document and receive complete translations. It matters to you because it keeps our own costs low, which keeps your price flat, and because the engine translating your document is exercised every week on ours.

5. Customer Experience

5.1 Account Dashboard — The Entry Point

The Trinity Beast Account Dashboard is where you manage everything. Translation-only customers are first-class citizens: you do not need a Price API subscription to use translation.

Dashboard URL: cpmp-site.org/dashboard

One Email = One Account

When you log in you see everything associated with that email: REST API subscriptions, translation keys, webhook price feeds and recurring giving, in one view. To separate services by department or cost center, use a different email for each; every email is its own account with its own billing portal, invoices and payment method.

Translations Panel

Billing

5.2 Completion Email

When a job finishes you receive one email, in your communication language, with everything you need: the result for every document and language, the agent that translated it, how long it took, what you were charged, and a download link for every file. If any pair failed, the email shows which ones, and you can retry just those at no extra charge. When a failed pair is refunded, a second email in your language confirms which pairs were refunded and the amount coming back to you.

A batch is one email too. When you accept several quotes together, you hear from us once, when the last job in the batch is done, rather than once per document (see 5.6).

5.3 Brand Terms Protection

Every account can protect up to 150 brand terms. They are never translated, transliterated or changed in any way; they appear exactly as you wrote them in every language.

Why This Matters

Generic translation services do not know your brand names. "Acme Corp" becomes "アクメ株式会社" in Japanese and "DataForge" becomes "DatenSchmiede" in German. Product names, people's names and technical identifiers get localized when they should not.

How to Set Up

  1. Log in to your Account Dashboard at cpmp-site.org/dashboard
  2. Open the Translations panel
  3. Open Brand Terms
  4. Add your product names, people's names, company names and technical identifiers
  5. Save — the terms apply to every future job automatically

Limits

ConstraintValue
Maximum terms per account150
Maximum characters per term100
DuplicatesRemoved automatically
ScopeYour whole account — every job

What's Already Protected (Without Brand Terms)

Brand terms add protection where the engine cannot know on its own: names that appear inside ordinary sentences.

Example: with "CloudSync API" and "DataForge" as brand terms, "DataForge uses CloudSync API for real-time synchronization" becomes "DataForge は CloudSync API を使用してリアルタイム同期を行います" in Japanese — both names untouched, the sentence around them fully translated.

API Access

GET /dashboard/api/protected-terms    → your current list
PUT /dashboard/api/protected-terms    → replace the list
    Body: {"terms": ["Acme Corp", "DataForge", "CloudSync API"]}

These use your dashboard session rather than your API key. Details are in the API Reference §15.14.

5.4 Multi-Lingual API Messaging

Every API response speaks your language. When you set your api_lang preference in the Account Dashboard, all human-readable messages — errors, confirmations, validation feedback — are returned in that language. Data elements stay English always.

The Design Principle

Data stays English. Messages get localized.

Field names, IDs, status values, timestamps, amounts, quote IDs, job IDs — always English regardless of your language preference. Only the human-readable text (error descriptions, validation messages, informational labels) renders in your api_lang. This means your integration code never changes — parse the same JSON structure regardless of language.

What Gets Localized

CategoryLocalized?Example
Error messages✅ Yes"La cotización ha expirado" (Spanish)
Validation feedback✅ Yes"見積もりが見つかりません" (Japanese)
Email subjects & body✅ Yes"Ihre Übersetzung ist fertig" (German)
Field names❌ Noquote_id, job_status, total_price
Status values❌ No"accepted", "pending", "expired"
UME envelope brackets❌ No✅ [LPO] [us-east-2] [BeastMain]
Timestamps & amounts❌ No"2026-07-08T14:00:00Z", "$3.96"

Setting Your Language Preference

Two language settings control your experience:

Message Languages (12)

These are the languages our messages speak, not the languages your documents can go into: your documents can be translated into any of the 127 languages in 1.5. API messages and emails are available in all 12 core languages:

en · es · pt · fr · de · ru · hi · ur · it · ar · ja · zh

If your api_lang is set to a language not in this list, messages fall back to English.

Example: Same Error, Different Languages

A customer with api_lang: "ja" sees validation errors in Japanese:

{
  "status": "🛑 [LPO] [us-east-2] [BeastMain] [/translate/accept-batch] [409]",
  "status_code": 409,
  "endpoint": "/translate/accept-batch",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "ja",
  "api_key_id": "ed5ff412-4de1-4fb4-a96d-1f7053834f61",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "timestamp": "2026-07-08T14:00:00Z",
  "data": {
    "status": "validation_failed",
    "message": "バッチ内の一部の見積もりが処理できませんでした。エラーを確認し、有効な見積もりのみで再送信してください。",
    "errors": [
      {
        "quote_id": "qt-7c1e2a90-4b3",
        "error": "見積もり qt-7c1e2a90-4b3 は有効期限が切れています。新しい見積もりをリクエストしてください。",
        "key": "tbts_quote_expired",
        "new_quote_id": "",
        "new_total_price": 0
      }
    ],
    "valid": 2,
    "invalid": 1
  },
  "error": ""
}

The same request from a customer with api_lang: "es":

{
  "status": "🛑 [LPO] [us-east-2] [BeastMain] [/translate/accept-batch] [409]",
  "status_code": 409,
  "endpoint": "/translate/accept-batch",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "es",
  "api_key_id": "ed5ff412-4de1-4fb4-a96d-1f7053834f61",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "timestamp": "2026-07-08T14:00:00Z",
  "data": {
    "status": "validation_failed",
    "message": "Algunas cotizaciones no pudieron procesarse. Revise los errores y reenvíe solo las cotizaciones válidas.",
    "errors": [
      {
        "quote_id": "qt-7c1e2a90-4b3",
        "error": "La cotización qt-7c1e2a90-4b3 ha expirado. Solicite una nueva.",
        "key": "tbts_quote_expired",
        "new_quote_id": "",
        "new_total_price": 0
      }
    ],
    "valid": 2,
    "invalid": 1
  },
  "error": ""
}

The integration contract stays identical. Parse data.errors[].quote_id and branch on data.errors[].key, then show data.errors[].error to your user — same structure, same field names, same logic. The only difference is the human-readable string values. Your code doesn't need language-specific branches.

5.5 Error Handling & Pre-Validation

The batch acceptance endpoint validates every quote BEFORE charging your payment method. If any quote in your batch is invalid, the entire batch is rejected with structured errors — no partial charges, no guessing which ones failed.

Fail-Fast, Fix-Once Design

Validate everything. Charge nothing. Return all errors in one round-trip.

When you submit a batch of 9 quotes, all 9 are validated against Aurora before Stripe is touched. If quotes #3 and #7 have problems, you get both errors back immediately — fix them and resubmit. Your card is never charged for a batch that can't fully execute.

Error Types

Every entry in errors has the same five fields, always present: {quote_id, error, key, new_quote_id, new_total_price}. key tells your code exactly what happened, error says it in your api_lang, and the last two are filled only when we prepared a replacement quote for you. The pre-validation gate catches these problems:

KeyMeaningResolution
tbts_quote_not_foundThe quote ID does not exist or belongs to a different accountVerify the quote ID — check GET /translate/quotes for your active quotes
tbts_quote_already_processedThis quote was already accepted and paid for (includes the date it was processed)Remove it from your batch — the job is already running or complete
tbts_quote_expiredThe quote's 72-hour validity window has passedRequest a new quote — pricing may have changed
tbts_quote_accept_in_progressAnother request is accepting this quote right nowWait a minute, check the quote, then resubmit without it if it was accepted
tbts_quote_needs_requoteThe quote no longer passes today's checks, or a document couldn't be fetched againRequest a new quote for those documents
tbts_quote_requotedA document changed and the price or source result changed with it. new_quote_id and new_total_price name the replacement we preparedResubmit the batch with new_quote_id in place of the old ID
A rule's own key, such as tbts_doc_too_large or tbts_target_is_sourceA document now breaks a rule, for example it grew past 300 KB. No replacement quote is madeFix the document or the languages, request a new quote, and resubmit

The batch-level status: "validation_failed" and its message summarize the whole response, and valid / invalid count the quotes on each side.

Response Structure (409 Conflict)

When pre-validation fails, the response uses HTTP 409 with structured error detail:

{
  "status": "🛑 [LPO] [us-east-2] [BeastMirror] [/translate/accept-batch] [409]",
  "status_code": 409,
  "endpoint": "/translate/accept-batch",
  "cluster_node": "BeastMirror",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ed5ff412-4de1-4fb4-a96d-1f7053834f61",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "timestamp": "2026-07-08T14:30:00Z",
  "data": {
    "status": "validation_failed",
    "message": "Some quotes in your batch could not be processed. Please review the errors below and resubmit with valid quotes only.",
    "errors": [
      {
        "quote_id": "qt-7c1e2a90-4b3",
        "error": "Quote qt-7c1e2a90-4b3 has expired. Please request a new quote.",
        "key": "tbts_quote_expired",
        "new_quote_id": "",
        "new_total_price": 0
      },
      {
        "quote_id": "qt-91d0f3b2-6ae",
        "error": "user-guide.html changed since you were quoted, so we've prepared a new quote: $36.00. You have not been charged.",
        "key": "tbts_quote_requoted",
        "new_quote_id": "qt-e5b27c08-9f1",
        "new_total_price": 36.00
      }
    ],
    "valid": 7,
    "invalid": 2
  },
  "error": ""
}

Handling Pre-Validation in Your Integration

// Pseudocode — handling the 409 pre-validation response
response = POST("/translate/accept-batch", { quote_ids: [...] })

if response.status_code == 409:
    // No charge was made. Fix the issues and retry.
    for error in response.data.errors:
        log(f"Quote {error.quote_id} failed: {error.key} {error.error}")
        if error.key == "tbts_quote_requoted":
            replace(quote_ids, error.quote_id, error.new_quote_id)   // the fresh quote we prepared
        else:
            remove(quote_ids, error.quote_id)

    retry_response = POST("/translate/accept-batch", { quote_ids: quote_ids })

elif response.status_code == 200:
    // All quotes valid, payment processed, jobs submitted.
    task_id = response.data.task_id
    // Each job appears in your dashboard; one email arrives when the batch is done.

Idempotent by design: If a quote has already been processed, the error includes when it was processed and tells you to remove it. This prevents double-charging — even if your retry logic accidentally re-submits a completed quote, it will never be charged twice.

Successful Batch Response (200 OK)

When all quotes pass validation and payment succeeds:

{
  "status": "✅ [LPO] [us-east-2] [BeastMain] [/translate/accept-batch] [200]",
  "status_code": 200,
  "endpoint": "/translate/accept-batch",
  "cluster_node": "BeastMain",
  "region": "us-east-2",
  "language": "en",
  "api_key_id": "ed5ff412-4de1-4fb4-a96d-1f7053834f61",
  "ip_address": "203.0.113.42",
  "agent_profile_arn": "arn:tbi:us-east-2:211998422884:agent-profile/tbts/v1",
  "timestamp": "2026-07-08T14:30:00Z",
  "data": {
    "status": "accepted",
    "batch_size": 3,
    "quote_ids": ["qt-7c1e2a90-4b3", "qt-91d0f3b2-6ae", "qt-c48a5e17-0d2"],
    "task_id": "019f3b4d8e9f-0a1b2c3d4e5f6a7b",
    "task_status_url": "/translate/task/019f3b4d8e9f-0a1b2c3d4e5f6a7b",
    "jobs": [
      {"quote_id": "qt-7c1e2a90-4b3", "doc_name": "api-reference.html", "job_id": "019f3c5a0b1c-2d3e4f5a6b7c8d9e", "error": "", "key": "", "status_url": "/translate/status/019f3c5a0b1c-2d3e4f5a6b7c8d9e", "refunded": false, "refund_amount": 0, "notice": "", "notice_key": "", "updated_docs": []},
      {"quote_id": "qt-91d0f3b2-6ae", "doc_name": "user-guide.html", "job_id": "019f3c6b1c2d-3e4f5a6b7c8d9e0f", "error": "", "key": "", "status_url": "/translate/status/019f3c6b1c2d-3e4f5a6b7c8d9e0f", "refunded": false, "refund_amount": 0, "notice": "", "notice_key": "", "updated_docs": []},
      {"quote_id": "qt-c48a5e17-0d2", "doc_name": "quick-start.html", "job_id": "019f3c7c2d3e-4f5a6b7c8d9e0f1a", "error": "", "key": "", "status_url": "/translate/status/019f3c7c2d3e-4f5a6b7c8d9e0f1a", "refunded": false, "refund_amount": 0, "notice": "", "notice_key": "", "updated_docs": []}
    ],
    "payment_id": "pi_3TgvABC123...",
    "amount_usd": 99.00,
    "eta": {
      "estimated_seconds": 540,
      "estimated_low_seconds": 420,
      "estimated_high_seconds": 660,
      "estimated_formatted": "~9 minutes",
      "estimated_range": "7-11 minutes",
      "bottleneck_lang": "ar",
      "elapsed_seconds": 0,
      "remaining_seconds": 540,
      "remaining_formatted": "~9 minutes",
      "percent_complete": 0
    },
    "key": "",
    "message": ""
  },
  "error": ""
}

In this example three quotes of 11 pairs each come to one charge of 99.00. The task_id identifies the batch as a unit, task_status_url follows all of it at once, and each job inside it appears in your dashboard with its own progress and downloads. The eta is the slowest job in the batch, so it tells you when everything will be done.

Each entry in jobs reports that quote's own start, so one quote that can't start never holds the others back. A quote that couldn't start is refunded in full on its own: its job_id is empty, key is tbts_translation_start_refunded (or tbts_translation_start_pending while we confirm), refunded and refund_amount show what came back, and its status_url uses the quote ID. If no quote in the batch could start, the response is 502 with status: "start_failed" and the same jobs list.

5.6 Task Organizer — Batch Orchestration

When you accept several quotes in one batch, the engine creates a parent Task that tracks all of the resulting jobs as a unit. One acceptance, one Task, one notification when everything is finished.

The Problem It Solves

Without it, accepting 9 quotes would mean 9 separate completion emails arriving one by one. That is noise, not signal. The Task Organizer waits until every job in the batch has finished and then tells you once.

How It Works

  1. Batch accepted — every quote is checked, one payment is taken, the Task is created and each quote starts as its own job
  2. Jobs run in parallel — each job translates its documents into all of its languages at once, one container per language
  3. Jobs finish independently — the Task records each one, whether it succeeded, partly failed or was cancelled
  4. One email — when the last job finishes, you receive a single email covering the whole batch

The task_id in the accept-batch response identifies the batch, and each job inside it appears in your dashboard with its own live progress and downloads.

The Consolidated Email

The batch email arrives in your communication language and contains:

Cancel Within a Batch

Every quote in a batch is its own job, so you cancel with the same precision you accepted with: send POST /translate/cancel/{quote_id} for each quote you want to stop. The jobs you leave alone carry on untouched, finished jobs stay available, and each cancelled quote is refunded for exactly the pairs it did not deliver (see 3.8).

The workflow: request quotes → accept them as a batch → watch progress in the dashboard → one email when everything is done → download. One payment, one notification, every file in reach.

6. What's Live

Everything in this document is in production today:

EndpointWhat It Does
POST /translate/quotePrice one or more documents into one or more languages
GET /translate/quote/{quote_id}Look up one quote
GET /translate/quotesYour 30 most recent quotes
POST /translate/accept/{quote_id}Pay and start one quote
POST /translate/accept-batchPay and start up to 9 quotes together
GET /translate/download/{job_id}Download links for a finished job
POST /translate/cancel/{quote_id}Cancel a running job and refund every undelivered pair
GET /translate/status/{job_id}Live status of one job: every pair, the ETA and any refund
GET /translate/task/{task_id}Live status of a whole batch
POST /translate/retry/{quote_id}Retry the failed pairs, at no charge
POST /translate/refund-failed/{quote_id}Refund the failed pairs now instead of retrying
GET /translate/modelsThe six agents (public)
GET /translate/languagesThe live language catalog and limits (public)

Progress, history, retries, brand terms and billing live in the Account Dashboard. The full contract for every endpoint, including each error, is in the API Reference §15.

7. Risks & Mitigations

RiskImpactMitigation
Very large documentsUnreliable splitting, runaway cost300 KB per document, checked at quote time before any charge and again when the quote is accepted; a larger document is refused, never cut short
Floods of submissionsQueue congestion, costA valid API key on every request, payment before any work starts, at most 12 languages and 42 documents per quote with one worker per language, and an engine-wide $600 daily cap on AI spend that is checked before any charge
Unsupported languagesCharged for a translation we cannot doEvery language code is checked against the live catalog before a quote is priced
Paying for undelivered workCustomer charged for nothingFailed pairs are retried free and refunded automatically; a paid job that can't start is refunded in full; a pair is never both delivered and refunded
A document changes after the quoteCharged a price computed on other contentEvery document is fetched again and compared before the charge; a changed price is re-quoted, never charged
AI provider interruptionsJobs slow down or failAutomatic retries; a pair that still fails is reported, never delivered damaged, retried free on request, and refunded automatically if it can't be delivered
Damaged outputBroken documentationProtected content never reaches the agent, and seven validators check every section before delivery
Customer data privacyTrustA private folder per quote, signed download links that expire after 7 days, and automatic deletion after 93 days
Cost underestimationNegative marginFlat per-pair pricing, with Claude Opus 4.8 priced from its actual cost on large documents
Complex scriptsArabic, Urdu and Hindi take longerPer-language tuning of section size and difficulty, with every language running in its own container so a slow one never delays the rest

Business Case Summary

What we offer: AI-powered document translation that actually works for technical content.

Why it works:

Revenue potential: every document translated into 11 languages is 33.00. Ten customers translating ten documents a month into 11 languages is 1,100 pairs, or 3,300.00 a month, against an AI cost of a few cents per pair.

Bottom line: we solved this problem for ourselves. Now we can solve it for others — and let the service help fund the ministry's mission.