> ## Documentation Index
> Fetch the complete documentation index at: https://docs.kynva.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Troubleshooting

> Decision tree for the errors you'll actually hit.

## Decision tree

```
Response status?
├── 2xx → success
├── 401 → fix auth, then retry
├── 402 → top up credits or use kyn_test_ key
├── 403 → fix scope or workspace permission, do NOT retry
├── 404 → fix the ID, do NOT retry
├── 409
│   ├── IDEMPOTENCY_CONFLICT → generate fresh key
│   ├── APPROVAL_REQUIRED    → wait for approval, then retry
│   └── CONFLICT             → resolve state conflict (see message)
├── 429 → honor Retry-After, then retry
├── 4xx with fix_patch → apply patch, retry once
├── 4xx other → fix request, do NOT retry blindly
├── 5xx → exponential backoff, up to 5 retries (same Idempotency-Key)
```

## Common gotchas

<AccordionGroup>
  <Accordion title="I'm seeing UNAUTHORIZED but my key is correct">
    Three things to check:

    1. **Whitespace** — `Bearer  kyn_...` (two spaces) is invalid.
    2. **Wrong environment** — `kyn_test_` keys can't be used against live data, and vice versa. Check the prefix.
    3. **Rotation grace expired** — if you rotated a key, the old one stops working after 24h.
  </Accordion>

  <Accordion title="My render returns RENDER_FAILED only on certain briefs">
    Almost always a font resolution or asset access issue:

    * Did the brief reference a font your BrandKit doesn't have?
    * Is an asset URL in the brief reachable from the public internet?
    * Are you using a private asset ID that belongs to a different brand?

    Inspect the `decisions` log if available (compile API) or include the request\_id when contacting support.
  </Accordion>

  <Accordion title="IDEMPOTENCY_CONFLICT but I'm sending the same body">
    Three usual causes:

    1. **A timestamp in the body changed**. Even a millisecond difference makes the body hash different.
    2. **Field reordering produced different JSON bytes**. Use a canonical serializer (`JSON.stringify` is fine in JS; in Python use `json.dumps(..., sort_keys=True)`).
    3. **You changed the URL** (different query param). The cache is keyed on method + URL + body hash.
  </Accordion>

  <Accordion title="429 every few requests, even at low volume">
    You may be hitting a **per-category** limit you didn't realize you were spending. For example: a polling loop on `GET /facade/render-jobs/{id}` shares the reads bucket with the rest of your app.

    * Switch to webhooks for completion notifications.
    * Reduce poll frequency.
    * See the `X-RateLimit-Category` response header to confirm which bucket you hit.
  </Accordion>

  <Accordion title="SERVICE_UNAVAILABLE during a known maintenance window">
    Check [status.kynva.ai](https://status.kynva.ai). Subscribe to incident updates there so you get pinged before the next one.
  </Accordion>
</AccordionGroup>

## When to contact support

Send `support@kynva.ai` an email with:

* The **`X-Request-Id`** from the failing response (always in headers, also in `error.request_id`).
* The **timestamp** of the request, with timezone.
* The **request body** (redact any PII).
* The **expected vs actual** behavior.

We pull logs by request ID. Without it, debugging takes 10× longer.
