Queek docs

Errors

The one envelope every failure answers in, and every code the Merchant API emits.

The envelope

A failure answers with one envelope, plus the legacy top-level keys older clients read:

{
  "status": "failed",
  "error_code": "insufficient_scope",
  "message": "This API key does not carry the 'merchant-orders-update' scope it was called with.",
  "data": null,
  "error": {
    "code": "insufficient_scope",
    "message": "This API key does not carry the 'merchant-orders-update' scope it was called with.",
    "doc_url": "https://docs.usequeek.com/docs/versioning-and-errors#insufficient_scope",
    "request_id": "5d1c9b3e-8f2a-4b6d-9c07-3e1f8a2b6d45"
  }
}

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. error_code at the top level mirrors error.code for clients that predate the envelope.

error keyAlwaysMeaning
codeyesOne of the codes below.
messageyesHuman-readable. Not stable.
doc_urlyesDeep link to the code's row in the error reference.
request_idyesThe X-Request-Id of the call — quote it in support.
fieldvalidation onlyThe first offending field.
errorsvalidation onlyPer-field messages, { field: [message, …] }.

Validation failures answer 422 validation_failed with the offending fields:

{
  "status": "failed",
  "message": "The quantity change field is required.",
  "error_type": "validation_error",
  "errors": { "quantity_change": ["The quantity change field is required."] },
  "data": null,
  "error": {
    "code": "validation_failed",
    "message": "The quantity change field is required.",
    "field": "quantity_change",
    "errors": { "quantity_change": ["The quantity change field is required."] },
    "doc_url": "https://docs.usequeek.com/docs/versioning-and-errors#validation_failed",
    "request_id": "5d1c9b3e-8f2a-4b6d-9c07-3e1f8a2b6d45"
  }
}

The top-level message is the first failing rule; error.errors has all of them.

One refusal predates the envelope and keeps its older shape: a vendor header naming a different store than the key's answers 403 as {"error": "VENDOR_ACCESS_DENIED", "message": "This connection is bound to a different store"}. Switch on that top-level error key for this one code only — everything else is in the envelope above.

Every code

The codes this API emits, identical to the error.code enum in the merchant OpenAPI document (components.schemas.QueekError). Codes the shared vocabulary defines for other surfaces — the dashboard, checkout, AI tools — are listed at the end, so nothing in the enum is unaccounted for.

Keys and scopes

error.codeStatusCause
invalid_client_key401The key is unknown. Check it under Dashboard → Settings → API keys.
api_key_mode_mismatch401A _test_ key on a live store, or a _live_ key on a dev store. Keys never cross modes.
api_key_revoked403The key was revoked. Mint a fresh one.
api_key_expired403The key expired. Mint a fresh one.
private_key_required403A public pk_ key was sent. Mint a private sk_ key.
insufficient_scope403The key does not carry the scope this operation needs — including the contact filters on the customer list without merchant-customers-contact-read. The message names the missing scope.
route_not_available403Not part of the Merchant API — the endpoints are exactly those in the reference. A key sent to a retired api/v1/biz/… path is refused here too, with the new /api/v1/merchant/… path named in the message: This dashboard path no longer accepts API keys. Call POST /api/v1/merchant/orders/{order}/status instead. Anything else stays dashboard-only.
plan_inactive403The store's plan has no API access.

Note the two statuses that differ from the Storefront API: a revoked or expired key is 403 here, and so is a route outside the Merchant API — the key is valid, the call itself is what is refused.

Cross-store

error.codeStatusCause
VENDOR_ACCESS_DENIED403A vendor header named a different store than the key's. Answers in the older {"error", "message"} shape. Drop the header: the key already names its store.

Request shape

These are the status-implied defaults: the code a status carries when no surface has named the failure more precisely.

error.codeStatusCause
bad_request400The request could not be understood.
unauthenticated401Authentication is required and none that fits was presented.
forbidden403Authenticated, but not allowed to do this.
not_found404No such resource — an id the store does not have, for instance.
method_not_allowed405That method is not supported on this endpoint.
conflict409The request conflicts with the current state.
gone410The resource existed and no longer does.
payload_too_large413The body exceeds the size limit.
unsupported_media_type415Send Content-Type: application/json.
validation_failed422The body or query is invalid; see error.errors.
too_many_requests429Rate limited — honour Retry-After.
server_error500Queek's fault. Retry with backoff and quote the request_id.
service_unavailable503Temporarily unavailable. Retry with backoff.

Idempotency

See Idempotency & rate limits for the full contract.

error.codeStatusCause
idempotency_key_reuse409This Idempotency-Key was already used with a different body. Use a new key.
idempotency_key_in_progress409A request with this key is still running. Retry in a moment; nothing ran twice.

Catalogue writes

error.codeStatusCause
metaobject_reference_invalid422A product write names a metaobject reference that does not resolve.

Never emitted here

The shared envelope vocabulary names codes for surfaces this API is not. You will not meet them on a key call, and no retry or scope will produce them:

  • origin_required, origin_not_allowed — public-key origin checks. A pk_ key is refused with private_key_required before any origin is looked at.
  • client_key_required — no merchant operation names it; a call with no key at all is refused as unauthenticated.
  • ai_unavailable_in_test_mode — no AI route is on the Merchant API.
  • payment_amount_mismatch — checkout's verdict, never this API's. Fulfil on the orders/paid webhook, not on anything here.
  • custom_gateway_forbidden, custom_gateway_plan_required, custom_gateway_kyc_required, custom_gateway_store_ineligible, custom_gateway_not_verified, custom_gateway_not_configured, custom_gateway_verification_failed — the dashboard's own errors for a merchant collecting through its own provider account.
  • vendor_mail_sender_forbidden, vendor_mail_sender_not_configured, vendor_mail_sender_plan_required, vendor_mail_sender_verification_failed — the dashboard's mail-sender setup, not on the Merchant API.
  • metafield_definition_missing, metafield_filter_unsupported_type, metafield_namespace_reserved, metafield_value_invalid, metaobject_definition_missing, metaobject_field_invalid, metaobject_type_reserved — catalogue-validation codes from other surfaces; the reference documents exactly which fields each operation validates.

Retries that are safe

GET calls are safe to retry as they are. For writes, send an Idempotency-Key header — every write in the reference lists it as a parameter. 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.

On this page