StoreBay Developers

Conventions

The response envelope, cursor pagination, errors, idempotency, rate limits, and versioning.

The response envelope

Every response is JSON with one of exactly two shapes. Success carries a data payload plus optional meta; error carries a single error object (never both).

{ "data": { "…": "the resource, or an array of resources" }, "meta": { "…": "pagination etc." } }
{ "error": { "code": "validation_error", "message": "Human-readable summary.", "details": [] } }
  • Money is always an integer in minor units (*_minor, e.g. pence for GBP) paired with an ISO-4217 currency — never a float. 1050 + "GBP" means £10.50.
  • IDs are UUIDv7 strings; timestamps are RFC 3339 UTC.
  • Branch on the machine-stable error.code, never on message.

Cursor pagination

List endpoints use an opaque cursor, not an offset. Request with limit (default 25, max 100) and pass the meta.cursor.next token back verbatim for the next page. When meta.cursor.has_more is false, you have reached the end. Do not construct or parse a cursor.

Error code taxonomy

The code is the contract (HTTP status is aligned but secondary). It is stable and additive.

codeHTTPRetry?
invalid_request400No
unauthorized401No
forbidden / insufficient_scope403No
not_found404No
conflict / idempotency_conflict409Sometimes
validation_error422No — fix the fields
rate_limited429Yes, after Retry-After
internal_error / service_unavailable500 / 503Yes — backoff

Idempotency

Every mutating endpoint accepts an Idempotency-Key header. The first request's response is captured; any repeat with the same key replays the stored response byte-for-byte (the operation runs at most once). Reusing a key with a different body returns 409 idempotency_conflict. Use a fresh UUID per logical operation.

Rate limits

Each credential is metered by a per-key token bucket. Every response carries X-RateLimit-Limit / -Remaining / -Reset; a 429 includes Retry-After. Back off on 429, cache reference data, and prefer webhooks over polling.

Versioning and deprecation

The URL carries the major version — /v1. Additive, backward-compatible changes (a new endpoint, a new optional field, a new enum value, a new webhook event type, a new error code) ship in place under /v1 with no notice required — build clients to tolerate unknown fields and enum values; a client that hard-fails on one it doesn't recognise is the bug. A breaking change (removing or renaming a field, changing a type or default behaviour, tightening validation) only ever ships as a new major version, /v2, introduced alongside a still-serving /v1 — never in place.

A deprecated endpoint or version returns machine-readable headers ahead of an announced sunset date — Deprecation (RFC 9745), Sunset (RFC 8594), and a Link: rel="deprecation" to its migration guide — alongside a changelog entry. After the Sunset date the endpoint returns 410 gone.

Webhook payloads version independently of /v1, via each endpoint's own pinned api_version — see Webhooks.

This guide mirrors the canonical source in docs/api/conventions.md and docs/api/lifecycle.md.

On this page