Idempotency
Concept
If the integrator’s system retries a POST /facturas after a network timeout, without idempotency you could generate two documents for the same operation, which is a serious fiscal failure.
Idempotency guarantees that a POST with the same Idempotency-Key produces the same result, no matter how many times it’s retried.
Usage
Send a unique UUID per business operation in the header:
Idempotency-Key: 4f1c8a8a-2c4f-4b5e-9c2a-1234567890abBehavior
| Scenario | Response |
|---|---|
| First time with that key | Processes normally. Caches the response for 24 h. |
| Retry with same key + same body | Returns the cached response (same status + body). Not re-issued. |
| Retry with same key + different body | 409 Conflict |
| After 24 h | Cache expires. A POST with that key is processed normally again. |
Rules
The key must be generated client-side, BEFORE the first attempt. The same key must be used on all retries of the same business attempt. One key = one business operation. It must not be reused across different documents. Maximum length: 100 characters. Recommended format: UUID v4.
Correct pattern
async function emitir(facturaData, maxRetries = 3) {
const idempotencyKey = crypto.randomUUID(); // ← once, outside the loop
for (let intento = 0; intento < maxRetries; intento++) {
try {
return await axios.post('/api/v1/facturas', facturaData, {
headers: {
'X-Api-Key': process.env.LXS_API_KEY,
'Idempotency-Key': idempotencyKey // ← same value on every retry
},
timeout: 30000
});
} catch (err) {
if (err.response?.status >= 500 || err.code === 'ECONNABORTED') {
await sleep(2000 * (intento + 1));
continue;
}
throw err;
}
}
throw new Error('Max retries excedidos');
}Incorrect pattern
// WRONG: we generate the key inside the loop → each retry creates a different invoice
for (let i = 0; i < 3; i++) {
await axios.post('/api/v1/facturas', data, {
headers: { 'Idempotency-Key': crypto.randomUUID() } // ← bug!
});
}