# Retries and idempotency

Networks fail. A request can succeed on our side while your client times out. These two tools let you retry without
creating duplicates.

## `external_ref` on create

Send your own id for a service job as `external_ref` on `POST /v1/tickets`. It is unique per organization.

| Situation | Answer |
| --- | --- |
| First request | `201` with the new job. |
| Same `external_ref` again (from any caller in the organization) | `200` with the existing job. Nothing changes. |
| The `external_ref` belongs to a deleted job, or to a job on a board the key cannot open | `409`. |

Find a job by your reference with `GET /v1/tickets?external_ref=crm-48213`. Add `archived=1` to include archived
jobs.

Clients have their own `external_ref`, also unique per organization. Use it to link a client to your system and to
[open jobs for that client](/guides/service-jobs/#identify-the-client).

## `Idempotency-Key` on job actions

`POST /v1/tickets/{id}/complete`, `POST /v1/tickets/{id}/create-invoice` and `POST /v1/tickets/{id}/cancel` accept
an optional `Idempotency-Key` header. Use a new random value (for example a UUID) per action, and reuse it only to
retry that same action.

```bash
curl -X POST https://api.invoicing.proradius.co/v1/tickets/9a7c3e1f-2b4d-4e6f-8a1c-3d5e7f9b1c2d/complete \
  -H "Authorization: Bearer $PRORADIUS_API_KEY" \
  -H "Idempotency-Key: 6f1d2c3b-7a8e-4f90-a1b2-c3d4e5f60718" \
  -H "Content-Type: application/json" \
  -d '{ "outcome": "done" }'
```

- A successful (`200`) answer is stored for 24 hours, per organization and key.
- A retry with the same key within that time gets the stored answer and the header `Idempotent-Replayed: true`.
- A retry while the first request is still running answers `409`.
- Error answers are not stored, so you can fix the request and send it again with the same key.

Even without the header, completing an already finished job does not bill twice. It answers `200` with
`completion.replayed: true` and the same invoice.

## Webhooks

Webhook deliveries can arrive more than once. Each event has an `id` that stays the same across retries. Store the
ids you have processed and skip repeats. See [Webhooks](/guides/webhooks/#retries).