Errors
The Kreezalid APIs report errors in two ways, and a robust client handles both:
- Request errors: the request could not be served (no credentials, unknown resource,
wrong method). They come with a
4xxcode. - Refused operations: the request was understood, but the write was refused (a required
field is missing, a category does not exist). Most write endpoints report these with
"success": falsein the body, often with a200status.
Status codes
Section titled “Status codes”| Code | Meaning | What to do |
|---|---|---|
200 |
Success, or a refused write. | Read success on write endpoints. |
201 |
Created. Some creation endpoints use it, others answer 200. |
Treat 200 and 201 alike. |
400 |
Invalid parameters. | Fix the request. |
403 |
Missing or wrong credentials, or the resource is not yours. | Check authentication and the base URL. |
404 |
The resource or the endpoint does not exist, or the page is past the last one. | Check the id, the path and the page. |
405 |
The endpoint exists but not with this HTTP method. | Use the method shown in the reference. |
406 |
The body could not be used, for instance a metafield without value. |
Fix the body. |
422 |
Validation failed (Front API). | Show the message to the user. |
301 |
You called another address of the marketplace. | Call the primary address, see Base URLs. |
503 |
The marketplace is in maintenance. | Retry after the Retry-After delay. |
5xx |
A server error. | Retry with backoff; report it if it persists. |
Admin API
Section titled “Admin API”Request errors
Section titled “Request errors”Errors are returned as JSON with the HTTP code repeated in code:
{ "message": "Listing with ID 999999 cannot be found", "url": "/api/v1/listings/999999", "code": 404}{ "message": "Permission denied, wrong credentials", "url": "/api/v1/orders", "code": 403}A path that matches no endpoint at all also carries the method:
{ "message": "Not Found", "url": "/api/v1/listings/645646/photos", "method": "GET", "code": 404}Refused writes
Section titled “Refused writes”Create, update and delete endpoints answer with success and a human-readable message:
{ "id": null, "message": "Field price is required", "success": false}{ "success": false, "message": "Error. No updates were made."}When a user cannot be created because of invalid fields, message is an object keyed by
field, then by rule:
{ "id": null, "success": false, "message": { "address_zipcode": { "maxLength": "The postal code must be at most 20 characters long" } }, "content": []}Validation messages follow the marketplace’s language, so match on the field and rule names, not on the text.
POST /api/v1/checkout/preauth-success is the exception: it answers 400 with an errors
list naming each invalid field:
{ "code": 400, "status": "error", "message": "Invalid request params", "errors": [ { "field": "tos_acceptance_at", "message": "tos_acceptance_at is not a valid date (YYYY-MM-DD HH:MM:SS)" } ]}Partners API
Section titled “Partners API”Refused writes and unknown paths are answered in JSON, like the Admin API. Authentication
failures (403) and resources that do not exist or do not belong to the vendor (404) come
back with the right status code but an HTML error page as the body. Check the status code
before parsing the body.
Front API
Section titled “Front API”The Front API answers in JSON for the errors it anticipates, with a body that depends on the endpoint family:
| Endpoints | Error body |
|---|---|
| Authentication, account | {"code": 403, "status": "error", "message": "Token not found in request"} |
| Cart items | {"success": false, "error": "This product is no longer available for purchase."} |
| Cart creation, shipping method | {"status": "failed", "message": "There is no such order in this cart"} |
| Wishlists | {"success": false, "message": "listing_already_in_user_wishlist"} |
A listing or an order that does not exist, a request made while already signed in to an
endpoint for visitors, or a path that matches no endpoint, returns the right 4xx status with
an HTML page as the body.
Handle errors defensively
Section titled “Handle errors defensively”Whatever the API, the same few lines cover every case:
export async function call(url, init) { const res = await fetch(url, { ...init, redirect: 'manual' }); const isJson = (res.headers.get('content-type') || '').includes('application/json'); const body = isJson ? await res.json() : await res.text();
const code = (isJson && typeof body.code === 'number') ? body.code : res.status; if (code >= 300 || (isJson && body.success === false)) { const message = isJson ? (body.message ?? body.error ?? JSON.stringify(body)) : `HTTP ${res.status}`; const err = new Error(typeof message === 'string' ? message : JSON.stringify(message)); err.status = code; err.retryable = code === 429 || code >= 500; throw err; } return body;}function kreezalid_call(string $method, string $url, ?array $json, string $userpwd): array{ $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_CUSTOMREQUEST => $method, CURLOPT_USERPWD => $userpwd, CURLOPT_RETURNTRANSFER => true, CURLOPT_FOLLOWLOCATION => false, CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Accept: application/json'], CURLOPT_POSTFIELDS => $json === null ? null : json_encode($json), ]); $raw = curl_exec($ch); $status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE); $isJson = str_contains((string)curl_getinfo($ch, CURLINFO_CONTENT_TYPE), 'application/json'); $body = $isJson ? json_decode($raw, true) : null;
$code = is_array($body) && is_int($body['code'] ?? null) ? $body['code'] : $status; if ($code >= 300 || (is_array($body) && ($body['success'] ?? null) === false)) { $message = $body['message'] ?? $body['error'] ?? "HTTP $status"; throw new RuntimeException(is_string($message) ? $message : json_encode($message), $code); } return $body ?? [];}import requests
class KreezalidError(Exception): def __init__(self, message, status): super().__init__(message) self.status = status self.retryable = status == 429 or status >= 500
def call(method, url, **kwargs): res = requests.request(method, url, allow_redirects=False, **kwargs) is_json = "application/json" in res.headers.get("content-type", "") body = res.json() if is_json else None
code = body["code"] if isinstance(body, dict) and isinstance(body.get("code"), int) else res.status_code if code >= 300 or (isinstance(body, dict) and body.get("success") is False): message = (body or {}).get("message") or (body or {}).get("error") or f"HTTP {res.status_code}" raise KreezalidError(message if isinstance(message, str) else str(message), code) return body