MarketplaceHub Docs Docs

Conventions

Error codes and problem+json, rate limits and Retry-After, idempotency keys, paging, and incremental polling — the rules that hold across every endpoint.

These rules hold across every endpoint. Reading this page once will save you reading the same paragraph on each of the others.

The rules that hold across every endpoint: branch on the stable error code rather than its prose, respect Retry-After on a 429, send an idempotency key on writes, and page or poll incrementally rather than refetching FOUR RULES THAT HOLD ON EVERY ENDPOINT Branch on code, not on the words Failures return a real HTTP status and a problem+json body. "code": "validation-failed", "status": 400 "requestId": "6a730dcb444667a4a2a03fc5" code is stable; title and detail are prose. A burst of 10, refilling at 1 per second Per endpoint, and shared across all our servers — a second machine does not buy a second allowance. Over it: 429 plus Retry-After. Wait that long; never loop. Idempotency on writes Send an Idempotency-Key and a retry after a timeout cannot create the same thing twice — you get the first result back. The same key with a different body is a 409 conflict Which is the point: the retry was not the request you thought. Page through, then poll forward List endpoints page rather than returning everything, so read until a page comes back short. For changes since last time, poll on a timestamp rather than refetching the catalogue — cost follows what moved.
Four rules that hold everywhere, so each endpoint page does not repeat them.

Errors

Failures come back with a real HTTP status and an application/problem+json body (RFC 9457). Branch on code, which is stable, rather than on the prose in title or detail.

{
  "type": "urn:marketplacehub:error:validation-failed",
  "title": "The request is not valid",
  "status": 400,
  "code": "validation-failed",
  "requestId": "6a730dcb444667a4a2a03fc5",
  "errors": { "OrderItems[0].OrderItemId": ["Not a valid MarketplaceHub id."] }
}
CodeStatusMeans
validation-failed400Something about the request is wrong. errors names the fields.
unauthenticated401No usable token, or the token names no resolvable account.
insufficient-scope403Valid credential, but it was not granted this permission.
not-found404No such route, or no such record under one that exists.
method-not-allowed405The route exists but not for that HTTP method.
conflict409The request conflicts with current state — see idempotency below.
unsupported-media-type415The body was not JSON.
quota-exceeded429You are over your request allowance.
internal-error500Something failed on our side.

requestId identifies the row for that call in API → Logs in the app. It is on successful responses too. If you ever need to ask us about a call, that is the number to quote.

We deliberately do not return exception text. A 500 gives you a requestId and nothing else — the detail is in our logs, where it cannot leak our internals to you or yours to anybody else.

Rate limits

Each endpoint has its own allowance: a burst of 10 requests that refills at 1 per second. Being quiet for ten seconds buys you a full burst again.

Over the limit you get 429 with a Retry-After header in whole seconds. Wait that long — do not retry immediately, and do not retry in a tight loop. The limit is shared across all our servers, so a second machine will not get you a second allowance.

You can see the current state of your allowance in API → Request Throttling in the app.

Idempotency

Every write endpoint accepts an Idempotency-Key header. Use it. A timeout does not tell you whether the work happened, and without a key a retry can confirm a shipment twice or refund a buyer twice.

Idempotency-Key: 8f14e45f-ea6d-4d1b-9c1e-3f0f6d5b7a21

Rules:

  • Same key, same request → you get the original response back, byte for byte, including its requestId. The work is not repeated. The response carries Idempotent-Replay: true.
  • Same key, different request → 409 conflict. A key belongs to one request; reusing it for another is a bug we refuse rather than guess at.
  • Same key while the first attempt is still running → 409 conflict. Retry shortly.
  • Keys are remembered for 24 hours and are scoped to your account.
  • A 500 releases the key, so a genuine retry can proceed.

Use a fresh key per logical operation — a UUID, or your own order reference. Do not reuse one across different payloads.

Paging

Nothing returns an unbounded list. Omit a limit and you get a sensible page, not everything.

Orders page with a cursor: send the NextCursor from each response back as Cursor until HasMore is false. A cursor walks a changing set without repeating or skipping rows, which an offset cannot.

Products and product-type searches page with an offset against TotalCount. Those searches are offset-based underneath, and we would rather tell you that than pretend a cursor is available where it is not.

Asking for more than an endpoint's maximum is refused with 400 rather than quietly reduced — a silently smaller page reads as "that is all there is", and an integration that believes it stops early and loses records.

Polling for changes

Use UpdatedSince on the orders list rather than re-pulling a date range. It is inclusive: pass the largest LastModifiedDate you have seen, and expect to see those boundary orders again. Dropping a repeat by OrderId is cheap; missing an order is not.