Concepts
Idempotency
Networks fail. When a request times out you cannot know whether it was carried out — and a retried call request must never ring someone twice. An Idempotency-Key makes the retry safe.
How it works
Send a unique string in the Idempotency-Key header — a UUID is ideal. The first request with that key does the work and Voximate stores its answer for 24 hours. Any later request with the same key gets that same stored answer back, with the header Idempotent-Replayed: true, and nothing is done again.
Which endpoints
The endpoints that create something accept the header:
- POST /agents — Create an agent
- POST /calls — Start an outbound call
- POST /webhooks — Create a webhook endpoint
GET and DELETE are already safe to repeat. PATCH sets fields to the values you send, so repeating it gives the same result.
Example
Create the key once, before the first attempt, and reuse it for every retry of that same operation.
Start a call, safely
const key = crypto.randomUUID(); // one key per operation, not per attempt
async function startCall(attempt = 0) {
try {
const res = await fetch('https://api.voximate.no/public/v1/calls', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.VOXIMATE_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': key,
},
body: JSON.stringify({ agent_id: '7c3f1e90-5a44-4b21-9c07-2ed5b8f41a66', to: '+4712345678' }),
});
if ((res.status === 429 || res.status >= 500) && attempt < 4) throw new Error(`HTTP ${res.status}`);
return await res.json();
} catch (err) {
if (attempt >= 4) throw err;
await new Promise((r) => setTimeout(r, 500 * 2 ** attempt));
return startCall(attempt + 1); // same key: at most one call is placed
}
}Rules
| Situation | What you get back |
|---|---|
| Same key, same request, first one finished | The first answer again, with Idempotent-Replayed: true. |
| Same key, different request body or endpoint | 409 idempotency_error, code idempotency_key_reused. |
| Same key while the first request is still running | 409 idempotency_error, code idempotency_request_in_progress. Retry shortly. |
| The first request was refused with a 429, or for a reason that can change (call_rejected because a trial line is busy) | Nothing is stored. Wait (Retry-After says how long) and retry with the same key. |
| The first request failed with a 5xx | Nothing is stored, so a retry with the same key runs again. |
| POST /calls answered 503 call_outcome_unknown | Stored. The call may be ringing, so the same key never dials again. Look for the call with GET /calls; to try again, use a new key. |
| More than 24 hours later | The key has expired and the request runs as new. |
- Keys are per workspace and at most 255 characters.
- Validation errors (4xx other than 429) are stored like successes, so fix the request and use a new key.
- A replayed
POST /webhooksanswer hassecret: null. The signing secret is kept only in the vault, never with the stored answer; if you lost it, rotate it.
Derive the key from your own data when you can — for example
call-{crm_contact_id}-{campaign_id} — so even a restarted job cannot call the same person twice for the same reason.