Versioning & errors
What v1 promises, how a change is announced, and the shape every failure answers in.
One version, and it only grows
There is a single major version, v1, in the path:
https://client.usequeek.com/v1Inside v1:
- fields and endpoints may be added;
- nothing is removed, renamed, retyped, or given a new meaning;
- enum values may be added, and your code must tolerate values it does not know.
A breaking change would ship as v2 beside v1, never in place. So the integration you write
today keeps working: parse defensively, ignore unknown fields, and do not switch exhaustively
on a status string without a default branch.
The same promise covers webhook payloads. Every webhook delivery states the contract it was
built against in X-Queek-Api-Version.
How a retirement would be announced
Nothing in v1 is deprecated today. If something ever is, it is announced on the responses
themselves, months before it stops working:
| Header | Meaning |
|---|---|
Deprecation | RFC 9745 — when the endpoint was marked deprecated. |
Sunset | RFC 8594 — the date it stops answering. Never less than six months out. |
Link: rel="deprecation" | Where to read what replaces it. |
Log these headers if you can. They are the earliest possible warning, and they arrive long before anything breaks.
Errors
A failure answers with a JSON body whose error_code names the cause:
{
"status": "failed",
"error_code": "invalid_client_key",
"message": "This API key is unknown or has been revoked. Check it under Dashboard → Settings → API keys.",
"data": null
}Branch on error_code, never on message. The code is stable — a new failure mode gets a
new code, and an existing code never changes meaning — while the message is written for a
human and may be reworded.
Validation failures answer 422 with the offending fields:
{
"status": "failed",
"message": "The given data was invalid.",
"errors": { "email": ["The email field is required."] }
}Richer envelope, additively
Queek is rolling out an error object alongside the keys above — error.code,
error.message, optional error.field / error.errors, plus a doc_url for the code and the
request_id of the call. It is additive: the top-level keys documented here stay, so code that
reads error_code keeps working. Read error.code when it is present and fall back to
error_code.
Codes you will meet
error_code | Status | Cause |
|---|---|---|
client_key_required | 401 | No X-Client-Key header. |
invalid_client_key | 401 | Unknown or revoked key. |
origin_required | 403 | Public key sent with no Origin, and its allowlist is not *. |
origin_not_allowed | 403 | This Origin is not on the key's allowlist. |
plan_inactive | 403 | The store's plan has no API access. |
route_not_available | 404 | Not part of this API. |
vendor_not_found | 404 | No store resolves for this request. |
validation_failed | 422 | The request body or query is invalid; see errors. |
too_many_requests | 429 | Rate limited — honour Retry-After. |
404 on a resource path (a slug that does not exist) is an ordinary miss, not a contract
error: the store simply has no such product, page or collection.
Retries that are safe
GET calls are safe to retry as they are. For writes, send an Idempotency-Key header —
operations that accept it document it as a parameter in the
reference. Retrying the same key with the same body replays the stored
response instead of acting twice; the same key with a different body is refused, which is
the point of the header. Failures are never stored, so a retry after an error is a real retry.
When you need help
Quote the request_id from the failing response if it carries one — it is the correlation id
that ties your call to Queek's own logs. You may also send your own with the X-Request-Id
header (8–128 characters of A-Za-z0-9_.:-) and it will be used as-is.