# Idempotency

Retry a write safely. Send the same Idempotency-Key, and you get the first answer again instead of a second write.

A network can lose an answer after the write happened. Without a key, your retry starts a second run or makes a second workflow.

## Send a key

Choose a key that names this one attempt, such as an order number. Send it again only when you retry the same request.

```bash
curl -X POST "$GORUNNER/api/workflows/$ID/run?environment=production" \
  -H "Authorization: Bearer $GORUNNER_TOKEN" \
  -H "Idempotency-Key: order-A-1001" \
  -H "Content-Type: application/json" \
  -d '{"name": "Ada"}'
```

```text
HTTP/1.1 202 Accepted
{"data": {"id": "61af1b4f-…", "status": "pending", …}}

The same call again:
HTTP/1.1 202 Accepted
Idempotency-Replayed: true
{"data": {"id": "61af1b4f-…", "status": "pending", …}}
```

The retry gets the first answer, marked `Idempotency-Replayed: true`. Only one run was made.

## Calls that take a key

- **[Create a workflow](https://docs.gorunner.io/api/workflows/create-workflow)**: `POST /api/workflows`
- **[Import a workflow](https://docs.gorunner.io/api/workflows/import-workflow)**: `POST /api/workflows/import`
- **[Duplicate a workflow](https://docs.gorunner.io/api/workflows/duplicate-workflow)**: `POST /api/workflows/{id}/duplicate`
- **[Get a new webhook address](https://docs.gorunner.io/api/publish/rotate-workflow-webhook-token)**: `POST /api/workflows/{id}/webhook/rotate`
- **[Run a workflow](https://docs.gorunner.io/api/runs/run-workflow)**: `POST /api/workflows/{id}/run`
- **[Run a run again](https://docs.gorunner.io/api/runs/rerun-run)**: `POST /api/runs/{id}/rerun`
- **[Run many runs again](https://docs.gorunner.io/api/runs/rerun-runs-bulk)**: `POST /api/runs/rerun`
- **[Create a connection](https://docs.gorunner.io/api/connections/create-connection)**: `POST /api/connections`
- **[Make a workflow from a template](https://docs.gorunner.io/api/templates/use-template)**: `POST /api/templates/{id}/use`

Most Partner API `POST`s take one too. These don’t: `/login-codes`, `/accounts/{accountId}/tokens`, `/accounts/{accountId}/connections/oauth2/start`, and `/oauth-requests/{requestId}/complete` and `/deny`. Each answers once, by design. An AI app’s run tools take the same thing as `idempotencyKey`.

A call to a workflow’s webhook takes one too, with its own rules: its keys belong to the workflow, not a token, a failed run’s answer is kept too, and no call is refused for too many keys. See [Retry safely](https://docs.gorunner.io/api/webhooks#retry-safely).

The rest need none. Publishing an unchanged draft gives back the same version. A `PUT` replaces, and can carry `expectedRev`. A `DELETE` is done once.

## The rules

- **The key**: Any text up to 255 characters. Longer is `400 VALIDATION_ERROR`.
- **Whose**: Each token, or each signed-in person, has its own keys. Two tokens can use the same key.
- **How long**: 24 hours after a successful answer. A failed answer isn’t kept, so fix the request and retry with the same key.
- **How many**: At most 10,000 kept at once for one token or person. A new key past that is `429 RATE_LIMITED`, with `Retry-After` until the oldest expires, and nothing is done.
- **A different request**: The same key with another method, path, query or body is `422 VALIDATION_ERROR`. So `environment=test` and `production` are different requests.
- **Still running**: A retry while the first is in progress waits up to a second, then answers `409 CONFLICT`. Retry shortly.
- **A big answer**: An answer over 64 KB isn’t kept. The write still happens once, and a retry answers `409 CONFLICT`: read the result with a get instead.

**What if GoRunner can’t reach where it keeps the keys?**

The call goes through without the key, so a retry could write twice. GoRunner logs it. This happens only while that store is down.

Related: [Errors](https://docs.gorunner.io/api/errors) · [Run a workflow](https://docs.gorunner.io/api/runs/run-workflow) · [Turn your workflows into tools](https://docs.gorunner.io/mcp/workflows-as-tools)

Source: https://docs.gorunner.io/api/idempotency
