Skip to content

Idempotency

Outcome
Retry document or expense-payment creation without creating a duplicate.
Prerequisites
A stable business-operation identifier.
You'll use
X-Request-Id in REST or request_id in the JavaScript SDK.

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

  1. Send a unique ID for the business operation using X-Request-Id.
  2. The first request is reserved and processed.
  3. 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.
  4. A concurrent duplicate returns 409 request_in_progress; retry it shortly with the same body and request ID.
  5. 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:

Idempotency with cURLbash
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:

Idempotency with the JavaScript SDKtypescript
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

ResultWhat to do
Connection failure or timeoutRetry with the same body and request ID.
409 request_in_progressWait for Retry-After, then retry with the same body and request ID.
409 idempotency_key_mismatchDo 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 replayedTreat it as the original result. If you intentionally change the request after a validation error, use a new request ID.
503 idempotency_unavailableThe 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 passedReconcile 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-invoice
  • refund_456:create-credit-note
  • expense_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}/payments is the documented payment exception.