API ReferenceError Catalog

Error Catalog

The real error envelope, every `code` the Brainerce API returns, and how to recover.

The envelope

Every error response from the Brainerce API has this shape:

{
  "statusCode": 409,
  "code": "IDEMPOTENCY_KEY_REUSED",
  "message": "Human-readable explanation",
  "details": { "retryAfterSeconds": 12 },
  "error": "Conflict",
  "timestamp": "2026-08-23T10:15:00.000Z",
  "path": "/api/v1/orders"
}
FieldAlways present?What it is
statusCodeyesThe HTTP status, repeated in the body
codeyesThe stable, machine-readable code. Switch on this.
messageyesHuman-readable prose. Wording can change at any time — never parse it
detailsnoStructured extras when the error has them (e.g. retryAfterSeconds, limit, items[])
errornoThe generic status name ("Conflict"). Only present on some errors
timestampyesISO-8601 of when the error was generated
pathyesThe request path, query string stripped

Three consequences worth writing down:

  • error is conditional. Code that reads body.error breaks on the errors that omit it. Read code.
  • code is never inside message. They are separate fields; matching on the message text is how integrations break on a copy-edit.
  • A 4xx never says "Internal server error". That string is reserved for 5xx. If you see it, the server really did fail — it is not a validation error in disguise. A 4xx whose call site supplied no message falls back to a generic phrase for its status ("Bad request", "Conflict", …) instead.

Anything outside these seven keys is dropped. The envelope is rebuilt from a fixed key set before the response is sent, so structured per-error context always travels inside details — never as a sibling key. When this documentation says an error "carries items[]", it means details.items.

HTTP status codes

StatusMeaningCommon causes
400Bad RequestValidation error — missing required field, malformed JSON
401UnauthorizedMissing or invalid API key / customer token
402Payment RequiredPlan quota exhausted (orders, AI credits)
403ForbiddenAuthenticated but not allowed (wrong store, missing scope, suspended store)
404Not FoundResource doesn't exist or belongs to a different store
409ConflictState conflict, duplicate unique field, idempotency-key reuse
413Payload Too LargeRequest body exceeds the parser limit
415Unsupported Media TypeWrong Content-Type for the endpoint
422Unprocessable EntityBusiness rule violation
429Too Many RequestsRate limit exceeded — see Rate Limits
500Internal Server ErrorUnexpected error on our side — please report
501Not ImplementedDeprecated stub endpoint that returns no data — use the documented replacement instead
503Service UnavailableBrief outage or maintenance window

Error codes

These are the values the API actually puts in code. Adding a code is non-breaking; removing or renaming one is breaking and goes through the 90-day window in Versioning.

Validation (400)

  • VALIDATION_FAILED — one or more fields failed validation. message lists the failing fields.
  • MISSING_REQUIRED_FIELDS — a required field or header was omitted. Note the plural.
  • STORE_ID_REQUIRED — the route needs a storeId and none was resolvable from the credential or path.
  • STORE_ID_MISMATCH — the storeId in the path is not the one the credential belongs to.
  • INVALID_STOCK_VALUE — a stock quantity was negative or non-numeric.
  • PLATFORMS_REQUIRED, PLATFORM_REQUIRED, RESOLUTION_REQUIRED, TYPE_REQUIRED — sync / conflict-resolution routes missing their discriminating field.
  • DEVICE_AUTH_ALREADY_DECIDED — the device-flow code was already approved or denied, or an earlier approval attempt on it did not finish. Not retryable: start the connection again from the terminal for a fresh code.

Cart and checkout (400)

