Skip to content

Idempotency

AmpNexus APIs use optional idempotency keys to make client retries safe for mutating requests. This is especially important for warehouse scanners, payment and billing workflows, carrier labels, device lifecycle changes, firmware rollouts, webhooks, and other endpoints with external or irreversible side effects.

Platform Rule

All mutating backend endpoints should support the optional Idempotency-Key header unless the endpoint is explicitly documented as exempt.

This applies to:

  • POST, PUT, PATCH, and DELETE requests that change durable state.
  • Requests that trigger external side effects, such as payments, invoices, shipment labels, collection bookings, emails, device commands, firmware rollout actions, and audit records.
  • Event, webhook, or scanner ingestion endpoints where callers may retry after an unknown network result.

This usually does not apply to:

  • GET, HEAD, and OPTIONS.
  • Health, readiness, version, and pure lookup endpoints.
  • Streaming or file download endpoints.
  • Multipart uploads, unless the service implements an upload-specific idempotency strategy.
  • Create endpoints where repeating the exact same request must intentionally create another resource. These must document why they are exempt.

Client Contract

Clients MAY send:

Idempotency-Key: 9b5f54df-7f26-4f3f-8e9e-096f7fd6b84a

The key should be stable for one logical operation and reused for retries after timeouts, network drops, 408, 429, or 5xx responses where the client does not know whether the server completed the operation.

Clients that do not send the header keep the existing endpoint behavior.

Recommended key format:

  • Use UUIDv4 or another high-entropy opaque value.
  • Keep keys at or below 200 ASCII characters.
  • Do not include access tokens, API keys, customer PII, or raw device secrets.

Server Contract

For endpoints that support idempotency:

  • Missing Idempotency-Key: execute normally with no idempotency lookup.
  • Same tenant, method, route, key, and request body after a completed success: return the stored successful response without repeating side effects.
  • Same tenant, method, route, and key with a different request body: return 409 Conflict.
  • Same tenant, method, route, and key while the first request is still processing: return 409 Conflict or 425 Too Early.
  • Auth, tenant checks, and permission checks must run before a cached response can be returned.
  • Successful JSON responses are stored. Transient failures should release the key so the caller can retry.
  • Business validation failures may be stored only when the service has a clear reason to preserve that failure response. Otherwise they should release the key and allow a corrected retry.

The route used for idempotency must be the normalized backend route identity, not a user-controlled raw URL. Include path parameters that distinguish the operation, such as an order id or tag id.

Persistence Model

Each service owns its own idempotency table because each service owns its own database and side effects.

Recommended table shape:

CREATE TABLE idempotency_requests (
    id TEXT PRIMARY KEY,
    tenant_id TEXT NOT NULL,
    idempotency_key TEXT NOT NULL,
    method TEXT NOT NULL,
    route_key TEXT NOT NULL,
    request_hash TEXT NOT NULL,
    status TEXT NOT NULL CHECK (status IN ('in_progress', 'completed')),
    response_status INTEGER,
    response_body JSONB,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    completed_at TIMESTAMPTZ,
    expires_at TIMESTAMPTZ NOT NULL,
    UNIQUE (tenant_id, method, route_key, idempotency_key)
);

Use a stable hash of the canonical JSON request body. The hash must not include headers, credentials, signatures, cookies, or other replay material.

Recommended retention:

  • in_progress: short TTL, such as 15 minutes.
  • completed: 24 to 72 hours for interactive clients and scanner workflows.
  • Longer retention only when the external side effect requires it.

Services should delete expired rows opportunistically or through a scheduled cleanup task.

Security Requirements

  • Never use idempotency as authentication.
  • Never return a cached response until the current request has passed the same auth, tenant, and permission checks required by a fresh execution.
  • Scope keys by tenant, method, and normalized route.
  • Reject duplicate Idempotency-Key headers.
  • Avoid logging raw keys unless they are treated as low-sensitivity operational metadata. Do not log request bodies or secrets as part of idempotency traces.
  • Do not store sensitive response bodies for endpoints that return secrets. If the response contains secrets, design an endpoint-specific replay response or mark the endpoint exempt with a security note.

Rollout Order

Prioritize endpoints where duplicate execution causes real-world cost, customer impact, or irreversible state:

  1. Billing, payments, invoices, subscriptions, and credit notes.
  2. Shipment labels, carrier collections, warehouse packing, and dispatch.
  3. Inventory movement, allocation, stock intake, and ownership transfer.
  4. Device lifecycle transitions, adoption, handover, and installer actions.
  5. Firmware rollout creation, release publication, and device command dispatch.
  6. Webhook, event, and scanner ingestion endpoints.
  7. Audit/write endpoints where duplicate records reduce operational clarity.

Implementation Checklist

For each mutating endpoint:

  • Classify it as idempotent, exempt, or already naturally idempotent.
  • Add or reuse service-local idempotency_requests persistence.
  • Run auth and tenant preflight before cache lookup.
  • Compute a stable request hash from the deserialized request.
  • Reserve the key before executing side effects.
  • Store the successful JSON response after the operation commits.
  • Release the key on retryable or transient failures.
  • Return 409 Conflict for same-key/different-body requests.
  • Add tests for missing header, first request, replay, mismatch, in-flight duplicate, auth failure, and retry after transient failure.
  • Document support in OpenAPI or route docs.

Current Implementations

  • billing-api: billing run execution, invoice sync/send, order invoice sync, and suspension case transition endpoints.
  • chargepoint-api: installer/OCPP self-test, charge-test start/stop, and handover-state command endpoints.
  • order-api: scanner stock intake, packing scan, and packing command endpoints.
  • passport-api: Passport tag verify and programmed endpoints.