An idempotency key is a client-supplied string telling a translation API "this submission and the one I sent ninety seconds ago are the same job — do not start a second one." Without one, every retry creates a new job: a second charge, a second output file, and a matter folder holding two translations of one agreement with nothing to say which is authoritative. The key that works for legal work is derived rather than random — matter ID, a hash of the document, and the target language — so a retry collapses onto the original job while a genuine re-translation after an amendment does not.
This guide covers why retries happen, what duplicates cost, what to ask a vendor, and how to build and test a key you can rely on — one level below the document translation API evaluation checklist.
Retries Are a Property of Distributed Systems, Not a Bug in Your Code
Your integration will retry. Not because it is badly written, but because it sits on a network. A TLS handshake stalls and your client's timeout fires while the upload is already complete on the far side. A deploy rolls pods mid-flight and the request dies after the vendor accepted it. A load balancer returns 502 on a request that succeeded behind it. A worker crashes after submitting and before committing its own state, so on restart it submits again.
Then there is the human layer, which is less exotic and more frequent: a paralegal clicks Translate, the spinner does not move, and they click it again. Twice. Then they refresh the page.
Every one of those paths produces a duplicate submission indistinguishable, from the vendor's side, from a deliberate second job. The API cannot infer intent; you have to state it, and the idempotency key is how.
What a Duplicate Job Actually Costs
The billing line is the obvious cost and the least interesting one. Two hundred pages charged twice is annoying, but it is visible on an invoice and someone eventually notices.
The expensive failure is quieter. Two jobs on one source file, run minutes apart, can return subtly different outputs — a different rendering of a defined term, a different treatment of an ambiguous clause. Both land in the matter folder, timestamped within the same hour. Neither is marked canonical.
Six months later, someone quotes clause 14.3 of a translated agreement and someone else quotes a 14.3 that reads differently. You are no longer debugging an integration; you are reconstructing which file the advice rested on. Against Federal Rules of Civil Procedure discovery obligations, "we hold two versions and cannot say which was used" is a worse answer than any technical account of how it happened.
There is a data-protection edge too: duplicate copies of documents containing personal data multiply your retention and deletion surface, cutting against the storage-limitation principle in GDPR.
The Three Things an API Can Do With a Repeat Submission
Ask the vendor directly, in writing, because this is the behaviour your retry logic rests on.
It returns the original job. The API recognises the key, does not start new work, does not bill again, and returns the existing job ID and its current state — pending, processing or complete. This is the behaviour you want.
It returns an error. A 409 Conflict on a key already in use is workable, though it forces your client to treat an error path as a success path in disguise. It also breaks down if the original response was lost, because you then need a lookup-by-key endpoint to recover the job ID. Ask whether one exists.
It silently creates a second job. The API accepts the key, ignores it, and starts fresh work. This is the dangerous default, and more common than documentation suggests — an Idempotency-Key header that is accepted and discarded looks identical, from the client, to one that works.
Deriving a Key That Means Something
A random UUID generated at submission time is not an idempotency key. It is a request ID. If your process dies before it records that UUID, the retry generates a different one and you are back to duplicates.
The key has to be reproducible from facts that survive a restart. Three inputs cover matter-based work:
key = sha256(matter_id + ":" + document_hash + ":" + target_language)
matter_id scopes the key so the same standard NDA translated for two clients produces two jobs, which is correct — separate billable work, separate confidentiality boundaries. document_hash identifies the exact content being translated. target_language separates the French and German runs of one document.
Add the glossary or term base version if your pipeline applies one, since translating a contract under a revised deal glossary is a new job. Keep the recipe in one function, with the field order fixed — a key you cannot regenerate identically is not a key.
Hashing the Document, Not the Upload
The subtle part is document_hash. Hash the raw uploaded bytes and you will get false negatives — two submissions of "the same" document producing different keys and therefore duplicate jobs.
The reason is that modern office formats are ZIP containers. A DOCX is an Office Open XML package, and its bytes change without its content changing: modification timestamps inside the archive, compression level, entry ordering, the docProps metadata recording who last opened the file. Re-save an untouched contract in Word and the SHA-256 moves. Round-trip it through a document management system and it moves again.
Two workable approaches. Hash the extracted body — word/document.xml plus headers, footers and footnote parts — so cosmetic container changes are ignored. Or treat your DMS version ID as the identity, which is often simpler because the DMS has already made that decision.
Whichever you choose, decide once and write it down. Half a pipeline hashing bytes and half hashing content produces intermittent duplicates that are miserable to trace.
Amendments Should Break the Key, and That Is the Point
The common objection to derived keys is that they make re-translation impossible. They do not, provided the derivation includes the document hash.
An amended agreement is different bytes. Different bytes, different hash, different key, new job. Exactly right — the amended version genuinely needs translating and genuinely should be billed. What the key blocks is the accidental second job on unchanged input, the only case where duplication has no value.
The case needing an explicit decision is the deliberate re-run on identical input: someone was unhappy with the output and wants it done again. That is legitimate, and a derived key will block it. Handle it with a run counter in the recipe, incremented only by an explicit user action recorded with a reason. The re-run is then intentional and attributable — in a matter file, the difference between a second version and a mystery.
Key Lifetime and the Window That Matters
Idempotency keys are not stored forever. Vendors typically honour them for a bounded window — hours or days — after which the same key is treated as new. Ask for the exact retention period and whether it is measured from first submission or last activity.
For most retries the window is irrelevant, because they happen within seconds. It matters in exactly the cases that hurt: a job that failed on Friday and is manually re-queued on Monday, a backlog drained after an incident, a queue paused during a migration and released a week later.
Design so the window is not load-bearing. Your own store, not the vendor's key table, should be the authority on whether this work was already submitted. Look up the key locally first, and submit only if you have no record of it. The vendor's behaviour is then a second layer covering the case where your local write failed — precisely the case it is good at.
Making Your Own Side Durable
Idempotency on the vendor side is worth little if your side forgets what it sent. The order of operations matters, and the safe one is counterintuitive: write the key and its status to durable storage before you make the HTTP call, not after you get a response.
A row holding the derived key, matter ID, document hash, target language, status submitting and a created timestamp — committed first — means a crash mid-request leaves evidence. On restart the worker finds the submitting row, resubmits with the same key, and either gets the original job back or creates it for real. Both outcomes are correct.
Write the vendor's job ID into that row when the response arrives, and make the key a unique index. That constraint is the last line of defence: a bug that double-submits fails at the database rather than at the invoice. Teams comparing services in this r/sysadmin thread on document translation tend to arrive at this ordering after an outage rather than before one.
Testing Idempotency Instead of Believing It
Documentation claims are cheap and this behaviour is trivially testable. Four tests, twenty minutes, before anything is built on top.
Same key, twice, back to back. Expect one job ID and one billable unit. Check the usage endpoint, not just the response body.
Same key, second sent while the first is still processing. Some implementations deduplicate only against completed jobs, which is the wrong half of the problem.
Same key, different file. A vendor returning the first document for a mismatched payload is worse than one that errors — verify which you have.
Same key after the honoured window expires. Confirms the retention period you were quoted is real.
Repeat test one through whatever path handles bulk submissions, where one retry duplicates hundreds of documents rather than one. What you are left with is also most of an audit trail: the row that prevents duplicates records which source hash, matter, language and glossary version produced a given file — the reproducibility evidence an ISO/IEC 27001 control set expects.
Sources and Further Reading
Document translation services — sysadmins comparing services and describing what breaks in day-to-day operation
Related Reading
Last reviewed 24 August 2026 by the Bluente document engineering team, who build and test the pipeline described here. We update these guides when the underlying standards, regulations or file formats change.
Retry safely, bill once, keep one authoritative version. Try BluTranslate free.