Idempotency
Use idempotency keys to safely retry POST requests without creating duplicate resources. This is critical for reliable integrations.
How It Works
Include an Idempotency-Key header with a unique value (UUID v4 recommended) on POST requests. If the same key is sent again within 24 hours, the API returns the original response instead of processing the request a second time.
This means you can safely retry failed requests due to network timeouts or connection errors without worrying about duplicate side effects.
Generating Idempotency Keys
Use a UUID v4 for each unique operation. Do not reuse keys across different operations — each distinct action should have its own key.
Generate a UUID v4
import { randomUUID } from 'crypto';
const idempotencyKey = randomUUID();
// e.g., '550e8400-e29b-41d4-a716-446655440000'Supported Endpoints
The following POST endpoints support idempotency keys:
| Endpoint | Description |
|---|---|
| POST /link/token/create | Create a new link token for the Connect flow. |
| POST /bills/sync/trigger | Trigger a bill sync for a connected account. |
| POST /request-to-link/create | Create a biller-initiated link request. |
Full Example
Here is a complete example showing how to use an idempotency key with retry logic:
Idempotent request with retry
import { randomUUID } from 'crypto';
async function createLinkToken(userId, billerId) {
// Generate key once — reuse on retries for the SAME operation
const idempotencyKey = randomUUID();
for (let attempt = 0; attempt < 3; attempt++) {
try {
const response = await fetch(
'https://sandbox.api.billerapi.com/link/token/create',
{
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Client-ID': process.env.BILLERAPI_CLIENT_ID,
'X-Client-Secret': process.env.BILLERAPI_CLIENT_SECRET,
'Idempotency-Key': idempotencyKey,
},
body: JSON.stringify({
client_id: process.env.BILLERAPI_CLIENT_ID,
client_user_id: userId,
biller_id: billerId,
consents: ['bills'],
}),
}
);
if (response.ok) {
return await response.json();
}
// Don't retry client errors (except 429)
if (response.status < 500 && response.status !== 429) {
throw new Error(`Client error: ${response.status}`);
}
} catch (err) {
if (attempt === 2) throw err;
}
// Exponential backoff before retry
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
}
}Important Notes
- Idempotency keys expire after 24 hours. After that, the same key will be treated as a new request.
- Use the same key for retries of the same operation. Generate a new key for each distinct operation.
- If you send the same key with a different request body, the API returns a
409 Conflicterror. - GET, PUT, and DELETE requests are naturally idempotent and do not require the header.
Related
- Error Handling — error response format and retry behavior
- Rate Limits — rate limits and backoff strategies
- API Reference: Link Sessions — link token creation endpoint