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

# Idempotency and callbacks

> Retry a submit safely with an Idempotency-Key, and get a signed callback when a job starts or finishes.

## Idempotency-Key

A request that times out may still have started a job. Send an `Idempotency-Key` header with `POST /jobs` (or `POST /jobs/{id}/rerun`), and sending the same request again never starts a second, billed machine.

```bash theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
curl -X POST https://www.runcrate.ai/api/v1/jobs \
  -H "Authorization: Bearer $RUNCRATE_API_KEY" \
  -H "Idempotency-Key: nightly-eval-2026-10-20" \
  -H "Content-Type: application/json" \
  -d '{ "command": "python eval.py", "gpu_type": "A100-80GB", "max_runtime_minutes": 60 }'
```

* A key is unique per environment for 24 hours. Use any string up to 255 visible characters, such as a UUID per logical request.
* The same key with the same body answers the original job (or group) as it is now, with `200` and the header `Idempotent-Replayed: true`.
* The same key with a different body is a `409` with `details.reason: "idempotency_conflict"`.
* A request still being handled answers `409` with `details.reason: "idempotency_in_progress"`: retry a few seconds later.
* A request that was refused before any job was created (a validation error, the budget) does not use up its key.

Clients that cannot set headers send `idempotency_key` in the body. The MCP server does this for you: `run_job` and `rerun_job` derive a key from each tool call and its session, so a call the agent's client retries is not run twice, while the same call in a new session starts a new job.

## Per-job callbacks

Give a job a `webhook_url` and Runcrate POSTs to it when the job finishes, without setting up a workspace webhook.

```json theme={"theme":{"light":"github-light","dark":"catppuccin-mocha"}}
{
  "command": "python train.py",
  "gpu_type": "H100",
  "max_runtime_minutes": 600,
  "webhook_url": "https://ci.example.com/hooks/runcrate",
  "webhook_events": ["started", "finished"]
}
```

* `webhook_events` is `started`, `finished` or both (default `["finished"]`). A retried job gets `started` once per attempt and `finished` once.
* The body is the same event as the workspace's [`job.started` and `job.finished`](/docs/webhooks/events#jobs), with the same retries (up to 8 attempts over about 4 hours).
* It is signed with the `Runcrate-Signature` header the same way as [workspace webhooks](/docs/webhooks/verifying-signatures). When the workspace has a webhook endpoint, its secret signs the callback. Otherwise the job gets a secret of its own, returned once as `webhook_secret` in the answer to the request that created it: keep it then.
* The URL must use https and point to a public address. The job shows it as its host and the end of its path.
* Deliveries to a job's callback are not in the workspace's webhook delivery log.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.