Aan de slag
Errors en rate limits
Elke error-envelope die de storefront-API teruggeeft, de vorm van de 422-validatiefout, rate limiting en de paginatie-envelope.
Bekijk als MarkdownDe storefront-API signaleert fouten met conventionele HTTP-statuscodes en kleine JSON-bodies. Deze pagina catalogiseert elke error-envelope die de API produceert, legt de vorm van validatiefouten uit, documenteert de rate limit en definieert de paginatie-envelope die alle lijst-endpoints gebruiken. Endpoint-referentiepagina's linken hierheen in plaats van deze vormen te herhalen.
Alle error-bodies zijn JSON-objecten. De meeste dragen een message-veld; meerdere bijzondere gevallen wijken af en worden hieronder benoemd: de module-disabled-error gebruikt een error-key, en de suspensie-error, geweigerde cartregels, schendingen van het cart- en checkoutbeleid en sommige weigeringen op offertetokens voegen een machine-leesbare code toe.
De storefront-API signaleert fouten met conventionele HTTP-statuscodes en kleine JSON-bodies. Deze pagina catalogiseert elke error-envelope die de API produceert, legt de vorm van validatiefouten uit, documenteert de rate limit en definieert de paginatie-envelope die alle lijst-endpoints gebruiken. Endpoint-referentiepagina's linken hierheen in plaats van deze vormen te herhalen.
Alle error-bodies zijn JSON-objecten. De meeste dragen een message-veld; meerdere bijzondere gevallen wijken af en worden hieronder benoemd: de module-disabled-error gebruikt een error-key, en de suspensie-error, geweigerde cartregels, schendingen van het cart- en checkoutbeleid en sommige weigeringen op offertetokens voegen een machine-leesbare code toe.
401 Unauthenticated
Wordt teruggegeven wanneer de header Authorization: Bearer {api_key} ontbreekt, misvormd is of een ingetrokken key draagt. Wordt ook teruggegeven wanneer de header een ander schema dan Bearer gebruikt.
Oplossing: stuur een geldige, niet-ingetrokken API-key als Authorization: Bearer {api_key}. Verifieer de key met GET /v1/api/me.
{
"message": "Unauthenticated."
}401 Unauthenticated
Wordt teruggegeven wanneer de header Authorization: Bearer {api_key} ontbreekt, misvormd is of een ingetrokken key draagt. Wordt ook teruggegeven wanneer de header een ander schema dan Bearer gebruikt.
{
"message": "Unauthenticated."
}Oplossing: stuur een geldige, niet-ingetrokken API-key als Authorization: Bearer {api_key}. Verifieer de key met GET /v1/api/me.
403 Forbidden
Drie verschillende condities geven 403 terug. Onderscheid ze aan de vorm van de body.
Tenant geschorst
De winkel waar de key bij hoort is door Borkol geschorst. Elke /v1/api-request faalt met deze body tot de schorsing wordt opgeheven. Het code-veld is stabiel; vertak op code === "tenant_suspended".
Ontbrekende scope
De key is geldig maar draagt niet de scope die het endpoint vereist (bijvoorbeeld het aanroepen van een commerce:write-endpoint met een key die alleen commerce:read heeft).
Oplossing: maak in het client-dashboard (Settings > API keys) een key aan met de vereiste scope. Scopes kunnen niet aan een bestaande key worden toegevoegd.
Module niet ingeschakeld
De route hoort bij een module die niet is ingeschakeld voor de winkel. Deze error gebruikt een error-key, geen message, en wint van de scope-check: hij wordt zelfs teruggegeven wanneer de key de scope van de module draagt. {key} is de module-key: catalog, commerce, configurator, sales, stock, objects of forms.
De body bevat ook code: "module_disabled" en module (alleen de module-key), zodat een client op code kan vertakken in plaats van error te parsen. error blijft ongewijzigd.
Eén route antwoordt deze body met status 404 in plaats van 403: POST /v1/api/commerce/carts/{id}/quote-request zonder de sales-module geeft 404 {"error":"Module not enabled: sales"}, dezelfde status als offerteaanvragen die in de winkelinstellingen zijn uitgeschakeld, zodat een storefront één "niet beschikbaar"-geval hoeft af te handelen.
Oplossing: module-inschakeling wordt per winkel door Borkol beheerd; neem contact op met Borkol om de module in te schakelen.
{
"message": "Tenant suspended.",
"code": "tenant_suspended"
}{
"message": "Invalid ability provided."
}{
"error": "Module not enabled: commerce",
"code": "module_disabled",
"module": "commerce"
}403 Forbidden
Drie verschillende condities geven 403 terug. Onderscheid ze aan de vorm van de body.
Tenant geschorst
De winkel waar de key bij hoort is door Borkol geschorst. Elke /v1/api-request faalt met deze body tot de schorsing wordt opgeheven. Het code-veld is stabiel; vertak op code === "tenant_suspended".
{
"message": "Tenant suspended.",
"code": "tenant_suspended"
}Ontbrekende scope
De key is geldig maar draagt niet de scope die het endpoint vereist (bijvoorbeeld het aanroepen van een commerce:write-endpoint met een key die alleen commerce:read heeft).
{
"message": "Invalid ability provided."
}Oplossing: maak in het client-dashboard (Settings > API keys) een key aan met de vereiste scope. Scopes kunnen niet aan een bestaande key worden toegevoegd.
Module niet ingeschakeld
De route hoort bij een module die niet is ingeschakeld voor de winkel. Deze error gebruikt een error-key, geen message, en wint van de scope-check: hij wordt zelfs teruggegeven wanneer de key de scope van de module draagt. {key} is de module-key: catalog, commerce, configurator, sales, stock, objects of forms.
De body bevat ook code: "module_disabled" en module (alleen de module-key), zodat een client op code kan vertakken in plaats van error te parsen. error blijft ongewijzigd.
Eén route antwoordt deze body met status 404 in plaats van 403: POST /v1/api/commerce/carts/{id}/quote-request zonder de sales-module geeft 404 {"error":"Module not enabled: sales"}, dezelfde status als offerteaanvragen die in de winkelinstellingen zijn uitgeschakeld, zodat een storefront één "niet beschikbaar"-geval hoeft af te handelen.
{
"error": "Module not enabled: commerce",
"code": "module_disabled",
"module": "commerce"
}Oplossing: module-inschakeling wordt per winkel door Borkol beheerd; neem contact op met Borkol om de module in te schakelen.
404 Not Found
Wordt teruggegeven voor onbekende routes en voor padparameters die niet naar een bestaand record binnen de winkel verwijzen. De body is {"message": "..."}; bij een mislukte model-lookup is het bericht de standaard van Laravel, bijvoorbeeld:
Records die bij een andere winkel horen zijn niet te onderscheiden van niet-bestaande records: beide geven 404 terug, nooit 403.
{
"message": "No query results for model [Borkol\\Catalog\\Models\\Product] 9c4e7f0a-2b31-4d6e-8f0a-1d2c3b4a5e6f"
}404 Not Found
Wordt teruggegeven voor onbekende routes en voor padparameters die niet naar een bestaand record binnen de winkel verwijzen. De body is {"message": "..."}; bij een mislukte model-lookup is het bericht de standaard van Laravel, bijvoorbeeld:
{
"message": "No query results for model [Borkol\\Catalog\\Models\\Product] 9c4e7f0a-2b31-4d6e-8f0a-1d2c3b4a5e6f"
}Records die bij een andere winkel horen zijn niet te onderscheiden van niet-bestaande records: beide geven 404 terug, nooit 403.
422 Validatiefout
Wordt teruggegeven wanneer een request body of queryparameter de validatie niet doorstaat. De vorm is altijd:
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
message | string | altijd | Het eerste validatiefoutbericht (een leesbare samenvatting). |
errors | object | altijd | Map van veldnaam naar een array van één of meer foutberichten voor dat veld. Geneste velden gebruiken puntnotatie (bijvoorbeeld lines.0.quantity). |
De referentiepagina van elk endpoint vermeldt zijn velden met types, verplicht-vlaggen en validatieregels; de 422-body volgt altijd deze envelope.
Weigeringscode voor cartregels
Wanneer POST /v1/api/commerce/carts/{id}/lines een product weigert, voegt de 422-body een code op het hoogste niveau toe naast message en errors:
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
code | string | conditioneel | CAPABILITY_INACTIVE (de module die de capability van het product verkoopt is uitgeschakeld), INVALID_CONFIGURATION (variant of configuratie past niet bij het product) of NOT_PURCHASABLE (het product kan niet verkocht worden). Afwezig bij gewone invoervalidatiefouten. |
Behandel de code-enum als open. Het quote-endpoint van de configurator meldt configuratiefouten in de gewone envelope zonder code. Zie Cartregels.
{
"message": "The email field is required.",
"errors": {
"email": ["The email field is required."],
"lines.0.quantity": ["The lines.0.quantity must be greater than zero."]
}
}{
"message": "Product is not available.",
"errors": {
"product_id": ["Product is not available."]
},
"code": "CAPABILITY_INACTIVE"
}422 Validatiefout
Wordt teruggegeven wanneer een request body of queryparameter de validatie niet doorstaat. De vorm is altijd:
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
message | string | altijd | Het eerste validatiefoutbericht (een leesbare samenvatting). |
errors | object | altijd | Map van veldnaam naar een array van één of meer foutberichten voor dat veld. Geneste velden gebruiken puntnotatie (bijvoorbeeld lines.0.quantity). |
{
"message": "The email field is required.",
"errors": {
"email": ["The email field is required."],
"lines.0.quantity": ["The lines.0.quantity must be greater than zero."]
}
}De referentiepagina van elk endpoint vermeldt zijn velden met types, verplicht-vlaggen en validatieregels; de 422-body volgt altijd deze envelope.
Weigeringscode voor cartregels
Wanneer POST /v1/api/commerce/carts/{id}/lines een product weigert, voegt de 422-body een code op het hoogste niveau toe naast message en errors:
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
code | string | conditioneel | CAPABILITY_INACTIVE (de module die de capability van het product verkoopt is uitgeschakeld), INVALID_CONFIGURATION (variant of configuratie past niet bij het product) of NOT_PURCHASABLE (het product kan niet verkocht worden). Afwezig bij gewone invoervalidatiefouten. |
{
"message": "Product is not available.",
"errors": {
"product_id": ["Product is not available."]
},
"code": "CAPABILITY_INACTIVE"
}Behandel de code-enum als open. Het quote-endpoint van de configurator meldt configuratiefouten in de gewone envelope zonder code. Zie Cartregels.
429 Too Many Attempts (rate limiting)
Alle /v1/api-routes delen één rate limit van 120 requests per minuut per winkel. De limiet wordt bepaald per winkel (tenant), niet per API-key of IP: alle keys van één winkel putten uit hetzelfde budget. Ongeauthenticeerde requests naar rate-gelimiteerde routes worden per client-IP geteld. GET /v1/ping valt niet onder deze limiet.
Elke response op een rate-gelimiteerde route bevat:
| Header | Beschrijving |
|---|---|
X-RateLimit-Limit | Het budget per minuut (120). |
X-RateLimit-Remaining | Resterende requests in het huidige venster. |
Wanneer het budget op is, geeft de API HTTP 429 terug met een Retry-After-header (seconden tot het venster reset):
Clients horen X-RateLimit-Remaining in de gaten te houden, terug te schakelen voordat de teller nul raakt en Retry-After te respecteren bij een 429. Cache voor catalogus-zware storefronts de reads aan jouw kant in plaats van elke paginaweergave naar de API te proxyen.
Formulierinzendingen hebben daarnaast een eigen limiet: elk formulier accepteert een door de merchant ingesteld aantal inzendingen per bezoeker per uur. Daarboven antwoordt POST /v1/api/forms/{key}/submissions met 429 {"message": "Too many submissions. Try again later."}, zonder Retry-After-header. Zie Inzendingen. Bestandsuploads voor formulieren zijn beperkt tot 30 per formulier per bezoeker per uur: daarboven antwoordt POST /v1/api/forms/{key}/uploads met 429 {"message": "Too many uploads. Try again later."}, ook zonder Retry-After. Zie Uploads.
{
"message": "Too Many Attempts."
}429 Too Many Attempts (rate limiting)
Alle /v1/api-routes delen één rate limit van 120 requests per minuut per winkel. De limiet wordt bepaald per winkel (tenant), niet per API-key of IP: alle keys van één winkel putten uit hetzelfde budget. Ongeauthenticeerde requests naar rate-gelimiteerde routes worden per client-IP geteld. GET /v1/ping valt niet onder deze limiet.
Elke response op een rate-gelimiteerde route bevat:
| Header | Beschrijving |
|---|---|
X-RateLimit-Limit | Het budget per minuut (120). |
X-RateLimit-Remaining | Resterende requests in het huidige venster. |
Wanneer het budget op is, geeft de API HTTP 429 terug met een Retry-After-header (seconden tot het venster reset):
{
"message": "Too Many Attempts."
}Clients horen X-RateLimit-Remaining in de gaten te houden, terug te schakelen voordat de teller nul raakt en Retry-After te respecteren bij een 429. Cache voor catalogus-zware storefronts de reads aan jouw kant in plaats van elke paginaweergave naar de API te proxyen.
Formulierinzendingen hebben daarnaast een eigen limiet: elk formulier accepteert een door de merchant ingesteld aantal inzendingen per bezoeker per uur. Daarboven antwoordt POST /v1/api/forms/{key}/submissions met 429 {"message": "Too many submissions. Try again later."}, zonder Retry-After-header. Zie Inzendingen. Bestandsuploads voor formulieren zijn beperkt tot 30 per formulier per bezoeker per uur: daarboven antwoordt POST /v1/api/forms/{key}/uploads met 429 {"message": "Too many uploads. Try again later."}, ook zonder Retry-After. Zie Uploads.
Paginatie-envelope
Elk lijst-endpoint in de storefront-API geeft dezelfde envelope terug: een data-array plus links- en meta-objecten. Dit is hier eenmalig gedocumenteerd; endpoint-pagina's beschrijven alleen de vorm van de objecten binnen data.
Lijst-endpoints accepteren deze queryparameters:
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
page | integer | Nee | Paginanummer, beginnend bij 1. Standaard 1. |
per_page | integer | Nee | Items per pagina. Elk endpoint documenteert zijn standaard en maximum (doorgaans standaard 25, maximum 100); waarden buiten het toegestane bereik worden begrensd. |
De envelope:
| Naam | Type | Beschrijving |
|---|---|---|
data | array | De pagina met resources. |
links.first | string | URL van de eerste pagina. |
links.last | string | URL van de laatste pagina. |
links.prev | string of null | URL van de vorige pagina, null op de eerste pagina. |
links.next | string of null | URL van de volgende pagina, null op de laatste pagina. |
meta.current_page | integer | Huidig paginanummer. |
meta.from | integer of null | 1-gebaseerde index van het eerste item op deze pagina, null wanneer de pagina leeg is. |
meta.last_page | integer | Totaal aantal pagina's. |
meta.links | array | Paginator-linkobjecten (url, label, active) voor het bouwen van paginanavigatie. |
meta.path | string | Basis-URL van het endpoint zonder paginatieparameters. |
meta.per_page | integer | Effectieve paginagrootte. |
meta.to | integer of null | 1-gebaseerde index van het laatste item op deze pagina, null wanneer de pagina leeg is. |
meta.total | integer | Totaal aantal overeenkomende items. |
Endpoints die één object teruggeven (zoals GET /v1/api/me) geven het kale JSON-object terug zonder data-wrapper, tenzij hun referentiepagina anders vermeldt.
{
"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
}
}Paginatie-envelope
Elk lijst-endpoint in de storefront-API geeft dezelfde envelope terug: een data-array plus links- en meta-objecten. Dit is hier eenmalig gedocumenteerd; endpoint-pagina's beschrijven alleen de vorm van de objecten binnen data.
Lijst-endpoints accepteren deze queryparameters:
| Naam | Type | Verplicht | Beschrijving |
|---|---|---|---|
page | integer | Nee | Paginanummer, beginnend bij 1. Standaard 1. |
per_page | integer | Nee | Items per pagina. Elk endpoint documenteert zijn standaard en maximum (doorgaans standaard 25, maximum 100); waarden buiten het toegestane bereik worden begrensd. |
De envelope:
| Naam | Type | Beschrijving |
|---|---|---|
data | array | De pagina met resources. |
links.first | string | URL van de eerste pagina. |
links.last | string | URL van de laatste pagina. |
links.prev | string of null | URL van de vorige pagina, null op de eerste pagina. |
links.next | string of null | URL van de volgende pagina, null op de laatste pagina. |
meta.current_page | integer | Huidig paginanummer. |
meta.from | integer of null | 1-gebaseerde index van het eerste item op deze pagina, null wanneer de pagina leeg is. |
meta.last_page | integer | Totaal aantal pagina's. |
meta.links | array | Paginator-linkobjecten (url, label, active) voor het bouwen van paginanavigatie. |
meta.path | string | Basis-URL van het endpoint zonder paginatieparameters. |
meta.per_page | integer | Effectieve paginagrootte. |
meta.to | integer of null | 1-gebaseerde index van het laatste item op deze pagina, null wanneer de pagina leeg is. |
meta.total | integer | Totaal aantal overeenkomende items. |
{
"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 die één object teruggeven (zoals GET /v1/api/me) geven het kale JSON-object terug zonder data-wrapper, tenzij hun referentiepagina anders vermeldt.
Samenvatting foutafhandeling voor clients
| Status | Herken aan | Actie |
|---|---|---|
| 401 | message == "Unauthenticated." | Key ontbreekt of is ingetrokken; regel een geldige key. Niet opnieuw proberen. |
| 403 | code == "tenant_suspended" | Winkel geschorst; stop met aanroepen tot dit is opgelost. |
| 403 | message == "Invalid ability provided." | Key mist de scope; geef een key uit met de juiste scopes. Niet opnieuw proberen. |
| 403 | code == "module_disabled" (of error beginnend met "Module not enabled:") | Module uitgeschakeld voor de winkel. Niet opnieuw proberen. |
| 404 | message aanwezig, of error beginnend met "Module not enabled:" | Route of record bestaat niet in deze winkel, of (alleen offerteaanvraag) de sales-module is uitgeschakeld. |
| 422 | code is CAPABILITY_INACTIVE, INVALID_CONFIGURATION of NOT_PURCHASABLE, met errors | Cartregel geweigerd; branch op code, toon errors. |
| 422 | code aanwezig, geen errors | Beleids- of bedrijfsweigering; toon message. Codes: cart_constraint (cartregels), login_required, guest_only, b2b_approval_required, b2c_only, min_order_value (checkout), SIGNATURE_REQUIRED, SIGNATURE_INVALID (offerte accepteren). |
| 422 | errors-object aanwezig | Corrigeer de genoemde velden en stuur opnieuw. |
| 429 | message == "Too Many Attempts." | Wacht Retry-After seconden en probeer opnieuw. |
| 429 | message == "Too many submissions. Try again later." | Limiet voor formulierinzendingen van deze bezoeker; vraag de bezoeker het later opnieuw te proberen. |
| 429 | message == "Too many uploads. Try again later." | Limiet voor formulieruploads van deze bezoeker; vraag de bezoeker het later opnieuw te proberen. |
| 5xx | elke | Serverfout; probeer opnieuw met exponentiële backoff. |
Samenvatting foutafhandeling voor clients
| Status | Herken aan | Actie |
|---|---|---|
| 401 | message == "Unauthenticated." | Key ontbreekt of is ingetrokken; regel een geldige key. Niet opnieuw proberen. |
| 403 | code == "tenant_suspended" | Winkel geschorst; stop met aanroepen tot dit is opgelost. |
| 403 | message == "Invalid ability provided." | Key mist de scope; geef een key uit met de juiste scopes. Niet opnieuw proberen. |
| 403 | code == "module_disabled" (of error beginnend met "Module not enabled:") | Module uitgeschakeld voor de winkel. Niet opnieuw proberen. |
| 404 | message aanwezig, of error beginnend met "Module not enabled:" | Route of record bestaat niet in deze winkel, of (alleen offerteaanvraag) de sales-module is uitgeschakeld. |
| 422 | code is CAPABILITY_INACTIVE, INVALID_CONFIGURATION of NOT_PURCHASABLE, met errors | Cartregel geweigerd; branch op code, toon errors. |
| 422 | code aanwezig, geen errors | Beleids- of bedrijfsweigering; toon message. Codes: cart_constraint (cartregels), login_required, guest_only, b2b_approval_required, b2c_only, min_order_value (checkout), SIGNATURE_REQUIRED, SIGNATURE_INVALID (offerte accepteren). |
| 422 | errors-object aanwezig | Corrigeer de genoemde velden en stuur opnieuw. |
| 429 | message == "Too Many Attempts." | Wacht Retry-After seconden en probeer opnieuw. |
| 429 | message == "Too many submissions. Try again later." | Limiet voor formulierinzendingen van deze bezoeker; vraag de bezoeker het later opnieuw te proberen. |
| 429 | message == "Too many uploads. Try again later." | Limiet voor formulieruploads van deze bezoeker; vraag de bezoeker het later opnieuw te proberen. |
| 5xx | elke | Serverfout; probeer opnieuw met exponentiële backoff. |