API Docs

Errors

Every error response from /api/v1/* follows the same envelope. Below is the full catalog: what causes each, and how a well-behaved client handles it.

Envelope shape

{
  "error": {
    "type": "validation_error",
    "code": "missing_idempotency_key",
    "message": "Idempotency-Key header is required for this endpoint.",
    "doc_url": "https://watchtraderhub.com/docs/api/errors#missing_idempotency_key",
    "request_id": "req_…",
    "param": "Idempotency-Key"
  }
}
  • type: high-level category (drives SDK error class hierarchy).
  • code: stable machine-readable identifier. Match on this, not on message.
  • message: human-readable. Free to change between API versions.
  • doc_url: link to the entry on this page.
  • request_id: quote in support tickets to short-circuit triage.
  • param: present on validation errors; the JSON path of the offending field.

Match on code, not message

We change the wording of message as we improve diagnostics. We promise not to change code values. Those are the contract.

Catalog

Anchors are stable across API versions. Old persisted doc_urls of the form #errors-<code> still resolve here: both forms are rendered side by side on every entry.

missing_authorization

401authentication_error

No Authorization header was sent.

Common causes

  • The client did not include the `Authorization` header on the request.
  • A proxy or middleware stripped the header before it reached the API.

Recovery

Send `Authorization: Bearer wth_<your-key>` on every request.

malformed_authorization

401authentication_error

The Authorization header was present but did not match the expected `Bearer wth_<32 base62>` shape.

Common causes

  • The scheme was not `Bearer` (e.g. `Basic`, `Token`).
  • The key prefix was not `wth_`.
  • The key body was not exactly 32 base62 characters (`A-Za-z0-9`).

Recovery

Re-copy the key from /channels/custom — Settings → API credentials. Keys regenerated through the dashboard always satisfy this format.

invalid_api_key

401authentication_error

The key is well-formed but does not match any active integration.

Common causes

  • The key was regenerated and the client is still using the previous value.
  • The integration was deleted.
  • The key belongs to a different environment (test vs live).

Recovery

Regenerate or copy the current key from /channels/custom — Settings → API credentials.

integration_disabled

403permission_error

The integration was paused by the dealer.

Common causes

  • The dealer toggled `Integration enabled` off in Settings.

Recovery

Re-enable the integration at /channels/custom — Settings → Activation & mode.

auth_lookup_failed

401authentication_error

A transient database error occurred while looking up the API key.

Common causes

  • Brief database connectivity blip.

Recovery

Retry the request after a short backoff. Persistent failures should be reported to support.

invalid_request

400validation_error

The request body failed schema validation.

Common causes

  • A required field was missing.
  • A field value violated a length, type, or enum constraint.
  • A `metadata` payload exceeded 10,000 bytes.

Recovery

Read the `message` field — it surfaces the exact Zod issue and the offending field path.

invalid_json

400validation_error

The request body was not valid JSON.

Common causes

  • The client sent malformed JSON (trailing comma, unescaped quote, truncated body).
  • Content-Type was application/json but the body was form-encoded.

Recovery

Validate the body with `JSON.parse` (or your language equivalent) before sending. Make sure `Content-Type: application/json` is set.

invalid_cursor

400validation_error

The `after` cursor parameter could not be decoded.

Common causes

  • The cursor value was hand-crafted instead of taken verbatim from the previous response's `next_cursor`.
  • The cursor was URL-decoded twice.

Recovery

Pass `next_cursor` from the previous response unchanged. Cursors are opaque — do not parse or modify them.

missing_idempotency_key

400validation_error

POSTs to mutating endpoints must include an `Idempotency-Key` header.

Common causes

  • The client did not send the `Idempotency-Key` header on a POST/PUT.

Recovery

Send a unique key per logical operation (UUIDv4 is fine). Replay the same key + body to safely retry; replays return the cached response with `Idempotent-Replay: true`.

request_too_large

413validation_error

The request body exceeded the size cap (1 MB on regular endpoints, 10 MB on `/bulk` endpoints).

Common causes

  • A `metadata` field carried a large blob.
  • A bulk-customer request packed too many records.

Recovery

Trim the payload, or split bulk operations into chunks of ≤1000 records.

key_reused_with_different_body

409idempotency_error

An `Idempotency-Key` was reused with a body that differs from the original request.

Common causes

  • A bug caused the client to mutate the request body before retrying.
  • A non-deterministic field (e.g. timestamp, randomized id) appears in the body.

Recovery

Either use a new key for the new operation, or replay the exact same body. Idempotency records expire after 24 hours.

duplicate_channel_order_id

409conflict_error

An order with this `channel_order_id` already exists for the integration.

Common causes

  • The same order was POSTed twice without the `Idempotency-Key` header.
  • The client generated a non-unique `channel_order_id`.

Recovery

Make sure `channel_order_id` is unique per logical order. Use `Idempotency-Key` for safe retries.

too_many_requests

429rate_limit_error

The integration exceeded its rate limit (default: 600 requests / minute).

Common causes

  • A burst of API calls overwhelmed the rolling window.

Recovery

Honour the `Retry-After` header. Inspect `X-RateLimit-{Limit,Remaining,Reset}` headers to pace clients. Contact support to raise the cap.

listing_not_found

404not_found

No listing with that id is published on the custom integration for this dealer.

Common causes

  • The id is wrong, malformed, or belongs to another dealer.
  • The item was un-toggled from the custom channel.
  • The item was sold or archived.

Recovery

Re-fetch the collection (`GET /v1/listings`) to discover currently-active ids.

order_not_found

404not_found

No order with that id exists for this dealer.

Common causes

  • The id is wrong or belongs to another dealer.
  • The order has not yet been ingested.

Recovery

Re-fetch via `GET /v1/orders` or POST the order again with a fresh `Idempotency-Key`.

internal_error

500api_error

An unexpected server-side error occurred.

Common causes

  • A handler threw an unhandled exception.

Recovery

Retry with backoff. Persistent 500s should be reported to support — include the `request_id` from the response.

lookup_failed

500api_error

A pre-write lookup against the database failed.

Common causes

  • Transient database connectivity issue.

Recovery

Retry the request after a short backoff.

query_failed

500api_error

A read query against the database failed.

Common causes

  • Transient database connectivity issue.

Recovery

Retry the request after a short backoff.

upsert_failed

500api_error

The customer upsert (`POST /v1/customers`) failed at the database layer.

Common causes

  • Transient database error.
  • A schema constraint violation that was not caught by the validator.

Recovery

Retry with the same `Idempotency-Key` to safely re-attempt. Persistent failures should be reported with the `request_id`.

bulk_upsert_failed

500api_error

The bulk customer upsert (`POST /v1/customers/bulk`) failed at the database layer.

Common causes

  • Transient database error.
  • A schema constraint violation in one of the records.

Recovery

Retry with the same `Idempotency-Key`. To isolate a bad record, split the batch and POST in halves.

insert_failed

500api_error

The order insert (`POST /v1/orders`) failed at the database layer.

Common causes

  • Transient database error.

Recovery

Retry with the same `Idempotency-Key`.

cancel_failed

500api_error

The order cancellation update failed.

Common causes

  • Transient database error.

Recovery

Retry the cancel call. Persistent failures should be reported with the `request_id`.