---
title: "Idempotent writes"
description: "Retry accepted writes without duplicating resources, generation work, or repository effects."
url: https://typeship.dev/docs/typeship-api/idempotency
markdown: https://typeship.dev/docs/typeship-api/idempotency.md
section: "API"
---
> ## Documentation index
> Fetch the documentation index at https://typeship.dev/llms.txt or every prose page and both generated references at https://typeship.dev/llms-full.txt.
> Append .md to any prose docs URL, or send Accept: text/markdown, for the markdown twin of that page.

# Idempotent writes

Retry accepted writes without duplicating resources, generation work, or repository effects.

Send an `Idempotency-Key` on a retry-sensitive write. If the connection drops after Typeship accepts the request, retry the same operation with the same key and inputs. The API returns the first completed response without running the work again.

```bash
curl -sS https://typeship.dev/api/v1/projects/prj_.../generations \
  -X POST \
  -H "Authorization: Bearer $TYPESHIP_TOKEN" \
  -H "Idempotency-Key: deploy-acme-2026-09-02"
```

The TypeScript, Python, and Go SDKs generate a key when you omit one and keep it stable across the call's transport retries. Pass your own key when the logical write must survive a process restart. The typeship CLI exposes `--idempotency-key`, and typeship MCP tool schemas expose the same optional `Idempotency-Key` input.

## Replay contract

* A key is scoped to one authenticated account and operation. Anonymous `POST /generate` uses a hashed network identity as its account-less scope.
* The request identity includes the HTTP method, path parameters, query parameters, and JSON body. JSON object order and query-name order do not matter; array and repeated-query-value order do.
* Identical concurrent requests wait on one reservation and converge on its response.
* A replay does not consume the operation's rate-limit budget again or get replaced by a later rate-limit response.
* `Idempotency-Replayed: false` marks the first completed response. A replay returns `Idempotency-Replayed: true` with the original status, semantic body, and contract-relevant headers. The replay receives a fresh `request_id` so its own HTTP attempt remains traceable.
* Reusing a key with a changed path, query, or body returns `409 idempotency_key_reused` and does not run the new intent.
* If the first request is still running beyond the request wait window, the API returns retryable `429 rate_limited` with `Retry-After`. Retry the same request and key.

Completed responses and deterministic client or precondition failures are retained for 24 hours. `408`, `425`, `429`, server failures, and interrupted executions release their reservation so a later attempt can run. Expired rows are removed as new keyed writes arrive. After 24 hours, reusing a key starts a new logical write, so do not rely on it for permanent resource identity.

Authentication and authorization run before the account-scoped replay lookup. Fix `401` or `403` responses rather than retrying them as an idempotent write.

## Public write decisions

The checked-in source for this audit is [`openapi/idempotency-matrix.json`](https://github.com/ryanburke/typeship/blob/main/openapi/idempotency-matrix.json). `Replay store` means the operation accepts `Idempotency-Key`. `Natural` means repeating the method can change the response, but cannot repeat the resource or external side effect.

| Operation                     | Write                                                  | Decision     | Why                                                                                  |
| ----------------------------- | ------------------------------------------------------ | ------------ | ------------------------------------------------------------------------------------ |
| `run`                         | `POST /generate`                                       | Replay store | Generation consumes rate-limited compute even though no durable result is retained.  |
| `createProject`               | `POST /projects`                                       | Replay store | Creates a Project, Definition, initial Targets, and first revision.                  |
| `updateProject`               | `PATCH /projects/{project_id}`                         | Natural      | Replaces stored desired-state fields and starts no generation or delivery.           |
| `deleteProject`               | `DELETE /projects/{project_id}`                        | Natural      | Converges on absence without an external side effect.                                |
| `updateDefinition`            | `PATCH /definitions/{definition_id}`                   | Replay store | Resolves external source input and may record a new immutable revision.              |
| `refreshProjectDiagnostics`   | `POST /projects/{project_id}/diagnostics`              | Replay store | Fetches the source and may record a new immutable revision.                          |
| `remediateProjectDiagnostics` | `POST /projects/{project_id}/diagnostics/remediations` | Replay store | Changes overlays or creates or updates a source review.                              |
| `createTarget`                | `POST /projects/{project_id}/targets`                  | Replay store | Persists a separately metered Target and its Deliveries.                             |
| `updateTarget`                | `PATCH /targets/{target_id}`                           | Natural      | Replaces Target and Delivery desired state; generation owns repository effects.      |
| `deleteTarget`                | `DELETE /targets/{target_id}`                          | Natural      | Is allowed only without Generation or release dependencies and converges on absence. |
| `generateProject`             | `POST /projects/{project_id}/generations`              | Replay store | Persists Generations, consumes metered work, and may update repository reviews.      |
| `revokeApiKey`                | `DELETE /api_keys/{api_key_id}`                        | Natural      | Converges on one revoked state and already returns the same representation.          |

For a replay-store operation, omitting the header starts an ordinary unprotected write. Do not retry an accepted write after a timeout unless your SDK supplied a key or you supplied one yourself.

## Sitemap

[Documentation index](https://typeship.dev/llms.txt)
