Prebit Developer Docs

Errors, Pagination & Idempotency

The conventions every Partner API v1 endpoint follows.

Error envelope

Every error response is JSON with a matching HTTP status:

{ "error": "This installation was not granted the \"write_products\" scope" }

Some routes add extra fields on top:

{ "error": "Manifest validation failed", "issues": ["version: version must be semver (x.y.z)"] }
{ "error": "Review scan rejected this submission", "issues": [...], "warnings": [...] }

Standard status codes

StatusMeaning
400Malformed request (bad JSON, invalid id, missing required field)
401Missing/invalid/expired bearer token
403Missing scope/capability, or installation not active
404Resource not found (or store not found — tenancy is derived from your token, never the request)
409Idempotency-Key reused with a different request body
422Semantically invalid (e.g. a plan limit reached, an Extension Storage quota exceeded)
429Rate limit exceeded

Pagination

Every list endpoint uses cursor pagination:

GET /platform/v1/products?cursor=<lastId>&limit=<n>
  • limit — default 50, max 200.
  • Response shape: { "<resource>": [...], "nextCursor": "<id>" | null }.
  • The cursor is simply the last row's own id (ascending sort) — not an opaque token. There's no promise of cursor opacity as part of the contract.
let cursor: string | undefined;
do {
  const { products, nextCursor } = await client.listProducts(cursor, 200);
  // ...process products
  cursor = nextCursor ?? undefined;
} while (cursor);

Idempotency

Every write (POST/PATCH/DELETE) requires an Idempotency-Key header.

await client.createProduct({ name: "Mug", price: 499 }, crypto.randomUUID());
curl -X POST .../products -H "Idempotency-Key: 6b1f2b1a-..." -d '{...}'

Behavior:

  • Fresh key → runs the handler, stores the result.
  • Repeated key, same request body → replays the stored response, doesn't re-run the handler. Safe to retry a dropped connection.
  • Repeated key, different request body → rejected (400; the header being absent entirely is a separate missing_key error). A body mismatch on an existing key is a client bug, not a legitimate retry.
  • Records expire after 24 hours.

Generate one key per logical operation, not one per call

The whole point of the header is that a retry of the same operation reuses the same key. If you generate a fresh UUID on every call (including retries), you get zero idempotency protection — you've just added an unused header. Generate the key once per logical write, and reuse it across your own retry attempts of that exact write.

Rate limiting

A flat 100 requests / 60-second rolling window per installation. Exceeding it returns 429. There is no X-RateLimit-* response header today — build your own backoff-on-429 logic rather than trying to pre-empt the limit from a quota header that doesn't exist.

On this page