Skip to content

API reference

Platform API conventions: base URL, authentication, Idempotency-Key, pagination, limits, headers, versioning, errors, and the PII policy.

Updated 2 Sept 2026
Base URL
https://dukkan.one/platform-api/v1
Version
1.0.0
On this page

This page collects the conventions that apply to every Platform API operation; the resource pages that follow are generated from the same OpenAPI specification the server runs. Read it once, then come back for any number.

Base URL#

Text
https://dukkan.one/platform-api/v1

The API is served on the main host only; storefront subdomains and custom domains return 404. Every response is application/json with Cache-Control: no-store.

Authentication#

HTTP
Authorization: Bearer YOUR_ACCESS_TOKEN

An app access token from Authorization, bound to one store and one install. Every operation requires a specific effective scope listed in the scope reference; a missing one returns 403. A missing, expired or revoked token returns 401, as does the granting member losing access to the store.

Idempotency-Key#

Every operation that creates or changes store data (PATCH /orders/{id}/status, POST /orders/{id}/fulfillments, PATCH /orders/{id}/fulfillments/{fulfillmentId}, POST /orders/{id}/refunds, POST /inventory/movements) requires the header:

HTTP
Idempotency-Key: booking-6f1d2c3b-1
  • A string of 1 to 200 characters, unique per install. Omitting it returns 400.
  • Repeating the request with the same key and the same payload returns the original response and status with the header Idempotency-Replayed: true.
  • The same key with a different payload returns 409 with the message Idempotency key was already used for a different request; a request still in progress also returns 409.
  • Recorded responses are kept for 24 hours.

PUT /webhooks and DELETE /webhooks are idempotent by nature and do not take the key.

Pagination#

Lists return { "data": [...], "next_cursor": "..." }:

Parameter Rule
limit 1–100, default 50
cursor opaque, up to 512 characters, from the previous next_cursor
next_cursor null at the end of the list

The cursor keys on the immutable created_at, so it stays stable while data changes. For incremental sync use the created_at_min and updated_at_min filters (orders) and updated_at_min (products), and keep the last timestamp you processed.

Rate limits#

Limit Value
Requests per install 180 per minute
Writes per install a separate, lower budget
Request body size 64 KB; larger returns 413
Invalid tokens per IP address a separate budget against guessing

Exceeding a budget returns 429 with code rate_limited and a Retry-After header in seconds. Wait for it, then retry; never retry earlier.

Headers#

Header Direction Meaning
Authorization request Bearer + the access token
Idempotency-Key request the safe-write key
X-Request-Id response a unique id per request; include it in any support report
X-Dukkan-Api-Version response the current dated revision of v1, today 2026-09-09
Idempotency-Replayed response true when the response is a replay of a recorded result
Retry-After response seconds until the budget resets, with 429

Versioning#

  • v1 in the path is the contract: no field is ever removed or retyped within it.
  • Additive changes (a new field, a new topic, a new enum value) ship as dated revisions that appear in X-Dukkan-Api-Version and are recorded in the changelog.
  • Webhook payloads carry the same revision in their header and api_version: "v1" in the envelope.
  • Ignore fields you do not know, and never depend on field order.

Errors#

Every error carries the same envelope:

error.jsonJSON
{
  "error": {
    "code": "conflict",
    "message": "Order cannot be fulfilled in its current status",
    "request_id": "7d0c2b1a-5e4f-4a3b-8c9d-0e1f2a3b4c5d"
  }
}
Status code When
400 invalid_request an invalid body or parameter, a missing Idempotency-Key, or a malformed cursor
401 unauthorized a missing, invalid, expired or revoked token
403 forbidden the required effective scope is missing
404 not_found the resource does not exist in the token-bound store
409 conflict a refused state transition, an inventory or refund conflict, or an Idempotency-Key conflict
413 payload_too_large a body over 64 KB
429 rate_limited the budget is exhausted; see Retry-After
5xx internal_error an internal error; the message is redacted, and request_id is what you report

message is for humans and may change; branch on code and the status only. details is an optional object with validation specifics.

PII policy#

The API is customer-data free by default. The customer and shipping_address fields appear in GET /orders/{id} only when the install holds clients:read (gated by the data processing agreement), never in lists and never in any webhook. Every call is recorded in the merchant's audit trail with its status and request_id. See the scope reference and Data processing.

Money#

Every amount is a { amount_minor, currency, decimals } object. Read decimals from each amount; never use an ISO table. See Money in minor units.

Resources