Idempotency
Idempotency prevents document or expense-payment creation from running twice when a response is lost or ambiguous. Send a stable business-operation ID in X-Request-Id when creating an invoice, estimate, credit note, advance invoice, delivery note, expense, or payment through POST /expenses/{id}/payments. Both calculated and custom document creation endpoints support the same behavior.
How It Works
- Send a unique ID for the business operation using
X-Request-Id. - The first request is reserved and processed.
- For 24 hours, a later request with the same API credential, entity, method, path, body, and request ID replays the original status and response body.
- A concurrent duplicate returns
409 request_in_progress; retry it shortly with the same body and request ID. - After 24 hours, the request ID expires and reuse starts a new operation.
This behavior applies to authenticated API-key document creation and expense-payment creation. Session-authenticated expense workflows also support it where documented. Other mutations do not currently promise idempotent replay merely because they receive an X-Request-Id header.
Usage
Include a unique identifier in the X-Request-Id header:
curl -X POST https://eu.spaceinvoices.com/invoices \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "X-Entity-Id: ent_123" \
-H "X-Request-Id: order-12345:create-invoice" \
-H "Content-Type: application/json" \
-d '{"customer": {"name": "Acme"}, "items": [{"name": "Service", "price": 100}]}'If your request times out and you retry with the same X-Request-Id, you’ll get the original response—no duplicate invoice is created.
Retry within 24 hours of the first request. After that window, the API no longer treats the request ID as an idempotency key, even though the request may still appear in request logs.
The JavaScript SDK uses the same snake_case convention as the REST body. Pass the request ID as request_id in the final method-options argument:
const createInvoice = () =>
sdk.invoices.create(
{
customer: { name: "Acme Corp" },
items: [{ name: "Service", quantity: 1, price: 100 }],
},
{
entity_id: "ent_123",
request_id: "order-12345:create-invoice",
},
);
// Retry with the same body and request_id after a timeout or ambiguous 5xx.
const _invoice = await createInvoice();
const _retry = await createInvoice();Retry Decisions
| Result | What to do |
|---|---|
| Connection failure or timeout | Retry with the same body and request ID. |
409 request_in_progress | Wait for Retry-After, then retry with the same body and request ID. |
409 idempotency_key_mismatch | Do not retry. The ID was already used for a different body; investigate the caller or use the correct operation ID. |
Original 2xx, 4xx, or 5xx response is replayed | Treat it as the original result. If you intentionally change the request after a validation error, use a new request ID. |
503 idempotency_unavailable | The operation was not performed because the request could not be reserved safely. Retry with a new request ID as instructed by the response. |
| More than 24 hours have passed | Reconcile the original operation before retrying. Reuse starts a new operation and can create another document or payment. |
Response Header
Every response includes an X-Request-Id header:
- If you provided one, it is echoed back.
- If you did not, one is generated for observability.
Always provide the ID before the first document-create request. A generated response ID cannot help if the response itself was lost.
Best Practices
Use one stable ID per side effect:
order_123:create-invoicerefund_456:create-credit-noteexpense_789:create-payment
Do not add a changing timestamp when retrying the same operation. A new ID means a new document-creation attempt.
Keep each request ID for at least 24 hours in your system, then reconcile the resulting Space Invoices resource before deciding whether another operation is needed.
Key scope:
- Replay matching includes the account, entity, API credential, HTTP method, and path. The same text on another operation is a separate scope, but operation-specific suffixes make logs easier to understand.
- Request IDs expire for replay 24 hours after the first request. Reusing one after expiry starts a new operation.
- Request-log bodies are retained for 90 days for debugging, but that longer data-retention window does not extend the 24-hour idempotency guarantee. See Request logs.
- Do not rely on request-ID replay for updates, deletes, email delivery, outgoing-document payments, or unrelated resource mutations. Expense-payment creation at
POST /expenses/{id}/paymentsis the documented payment exception.