These five are the codes that carry structured recovery data in details. Read details instead of parsing numbers or product names out of message.

  • CART_LINE_LIMIT_REACHED — the cart already holds the maximum number of distinct product lines (50 today) and the add would have created a 51st. details.maxLines is the limit, so you never have to parse the number out of the sentence. Storefront traffic only — vc_* and storeId callers; an admin API key is uncapped. It is checked only when a NEW line would be created, so a full cart can still have quantities changed and lines removed: render it as "your cart is full, remove something to add this", not a generic failure. Older integrations matched the message text ("A cart can hold at most 50 different items…") because there was no code. That match still works and the wording has not changed, so keep it as a fallback while you are talking to a backend that may predate the code — but switch on code from now on.
  • INSUFFICIENT_STOCK — not enough inventory. Cart add/update and inventory reservation reject a single line and send details.available + details.requested; checkout validates the whole cart and sends details.items[] with productId, variantId, available, requested. Branch on which is present.
  • PRODUCT_UNAVAILABLE — a line's product is unpurchasable (deleted variant, or inventory tracking disabled). Carries details.items[] with productId and variantId when more than one line is involved.
  • PRICE_DRIFT — a cart line's snapshot price no longer matches the live price, so createCheckout refused. details.items[] carries itemId, oldUnitPrice, newUnitPrice, delta and direction per affected line. Recover with POST /cart/{id}/refresh-snapshots or by removing those lines.
  • MODIFIER_VALIDATION_FAILED — a cart payload's selections failed modifier-group validation. details.errors[] lists each issue with its own code, message, modifierGroupId and modifierId. The envelope message is deliberately generic — the actionable detail is in that array. The per-issue codes are listed in Critical Rules → Modifier validation errors.

Note that OUT_OF_STOCK (below) is a different code from INSUFFICIENT_STOCK and is raised by different routes — do not treat them as aliases.

Gift-card refusals carry no code of their own. POST /checkout/{id}/gift-card answers every rejected code — unknown, expired, spent, disabled, wrong currency — with the same 400 / generic BAD_REQUEST and the same message, That gift card code cannot be used on this order. There is no GIFT_CARD_EXPIRED, GIFT_CARD_NOT_FOUND or INSUFFICIENT_BALANCE code, and the sameness is deliberate: a gift-card code is bearer value, so a response that distinguished "no such code" from "that code exists but is refused" would be an oracle for discovering real codes. POST /gift-cards/balance follows the same rule without erroring at all — an unknown, disabled or expired code returns { "balance": "0.00", "usable": false }. Show one message and let the shopper re-enter the code.

Authentication and authorization (401 / 403)

  • UNAUTHORIZED — missing, malformed, revoked or expired credential. This one code covers every credential type; there is no separate INVALID_API_KEY, INVALID_CUSTOMER_TOKEN or MISSING_AUTHORIZATION.
  • SESSION_EXPIRED — a dashboard session expired.
  • INSUFFICIENT_SCOPE — the API key is valid but its scope list does not cover this route. The most common 403 for integrators, and the one STORE_ACCESS_DENIED used to be documented as.
  • INSUFFICIENT_PERMISSION — the acting user lacks the store permission this action needs. Singular.
  • STORE_SUSPENDED — the store is suspended; writes are refused.

Plan and quota (402 / 403)

  • PLAN_LIMIT_REACHED — a resource-count cap was hit (products, channels, …).
  • PLAN_FEATURE_REQUIRED — the store's plan is too low for this feature.
  • PLAN_ORDERS_EXHAUSTED — the plan's order allowance is used up; storefront checkout is blocked.
  • PLAN_AI_CREDITS_EXHAUSTED — monthly or daily AI credits are depleted.

Not found (404)

  • RESOURCE_NOT_FOUND — generic.
  • PRODUCT_NOT_FOUND, ORDER_NOT_FOUND, CUSTOMER_NOT_FOUND, COUPON_NOT_FOUND, CHECKOUT_NOT_FOUND — resource doesn't exist or isn't visible to this credential.
  • IDEMPOTENCY_KEY_NOT_SUPPORTED — you sent Idempotency-Key on a GET. See Idempotency.

Conflict and business rules (409 / 422)

  • CONFLICT — generic state conflict. There is no CONCURRENT_MODIFICATION code.
  • IDEMPOTENCY_KEY_REUSED — the same Idempotency-Key arrived with a different request body.
  • ALREADY_INSTALLED — the app is already installed on this store.
  • COUPON_EXPIRED, COUPON_NOT_ACTIVE, COUPON_USAGE_LIMIT_REACHED, COUPON_PER_CUSTOMER_LIMIT_REACHED — coupon validation failures. There is no COUPON_NOT_APPLICABLE.
  • OUT_OF_STOCK — variant has no available inventory.
  • RESERVATION_EXPIRED — cart reservation timed out; refresh and re-add items.
  • ORDER_INVALID_STATE — the order is not in a state that allows this transition.
  • PAYMENT_ALREADY_CAPTURED — capture was already taken for this payment.
  • CARD_DECLINED — the payment processor rejected the charge. There is no PAYMENT_DECLINED.

