borkoldocs

Getting Started

Errors and Rate Limits

Every error envelope the storefront API returns, the 422 validation shape, rate limiting, and the pagination envelope.

View as Markdown

The storefront API signals failures with conventional HTTP status codes and small JSON bodies. This page catalogs every error envelope the API produces, explains the validation error shape, documents the rate limit, and defines the pagination envelope used by all list endpoints. Endpoint reference pages link here instead of repeating these shapes.

All error bodies are JSON objects. Most carry a message field; two special cases differ and are called out below (the module-disabled error uses an error key, and the suspension error adds a machine-readable code).

401 Unauthenticated

Returned when the Authorization: Bearer {api_key} header is missing, malformed, or carries a revoked key. Also returned when the header uses a scheme other than Bearer.

Response
{
  "message": "Unauthenticated."
}

Fix: send a valid, unrevoked API key as Authorization: Bearer {api_key}. Verify the key with GET /v1/api/me.

403 Forbidden

Three distinct conditions return 403. Distinguish them by the body shape.

Tenant suspended

The store the key belongs to is suspended by Borkol. Every /v1/api request fails with this body until the suspension is lifted. The code field is stable; branch on code === "tenant_suspended".

Response
{
  "message": "Tenant suspended.",
  "code": "tenant_suspended"
}

Missing scope

The key is valid but does not carry the scope the endpoint requires (for example calling a commerce:write endpoint with a key that only has commerce:read).

Response
{
  "message": "Invalid ability provided."
}

Fix: create a key with the required scope in the client dashboard (Settings > API keys). Scopes cannot be added to an existing key.

Module not enabled

The route belongs to a module that is not enabled for the store. This error uses an error key, not message, and wins over the scope check: it is returned even when the key carries the module's scope. {key} is the module key: commerce, stock, customers, or objects.

Response
{
  "error": "Module not enabled: commerce"
}

Fix: module enablement is managed by Borkol per store; contact Borkol to enable the module.

404 Not Found

Returned for unknown routes and for path parameters that do not resolve to an existing record within the store. The body is {"message": "..."}; for a missed model lookup the message is Laravel's default, for example:

Response
{
  "message": "No query results for model [Borkol\\Commerce\\Models\\Product] 9c4e7f0a-2b31-4d6e-8f0a-1d2c3b4a5e6f"
}

Records belonging to a different store are indistinguishable from nonexistent records: both return 404, never 403.

422 Validation error

Returned when a request body or query parameter fails validation. The shape is always:

NameTypeRequiredDescription
messagestringalwaysThe first validation error message (a human-readable summary).
errorsobjectalwaysMap of field name to an array of one or more error message strings for that field. Nested fields use dot notation (for example lines.0.quantity).
Response
{
  "message": "The email field is required.",
  "errors": {
    "email": ["The email field is required."],
    "lines.0.quantity": ["The lines.0.quantity field must be at least 1."]
  }
}

Each endpoint's reference page lists its fields with types, required flags, and validation rules; the 422 body always follows this envelope.

429 Too Many Attempts (rate limiting)

All /v1/api routes share one rate limit of 120 requests per minute per store. The limit is keyed by the store (tenant), not by API key or IP: all keys of one store draw from the same budget. Unauthenticated requests to rate-limited routes are keyed by client IP. GET /v1/ping is not subject to this limit.

Every response on a rate-limited route includes:

HeaderDescription
X-RateLimit-LimitThe per-minute budget (120).
X-RateLimit-RemainingRequests left in the current window.

When the budget is exhausted the API returns HTTP 429 with a Retry-After header (seconds until the window resets):

Response
{
  "message": "Too Many Attempts."
}

Clients should watch X-RateLimit-Remaining, back off before hitting zero, and honor Retry-After on 429. For catalog-heavy storefronts, cache reads on your side rather than proxying every page view to the API.

Pagination envelope

Every list endpoint in the storefront API returns the same envelope: a data array plus links and meta objects. This is documented once here; endpoint pages describe only the shape of the objects inside data.

List endpoints accept these query parameters:

NameTypeRequiredDescription
pageintegerNoPage number, 1-based. Defaults to 1.
per_pageintegerNoItems per page. Each endpoint documents its default and maximum (commonly default 25, maximum 100); values outside the allowed range are clamped.

The envelope:

NameTypeDescription
dataarrayThe page of resources.
links.firststringURL of the first page.
links.laststringURL of the last page.
links.prevstring or nullURL of the previous page, null on the first page.
links.nextstring or nullURL of the next page, null on the last page.
meta.current_pageintegerCurrent page number.
meta.frominteger or null1-based index of the first item on this page, null when the page is empty.
meta.last_pageintegerTotal number of pages.
meta.linksarrayPaginator link objects (url, label, active) for building page controls.
meta.pathstringBase URL of the endpoint without pagination parameters.
meta.per_pageintegerEffective page size.
meta.tointeger or null1-based index of the last item on this page, null when the page is empty.
meta.totalintegerTotal number of matching items.
Response
{
  "data": [
    { "id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" }
  ],
  "links": {
    "first": "https://api.borkol.com/v1/api/commerce/products?page=1",
    "last": "https://api.borkol.com/v1/api/commerce/products?page=4",
    "prev": null,
    "next": "https://api.borkol.com/v1/api/commerce/products?page=2"
  },
  "meta": {
    "current_page": 1,
    "from": 1,
    "last_page": 4,
    "links": [
      { "url": null, "label": "« Previous", "active": false },
      { "url": "https://api.borkol.com/v1/api/commerce/products?page=1", "label": "1", "active": true }
    ],
    "path": "https://api.borkol.com/v1/api/commerce/products",
    "per_page": 25,
    "to": 25,
    "total": 87
  }
}

Endpoints that return a single object (such as GET /v1/api/me) return the bare JSON object with no data wrapper unless their reference page says otherwise.

Error handling summary for clients

StatusDetect byAction
401message == "Unauthenticated."Key missing or revoked; obtain a valid key. Not retryable.
403code == "tenant_suspended"Store suspended; stop calling until resolved.
403message == "Invalid ability provided."Key lacks the scope; issue a key with the right scopes. Not retryable.
403body has error starting with "Module not enabled:"Module disabled for the store. Not retryable.
404message presentRoute or record does not exist in this store.
422errors object presentFix the listed fields and resend.
429message == "Too Many Attempts."Wait Retry-After seconds, then retry.
5xxanyServer-side failure; retry with exponential backoff.