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. Project generation replays the same Generation IDs with their current statuses; other writes replay their saved response when available.
curl -sS https://typeship.dev/api/v1/projects/prj_.../generations \
-X POST \
-H "Authorization: Bearer $TYPESHIP_TOKEN" \
-H "Idempotency-Key: deploy-parcel-2026-09-02"The Typeship CLI, MCP server, and 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 accepts --idempotency-key, and Typeship MCP tools accept Idempotency-Key.
A key must contain 1 to 255 visible ASCII characters, with no spaces. An invalid key returns 400 idempotency_key_invalid with in: "header" and field: "/idempotency-key" before the write starts.
Replay contract
- A key is scoped to one authenticated Organization and operation. Anonymous
POST /generatescopes the key to the originating network identity. - The request identity includes the HTTP method, path parameters, query parameters,
If-Matchheader, and JSON body. JSON object order and query-name order do not matter; array and repeated-query-value order do. - Identical concurrent requests share one execution. For Project generation, the queued Generations and replay handle are saved together before the
202response; a replay during the run returns their current statuses. Other writes can return429 idempotency_key_in_usewithRetry-After: 1while their result is unavailable. Pending duplicates and completed replays do not consume another generation allowance. - A replay does not consume the operation's rate-limit budget again or get replaced by a later rate-limit response.
Idempotency-Replayed: falsemarks the first response. A replay returnsIdempotency-Replayed: truewith the original status and a freshrequest_id. For Project generation, its body reflects latest statuses for its Generations; other writes return the saved semantic body and contract-relevant headers, includingETag.- Reusing a key with a changed path, query,
If-Match, or body returns409 idempotency_key_reusedand does not run the new intent.
A write that ends with a server failure releases its key at once unless the response is follow_up_failed, which means the change was saved. That committed failure is retained for replay. If the platform ends a one-shot POST /generate before it can answer (for example, at its 5-minute limit), its key is released when that run's lease expires, a little over 5 minutes after it started; until then a retry returns 429 idempotency_key_in_use. Other writes keep such a reservation so they never run twice, and a retry after the lease still returns 429 until the 24-hour window ends.
Completed responses, deterministic client or precondition failures, and follow_up_failed responses are available for replay for 24 hours from the first accepted request. This includes generated files from POST /generate. We delete cached responses after their replay window expires. Retry 408, 425, 429, and other server failures with the same key. For follow_up_failed, follow suggested_action instead of retrying the original key. After 24 hours, reusing a key starts a new logical write, so inspect the affected resources before starting another attempt.
Keyed one-shot generations also return download: a private ZIP URL, SHA-256 checksum, file count, size, and expiry. The URL downloads the exact generated files until the original replay window ends. Anyone with the URL can use it without an API key. Replays return the same link; downloading neither generates again nor extends its expiry. Requests without an idempotency key return inline files without a download link. CLI, MCP, and SDK clients supply keys automatically.
Authentication and authorization run before the Organization-scoped replay lookup. Fix 401 or 403 responses rather than retrying them as an idempotent write.
Supported operations
Key supported means the operation accepts Idempotency-Key. Safe to repeat means repeating the operation can change its response, but does not duplicate its resource or external side effect. If-Match preconditions still apply.
| Operation | Write | Retry behavior | Effect |
|---|---|---|---|
generatePackage | POST /generate | Key supported | Returns one package without creating a Project; a replay returns the same files without repeating generation work. |
createProject | POST /projects | Key supported | Creates a Project, Spec, initial Targets, and first revision. |
updateProject | PATCH /projects/{project_id} | Safe to repeat | Saves desired state, then retires obsolete release reviews. A retry finishes any remaining retirement. |
deleteProject | DELETE /projects/{project_id} | Safe to repeat | Retires release reviews before deleting the Project. Repeating a completed deletion returns 404. |
updateSpec | PATCH /specs/{spec_id} | Key supported | Resolves external source input and may record a new immutable revision. |
refreshSpec | POST /specs/{spec_id}/refresh | Key supported | Fetches the source, may record a new immutable revision, and queues automatic generation when enabled. |
createTarget | POST /targets | Key supported | Creates a Target and its Deliveries. |
updateTarget | PATCH /targets/{target_id} | Safe to repeat | Saves desired state and retires obsolete reviews; retained Delivery kinds keep their IDs and hosted URLs. A version selection also starts or resumes generation. |
updateDraft | PATCH /drafts/{draft_id} | Safe to repeat | Saves a version selection and starts or resumes its generation. Repeating a completed selection starts no new work. |
resolveDraft | POST /drafts/{draft_id}/resolve | Safe to repeat | Requires the current Draft head. Conflict decisions save together; choosing generated for customized files commits those replacements. A no-op returns status: up_to_date; after a commit, retrying the old head returns resource_changed. Preview changes nothing. |
recoverDraft | POST /drafts/{draft_id}/recover | Safe to repeat | Requires both reviewed revisions and saves one recovery; repeating it returns approved without a second recovery. Typeship continues after approval. |
deleteTarget | DELETE /targets/{target_id} | Safe to repeat | Is allowed only without Generation or release dependencies and converges on absence. |
createDelivery | POST /deliveries | Key supported | Adds a Delivery, may allocate a hosted MCP URL, and may queue the Target's Generation. |
updateDelivery | PATCH /deliveries/{delivery_id} | Safe to repeat | Replaces the Delivery's settings with desired state; repeating it saves the same settings. |
deleteDelivery | DELETE /deliveries/{delivery_id} | Safe to repeat | Converges on absence; repeating it returns 404. |
generateProject | POST /projects/{project_id}/generate | Key supported | Returns 202 with queued Generations; a matching replay returns the same IDs with current statuses. |
adoptTarget | POST /targets/{target_id}/adopt | Key supported | Verifies external provenance, records imported release, and opens the first Draft. |
retryRelease | POST /releases/{release_id}/retry | Key supported | Dispatches repository automation for one release. |
revokeApiKey | POST /api-keys/{api_key_id}/revoke | Safe to repeat | Converges on one revoked state and already returns the same representation. |
For an operation that supports Idempotency-Key, a direct API request without the header starts an unprotected write. After a timeout, retry only if the first request included a key, and reuse that key.
Recover after a timeout or disconnect
A timeout or disconnect does not establish whether the write finished, and stopping your client does not cancel accepted server work. The API has no cancellation operation. The Typeship CLI, MCP server, and SDKs bound their automatic retries; exhausting those retries does not establish the result either. Keep your own key so you can retry the same logical write from another call or process.
For Project generation, retry the same key after a lost response. The 202 body gives the original Generation IDs with current statuses, and Typeship resumes interrupted work. Poll each Generation until it succeeds or fails. For other writes, idempotency protects against duplicate execution but a pending key can return 429 until its 24-hour window expires.
For a Project, inspect the affected Targets and their Drafts after files are generated. A successful Generation does not prove that repository delivery finished. A new key starts another run for Targets that are no longer queued or running; use target_id to limit a deliberate new generation.
For one-shot generation, there is no Project or Generation record to retrieve. If no replay becomes available, a new key starts generation again. Waiting for the original key to expire also permits a new execution; expiry does not undo earlier effects.
Recover a saved update
A 502 from Project or Target PATCH means Typeship saved the update but could not complete its follow-up work. Retrieve the resource to inspect its current state. If you still want the same update, repeat it to finish retiring obsolete reviews or resume unfinished version-selection generation.
A 502 follow_up_failed after saving Spec patches has retryable: false: the patches were saved but automatic generation could not be queued. Retrieve the Spec to confirm its patches, then call POST /projects/{project_id}/generate with a new idempotency key or run typeship projects generate <project_id>. Re-saving the same patch list does not queue generation. Retrying the original key replays the recorded failure for 24 hours, even after generation becomes available again.
For regeneration_failed, follow retryable. When it is false, inspect the Target's Generation results and correct the cause before repeating the version selection or generating again. If you sent If-Match, retrieve the Draft and use its current ETag after checking that the saved selection is still the one you want. A stale precondition returns 412 without starting more work.
A 502 from Project DELETE means the Project still exists. Some release reviews may already be closed. Retry deletion to finish closing the remaining reviews and delete the Project. A later 404 confirms that the Project is absent from your organization.