{✦} Stylumia Developers

Error handling

The shape

Every error is application/problem+json, not a bespoke body per endpoint:

{
  "type": "https://developer-api.stylumia.net/errors#unknown_category",
  "title": "Unknown category",
  "status": 400,
  "detail": "'power tool' is not a recognized category for retailer 'home_depot'.",
  "code": "unknown_category",
  "retryable": false,
  "recovery": "Call GET /v1/coverage?retailer=... to list that retailer's categories, or use one of the suggestions in valid_options.",
  "field": "category",
  "valid_options": ["power_tools", "hand_tools", "tool_storage"],
  "request_id": "req_9f2c31a4d8e0176b3f52"
}

unknown_retailer, unknown_category, and unknown_department share this exact shape — only field, detail, and valid_options differ by which parameter was wrong. All three are the vocabulary lookup this API has, since there's no separate endpoint listing retailers, categories, or departments on their own; see Coverage & RCD combinations.

Member What it's for
type A URI that resolves to this code's entry on /errors
title Human-readable name — for display, not for branching
status The HTTP status code
detail Human prose about this specific occurrence — wording may change over time
code The stable identifier — branch on this
retryable false = fix the request; true = back off and retry
recovery What to do instead, in one sentence
field Which parameter caused it, when applicable
valid_options Close real values, when the error is about a guessed vocabulary term
request_id This request's id — quote it to support

Branch on code, never detail

detail is written for a person reading a log. Its wording can change without notice — it is not a contract. code is: it's enumerated at /errors, append-only, and every type URI resolves there. If you're writing if "not a recognized" in detail: anywhere, stop — match code == "unknown_category" instead.

New codes may appear over time as the API grows. Treat a code you don't recognize by its status class rather than failing closed on it.

Three response classes

Class Status Meaning What to do
Fix the request 4xx (except 429) The request itself is wrong Read recovery and valid_options, fix it, resend — don't retry unchanged
Retry with backoff 429, 5xx Transient — rate limit or our side Back off and retry; honor Retry-After when present
Ask a human 403 Your team lacks the entitlement Check your team's entitlement — retrying won't fix this

The retryable recipe

attempts = 0
while attempts < 5:
    resp = call()
    if resp.ok:
        break
    body = resp.json()
    if not body.get("retryable"):
        raise Exception(body["detail"])  # fix the request instead
    sleep_for = min(2 ** attempts + random.uniform(0, 1), 60)
    time.sleep(sleep_for)
    attempts += 1

All /v1 endpoints are reads — every request is safe to repeat, with or without this loop. There are no idempotency keys to manage.

recovery and valid_options in practice

Guess a category that doesn't exist and you get exactly the error shown above: unknown_category, with valid_options holding the top five real values for the retailer you asked about. A five-line client reads it and retries without a human involved:

resp = requests.get(url, headers=headers)
if resp.status_code == 400 and resp.json()["code"] == "unknown_category":
    fixed = resp.json()["valid_options"][0]
    url = url.replace(f"category={bad_category}", f"category={fixed}")
    resp = requests.get(url, headers=headers)

Trigger one on purpose

Errors are safe to trigger on purpose — every endpoint here is a read. Call the same endpoint with a junk category and watch the shape:

curl "https://api.stylumia.com/v1/catalog?retailer=home_depot&category=not_a_real_category" \
  -H "Authorization: Bearer $STYLUMIA_KEY"
  # Never commit your key — see /docs/key-security

That's a deliberate, safe way to see recovery and valid_options before you need them in production code.

The full catalogue — every code, its status, causes, and extension members — is at /errors. Each code here resolves to an anchor there.

View as markdown