A duplicate customer email surfaces as a 409 with CONFLICT or VALIDATION_FAILED depending on the route — there is no DUPLICATE_EMAIL code.

Rate limiting (429)

  • RATE_LIMITED — slow down. Read details.retryAfterSeconds and the Retry-After header. There is no RATE_LIMIT_EXCEEDED.
  • Repeated failed authentication from one IP also returns 429 after 50 failures in 5 minutes — fix the credential rather than retrying. See Rate Limits → Failed-authentication throttling.

Server and provider (5xx)

  • INTERNAL_SERVER_ERROR — unexpected failure on our side.
  • PROVIDER_NOT_READY, PROVIDER_NOT_CONFIGURED — a payment or shipping provider is missing or incompletely configured.
  • PAYMENT_PROCESSING_ERROR, REFUND_PROCESSING_ERROR — the provider call failed.
  • DOMAIN_REGISTRATION_FAILED, DOMAIN_VERIFICATION_FAILED — custom-domain operations.
  • MEDIA_UPLOAD_FAILED, MEDIA_REPLACE_FAILED — media pipeline failures.
  • AI_TOOL_FAILED, AI_PROCESSING_FAILED — AI assistant failures.
  • IDEMPOTENCY_LOCK_UNAVAILABLE (503) — we could not take the once-only lock for your Idempotency-Key, so the request was refused rather than run unguarded. Nothing was processed — on POST /v1/checkout/{checkoutId}/complete, the only route that can return it today, that means no order and no charge. Retry with the same key after a short backoff. ⛔ Do not treat it like IDEMPOTENCY_KEY_REUSED: that code is a 409 telling you to generate a new key, and doing that here sends a second, unprotected attempt at the same payment.
  • SERVICE_TEMPORARILY_UNAVAILABLE (503) — a transient internal dependency was unreachable and the request was refused rather than run without its safety guarantee. Nothing was processed. Retry the identical request after a short backoff; changing the request will not help. Distinct from PROVIDER_NOT_READY, which points at a payment provider — this one does not, and a shopper hitting it on a paid download should simply try again.

Not implemented (501)

  • NOT_IMPLEMENTED — a deprecated stub endpoint. Today that is POST /v1/sync and GET /v1/sync/{jobId}; use the per-resource replacements (e.g. POST /v1/coupons/{id}/sync).

Status-derived fallbacks

Not every error originates from the catalog above. An exception thrown without an explicit code gets one derived from its HTTP status, so these appear on the wire too and belong in a default: branch rather than being treated as unknown:

BAD_REQUEST (400), FORBIDDEN (403), NOT_FOUND (404), PAYLOAD_TOO_LARGE (413), UNSUPPORTED_MEDIA_TYPE (415), UNPROCESSABLE_ENTITY (422), and ERROR for anything else.

Codes that are not from this API

INVALID_JSON is returned by the marketplace-app framework (@brainerce/integration-shared) when an app's own webhook receiver is sent an unparseable body. You will see it if you are building a marketplace app; you will not see it in a response from api.brainerce.com.

Idempotency

Idempotency-Key is opt-in per route, not blanket coverage of every mutation. Most /v1/* mutations honour it, thirteen do not, and checkout completion requires it. Read Idempotency before relying on a retry being safe — a key sent to a route that does not support it is accepted and ignored, with nothing in the response to tell you.

When in doubt

If you hit an error you don't understand:

  1. Look up the value of the code field in this catalog.
  2. Check the Critical Rules for known integration gotchas.
  3. Reach out at [email protected] — quote the endpoint you called plus the timestamp and path from the error body. There is no request-id response header to quote (neither X-Request-Id nor X-Brainerce-Request-Id is set by the API); those two body fields are what let us find the request in our logs.