Error Handling

HTTP status codes, error format, and complete error code reference.

Error Envelope

All error responses — regardless of status code — use a consistent JSON envelope:

{
  "error": {
    "code": "SCREAMING_SNAKE_CASE",
    "message": "Human-readable description",
    "details": [
      { "field": "field_name", "message": "Description of the issue" }
    ]
  }
}

Guarantees:

  • Every error response is application/json — never text/plain
  • code is always a SCREAMING_SNAKE_CASE string
  • details is only present for VALIDATION_ERROR responses; omitted otherwise

HTTP Status Codes

StatusMeaningWhen
200OKRequest succeeded
201CreatedResource created successfully
401UnauthorizedMissing or invalid API key
403ForbiddenValid key but insufficient scope or plan limit hit
404Not FoundResource does not exist
409ConflictDuplicate resource (e.g., category name already exists)
422Unprocessable EntityInvalid input — see details[] for per-field errors
429Too Many RequestsRate limit exceeded
500Internal Server ErrorUnexpected server error

Error Code Reference

AUTHENTICATION_ERROR — 401

Returned when the X-API-Key header is missing, malformed, invalid, expired, or inactive.

{
  "error": {
    "code": "AUTHENTICATION_ERROR",
    "message": "Missing X-API-Key header"
  }
}

Other messages for this code:

  • "Invalid API key format" — key does not start with wk_live_ or wk_test_
  • "Invalid or inactive API key" — key not found or deactivated
  • "API key has expired" — key's expiry date has passed

SCOPE_INSUFFICIENT — 403

Returned when the API key does not have the required scope for the endpoint.

{
  "error": {
    "code": "SCOPE_INSUFFICIENT",
    "message": "This API key does not have the 'write:products' scope"
  }
}

PLAN_LIMIT_EXCEEDED — 403

Returned when a create operation would exceed the store's plan limit.

{
  "error": {
    "code": "PLAN_LIMIT_EXCEEDED",
    "message": "Plan limit exceeded for products: 100/100"
  }
}

NOT_FOUND — 404

Returned when the requested resource does not exist.

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Resource not found"
  }
}

Also returned for route-not-found (unknown endpoint):

{
  "error": {
    "code": "NOT_FOUND",
    "message": "Route GET /v1/unknown not found"
  }
}

CONFLICT — 409

Returned when a resource already exists (e.g., duplicate name).

{
  "error": {
    "code": "CONFLICT",
    "message": "Resource already exists"
  }
}

VALIDATION_ERROR — 422

Returned when input fails validation. Always includes a details array with per-field errors.

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request parameters",
    "details": [
      { "field": "price", "message": "Number must be greater than 0" },
      { "field": "name", "message": "Required" }
    ]
  }
}

The field property uses dot notation for nested fields (e.g., "address.city").


RATE_LIMITED — 429

Returned when the API key exceeds its per-minute request quota. Includes a Retry-After response header (seconds).

{
  "error": {
    "code": "RATE_LIMITED",
    "message": "Rate limit exceeded. Retry after 23 seconds."
  }
}

Response headers on rate-limit responses:

HeaderValue
X-RateLimit-LimitRequests allowed per minute
X-RateLimit-RemainingRequests remaining in current window
X-RateLimit-ResetUnix timestamp (seconds) when window resets
Retry-AfterSeconds to wait before retrying

INTERNAL_ERROR — 500

Returned for unexpected server-side failures. The message is intentionally generic — internal details are never exposed.

{
  "error": {
    "code": "INTERNAL_ERROR",
    "message": "An unexpected error occurred"
  }
}

Best Practices

  1. Check error.code first, not just the HTTP status — multiple codes can share a status
  2. For VALIDATION_ERROR, iterate error.details to surface field-level messages to users
  3. For RATE_LIMITED, respect the Retry-After header with exponential backoff
  4. Don't retry 4xx errors (except RATE_LIMITED) — they indicate a client issue
  5. Retry 5xx errors with backoff — they indicate temporary server issues
  6. Log error.code + error.message in your error tracking for fast debugging

On this page