PlatformHTTP API

Retries

Retry writes safely with IDs you choose before the first attempt, and find out whether a commit happened.

Available

Networks drop responses, so a write can succeed even when your client sees a timeout. To make retrying safe, every write in Spiral takes an ID that you choose before the first attempt, such as a transaction ID or an idempotency key. If you send the same request with the same ID again, you get the original result instead of a second write. If you send a different request with that ID, the service rejects it.

Automatic request retries

The Rust, Python, TypeScript and Go SDKs retry replayable GET, HEAD and PUT requests after a transient connection failure or HTTP 408, 429, 500, 502, 503 or 504. They use bounded backoff, with at most ten retries within the request's deadline. Cancellation stops retry backoff as well as the current attempt.

A supplied HTTP client or fetch function receives the same policy. Ordinary POST, PATCH and DELETE requests and streamed uploads are sent once by the transport. Use the operation's recovery API and the same request IDs when a write's outcome is uncertain. Table reads and other operations that already recover progress keep their own retry policy.

Each wire attempt carries trace context and emits a client span when your application configures OpenTelemetry. Retry diagnostics omit credentials, query strings and response bodies.

Pick an ID before the first attempt

OperationID that you chooseResult of a retry
Publish a Catalog committransactionId (UUIDv7) and idempotencyKeyThe saved publication
Create, rename, or delete a branch, or create a tagidempotencyKeyThe saved result
Publish a Catalog operationoperation.operationIdThe saved operation result
Create a Location versionversionId (UUIDv7)The existing version, if the content matches
Register a Table versionrootObjectId (UUIDv7) of the uploaded snapshotThe registered version
Create a TaskidempotencyKeyThe saved Task
Run a task trigger nowrequestId (UUID)The saved invocation

An idempotencyKey is an opaque string of 1 to 128 bytes. The Catalog and the Task service scope it to the Project, and the Catalog also scopes it to the caller. Generate a UUIDv7 for each new ID: it sorts by creation time, and the Catalog requires that form for transaction IDs.

Reusing an ID for something else

An ID stands for one intent. If you reuse it for a different request, the service rejects the request with status 409:

ServiceError
CatalogCatalog:IdempotencyKeyReused, or Catalog:OperationInputMismatch for an operation ID
LocationsA conflict kind body
TasksA conflict kind body

Create a new ID for each new intent, and keep the old one for retries of the old intent.

Finding out whether a commit happened

A Catalog commit can succeed after the connection fails. To learn the outcome, recover it by its transaction ID:

curl -X POST "https://api.spiraldb.dev/api/catalog/projects/$PROJECT_ID/publications/recover" \
  -H "Authorization: Bearer $SPIRAL_TOKEN" \
  -H "Content-Type: application/json" \
  -d "{\"transactionId\": \"$TRANSACTION_ID\"}"

The response is either published, with the commit ID and its parents, or notPublished. notPublished means the commit had not happened when you asked; a request that is still in flight can complete afterwards. So do not treat it as a failure. To finish the commit, send it again with the same transactionId and idempotencyKey; if the first attempt did land, you get its result back.

For a Catalog operation, read its saved result with GET /api/catalog/projects/{project_id}/operations/{operation_id}. A result exists only once the operation has completed, and it always keeps the first saved result.

Writes through the data plane

A write through the data plane has two steps after the data is uploaded: the new Table version is registered, and then it is committed. If the outcome of either step is unknown, retry that step with the same IDs. Do not upload the data again; it is already in storage and immutable. The Rust SDK follows these rules for you, and lets you save a commit request and resume it after a restart.

Conflicts are not retries

A conflict is a different outcome from a lost response. When a Catalog commit returns a conflict, another commit changed the branch first, and the service does not rebase your change for you. Read the new state, decide what to write, and commit with a new transaction ID and a new idempotency key. See Commits and Branches.

Found a problem? Edit this page or report an issue.

On this page