Browse the docs

Operate

Errors, retries and limits

The error format, idempotency keys, pagination and the rate limit.

The error format

Every failure has the same shape, whatever went wrong:

JSON
{
  "error": {
    "code": "order_not_in_required_state",
    "message": "Order is ACCEPTED; this needs it to be PENDING."
  }
}

code is stable and meant for your program — branch on it. message is for people and may be reworded; log it, show it, never parse it. An invalid_request error also carries details, the problem with each field:

JSON
{
  "error": {
    "code": "invalid_request",
    "message": "Some fields are invalid.",
    "details": { "items": ["At most 1000 items per push; split larger catalogues."] }
  }
}

Every response — success or failure — has a Request-Id header (req_…). Include it when you write to us; it finds the exact request in our logs, and in your dashboard's request log.

Status codes and common codes

Status Codes you will meet What to do
400 invalid_request, invalid_connection_code, invalid_timestamp, invalid_cursor, invalid_limit, invalid_webhook_url, fulfilment_unavailable, unknown_barcode Fix the request. Retrying unchanged will fail again.
401 invalid_api_key, authentication_required Wrong, revoked or missing key — or a key for the other environment.
403 forbidden, not_allowed_for_store, key_scope_insufficient Your company is not approved for live or is suspended; the store has not allowed this action; or a store-scoped key tried something only a company-wide key may do — claim a code, manage webhook endpoints, retry a delivery.
404 store_not_found, order_not_found, webhook_endpoint_not_found, sync_not_found A store that is not connected to you is a 404 too, deliberately.
409 order_not_in_required_state, insufficient_stock, request_in_progress, courier_already_booked, not_courier_order The thing changed under you. Fetch it again and decide.
422 idempotency_key_reused You sent a different request with an Idempotency-Key already used. A bug on your side.
429 rate_limited Wait for Retry-After seconds.
5xx — Our fault. Retry with backoff, using the same Idempotency-Key.

Idempotency

Networks fail half-way: your request reached us and was carried out, but the answer never reached you. Retry blindly and you might accept an order twice, or apply a stock adjustment twice.

Claiming a store, accepting, rejecting, changing the delivery method, pushing or adjusting stock, acknowledging sales and creating a webhook endpoint all accept an Idempotency-Key header — the reference marks each one. Send a fresh unique value (a UUID) with each logical action, and the same value when you retry it:

Shell
curl -X POST https://api.scanimart.com/v1/stores/1042/orders/ORDER-1A2B3C4D5E/accept \
  -H "Authorization: Bearer $SCANIMART_KEY" \
  -H "Idempotency-Key: 6f1e2a8c-1d9b-4c3e-9a51-2f8e7b6d0c44" \
  -H "Content-Type: application/json" \
  -d '{"fulfilment_mode": "STORE_STAFF"}'
  • A retry with the same key and the same body gets the first answer back, with the header Idempotent-Replayed: true. Nothing happens twice.
  • The same key with a different body is refused with 422 idempotency_key_reused.
  • If the first request is still running, a retry gets 409 request_in_progress; wait a moment and retry again.
  • A 5xx answer is not remembered, so a retry runs the action for real.
  • Nor is 403 key_scope_insufficient: it is refused before anything runs, so retry with a company-wide key and the same Idempotency-Key.
  • Keys are kept for 24 hours, per environment.

Generate the key when the action is decided — when the cashier presses Accept — and store it with the action, so a retry after your process restarts still uses it.

Pagination

List endpoints return a page and a cursor:

JSON
{
  "object": "list",
  "data": [ "…" ],
  "has_more": true,
  "next_cursor": "ZTo0MjA5"
}

Pass next_cursor back as cursor for the next page, until has_more is false. limit sets the page size, 1–100 (default 50). Cursors are opaque: do not build or edit them.

Timestamps and money

  • Timestamps are ISO 8601 with an offset, e.g. 2026-10-08T10:15:00+05:30. Send them the same way; a timestamp without an offset is read as India time.
  • Amounts are decimal strings in rupees — "249.00", never a float. Parse them with a decimal type.

Rate limit

Each key may make 600 requests a minute. Over that you get 429 rate_limited with a Retry-After header in seconds. A busy store's traffic — a few orders a minute, a stock push every few minutes — is far below it; if you need more for a bulk job, write to us.

Versioning

This is API version 2026-10-01, shown in every event's api_version. Within a version we only add: new endpoints, new optional request fields, new response fields, new event types. Build your client to ignore fields and event types it does not know. A change that would break a working integration comes as a new version, announced with time to move.