Idempotency
Retry a create safely after a timeout or a network error.
Every POST that creates something accepts an Idempotency-Key header: 8 to 255 characters, ideally a UUID v4. Send one on every create, and send the same key when you retry. A retried create then never makes a second resource.
curl -X POST https://api.vojo-media.com/v1/screens \
-H "Authorization: Bearer $VOJO_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 6f1c2b9e-4d3a-4e8b-9f2a-1c7d5e3b8a40" \
-d '{ "name": "Lobby North", "size_inches": 55 }'const { data, error } = await api.POST("/v1/screens", {
params: { header: { "Idempotency-Key": "6f1c2b9e-4d3a-4e8b-9f2a-1c7d5e3b8a40" } },
body: { name: "Lobby North", size_inches: 55 },
});What a retry returns
| You send | You get |
|---|---|
| The same key and body, within 24 hours | The stored response (same status, same id) with Idempotent-Replayed: true. Nothing new is created. |
| The same key with a different body, or on a different endpoint | 409 idempotency_key_reused. Use a new key for a new request. |
| The same key while the first request is still running | 409 idempotency_request_in_progress. Wait, then retry with the same key. |
If your connection drops during a create, the request carries on. Retry with the same key: you'll get idempotency_request_in_progress until it finishes, then its stored response.
Which responses are stored
- A
5xxisn't stored, so retrying with the same key runs the request again. - A
409or429isn't stored either. - Other
4xxresponses, such as a422, are stored and replayed. Fix the request and use a new key.
Keys belong to your secret key and organisation, so another integration using the same value never sees your response.
Replays show the past
A replay returns exactly what the original request returned, even if the resource has changed since. To see the current state, GET the resource.
A replayed POST /v1/webhook-endpoints returns the original signing secret, even if you've rolled the secret or deleted the endpoint since.
POST /v1/wrappers is the exception. Its replay returns the wrapper as GET /v1/wrappers/{wrapper_id} shows it, with the same id and nothing re-wrapped. For delivery_mode: "inline" that means html is null, because the inline document embeds the whole creative and Vojo doesn't keep it. If you need the file on a retry, use delivery_mode: "archive", whose url is replayed, or wrap again with a new key. If the wrapper has been deleted since, the replay returns 404.