> ## Documentation Index
>
> Fetch the complete documentation index at: https://lithi.ai/llms.txt
>
> Use this file to discover all available pages before exploring further.

---
title: Idempotency keys and safe retries
description: >-
  Six retry situations and the one rule behind them. An exact replay returns the
  prior result, while the same key with a different payload refuses. Reconcile
  an unknown outcome before you retry.
route: /docs/compute/jobs/idempotency-and-retries
page_id: docs-compute-jobs-idempotency-and-retries
page_type: howTo
content_layer: guided documentation
surface_profile: guided_docs
audience: Compute customers and integrators writing retry logic
voice: D
reader_question: When is it safe to retry, and what will a repeat actually do?
primary_action: Retry without creating a second charge
source_locale: en-US
source_status: APPROVED
source_version: docs-compute-jobs-idempotency-and-retries-en-us-2026-09-05-v1
last_updated: '2026-09-05'
robots: index
claim_registry_pinned: true
claim_registry_resync: "npm run governed:tsx -- scripts/build-compute-public-claim-registry.ts --write"
---

## The one rule

An exact replay returns the prior result. The same identity carrying a different payload is equivocation, and it refuses rather than choosing between the two.

That is the whole contract. Every mutable record carries a non-decreasing version, and a late or stale attempt cannot advance state past a newer one.

## Reconcile before you retry

An unknown outcome is not a failed outcome. A dropped connection tells you nothing about whether the work was admitted.

Read the job's status first. If it exists, you have your answer and there is nothing to retry. Retrying an outcome you have not read is how one intent becomes two runs.

## Six situations, six different actions

| Situation | What to do | What comes back |
| --- | --- | --- |
| Transport error, same intent | Resend the identical request with the same key | The prior result, or the work runs exactly once |
| You already hold an admission | Read the job by its identifier | The current state; never resubmit to check |
| Quote expired or no longer matches | Request a new quote and approve it | Admission returns `REPLAN_REQUIRED` until you do |
| You genuinely want a second run | Use a new key and a new quote | New work, and a second charge |
| Repeating a delivery | Retry the destination write | The delivery repeats; the work does not rerun |
| Replaying a schedule occurrence | Replay that occurrence | A replay of the same occurrence, not a new run |

The schedule case is worth spelling out. An occurrence key is derived from the schedule, its version and the intended occurrence, so replaying the same occurrence is always a replay.

## The six idempotency scopes

Keys do not all mean the same thing, and they do not share a namespace.

- **Request** — one submitted call.
- **Batch** — one submitted batch as a whole.
- **Manifest** — one input manifest.
- **Logical item** — one item inside the work.
- **Artifact** — one uploaded input or output file.
- **Destination write** — one write to one destination.

A destination-write key is why a redelivery does not rerun the work. The work already happened; only the write repeats.

## Lifetime, and what we will not publish

We publish no key lifetime, so do not code against an assumed one. Design around the scope instead.

The response tells you which case you are in. It carries the typed state, the observation time and a next action. Branch on what actually happened, never on a timer.

## When a retry refuses

A refusal for equivocation means the key you reused is bound to a different payload. Do not vary the key to get past it.

Send the original payload if you meant a replay, or start new work with a new key if you meant new work. See [how to read a job's counts](/docs/compute/jobs/status-and-events) before deciding, and [how to repeat a delivery without rerunning](/docs/compute/delivery/retry-without-rerun) when only the write failed.
