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

# Error messages

> Diagnose and resolve common Perceptron API errors

# SDK errors

The SDK surfaces metadata for exceptions. All errors inherit from `perceptron.errors.SDKError`.

| Error / `err.code`                                      | Triggers                                                              | Suggested next step                                                                                          |
| ------------------------------------------------------- | --------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| `AuthError` (`auth`)                                    | Missing/invalid API key, revoked org access                           | Re-export `PERCEPTRON_API_KEY`, double-check `PERCEPTRON_PROVIDER`/`BASE_URL`, share the `request_id`.       |
| `BadRequestError` (`bad_request`)                       | Invalid payloads, malformed prompts, unsupported params               | Inspect `err.details["message"]` / offending field; fix input before retrying.                               |
| `ExpectationError` (`expectation_failed`)               | Strict-mode validation (boxes missing anchors, malformed coordinates) | Keep prompts/images ordered, attach `image=` handles, validate coordinates with `result.points_to_pixels()`. |
| `AnchorError` (`anchor_missing`, `bounds_out_of_range`) | Dedicated anchor violations exposed via strict mode                   | Add explicit anchors or clamp/rescale boxes; log `err.details["code"]`.                                      |
| `RateLimitError` (`rate_limit`)                         | 429 from API or provider                                              | Read `err.details["retry_after"]`, back off exponentially, log `request_id`.                                 |
| `TimeoutError` (`timeout`)                              | Client-side deadline exceeded                                         | Increase `config(timeout=...)`, trim uploads, consider `stream=True`.                                        |
| `TransportError` (`transport`)                          | Network failures (DNS, TLS, broken pipes)                             | Retry with jitter; if persistent, verify outbound firewall / proxy.                                          |
| `ServerError` (`server_error`)                          | 5xxs or malformed upstream responses                                  | Retry automatically up to your policy; include `request_id` when contacting support.                         |

Non-fatal semantic issues still land in `PerceiveResult.errors`; flip `strict=True` when you want them raised immediately during prompting and pointing.

## Retrying safely

Wrap SDK calls with targeted exception handling so transient faults (rate limits, network blips) never crash your job while still surfacing actionable diagnostic info.

```python theme={null}
from perceptron import caption
from perceptron.errors import SDKError, RateLimitError
import logging, random, time

logger = logging.getLogger(__name__)

def safe_caption(path):
    for attempt in range(5):
        try:
            return caption(path, style="concise")
        except RateLimitError as err:
            wait = err.details.get("retry_after") or 2 ** attempt + random.random()
            logger.warning(
                "Rate limited (request_id=%s). Retrying in %.1fs",
                err.details.get("request_id"),
                wait,
            )
            time.sleep(wait)
        except SDKError as err:
            logger.error("Perceptron error %s: %s", err.code or "unknown", err)
            logger.debug("details=%s", err.details)
            raise

    raise RuntimeError("Exceeded retries calling caption()")
```

## API error codes

API responses bubble up through the SDK as the error classes above. Use the table below to match symptoms (HTTP status, timeout, etc.) to the remediation you need in client code.

<AccordionGroup>
  <Accordion title="401 Unauthorized">
    **Symptom:** `401 Unauthorized` from API or SDK calls.

    **Fix:** Export `PERCEPTRON_API_KEY` (and `PERCEPTRON_BASE_URL` when self-hosting) or call `configure(api_key="sk_live_...")`. Share `err.details.get("request_id")` with support if the failure persists.
  </Accordion>

  <Accordion title="403 Forbidden">
    **Symptom:** Key is valid but lacks scope.

    **Fix:** Rotate/regenerate the key in the dashboard and ensure the organization has access to the requested provider (`perceptron`, `fal`, etc.).
  </Accordion>

  <Accordion title="429 Rate limit">
    **Symptom:** Too many requests per minute.

    **Fix:** Batch requests and implement exponential backoff using the surfaced `retry_after` seconds.

    ```python theme={null}
    from perceptron.errors import RateLimitError
    import random, time

    def call_with_backoff(fn, *args, **kwargs):
        for attempt in range(5):
            try:
                return fn(*args, **kwargs)
            except RateLimitError as err:
                wait = err.details.get("retry_after") or 2 ** attempt + random.random()
                time.sleep(wait)
        raise RuntimeError("Exceeded retries after RateLimitError")
    ```
  </Accordion>

  <Accordion title="400/422 Validation">
    **Symptom:** Immediate `BadRequestError`, `ExpectationError`, or warnings in `PerceiveResult.errors`.

    **Fix:** Inspect `err.details` (provider message, offending field, request ID) or switch on `strict=True` to surface anchor issues early.
  </Accordion>

  <Accordion title="500 Server error">
    **Symptom:** Transient backend issue.

    **Fix:** Retry with jitter and log `err.details.get("request_id")` when opening support tickets.
  </Accordion>

  <Accordion title="Timeout / connection reset">
    **Symptom:** Long-running uploads or unstable network.

    **Fix:** Resize oversized images (<code>\<=1024px</code> longest edge), prefer `stream=True` to receive incremental tokens, and reduce `config(timeout=...)` so hung connections fail fast.
  </Accordion>
</AccordionGroup>

## Size and context limits

### Oversized images (silent smart-resize)

Images with more than 6,144 patches (roughly anything above Full HD, image-area-wise) are smart-resized server-side before tokenization, preserving aspect ratio. The resize is **not signaled** in the response — `usage.prompt_tokens` reflects the post-resize count, not the original.

**How to detect:** compute the native patch count client-side (`⌈width/16⌉ × ⌈height/16⌉`) and compare against 6,144. If your use case is sensitive to image quality (OCR on small text, fine spatial detection), pre-resize to the target resolution yourself before uploading.

### Context overflow (400 Bad Request)

When the assembled prompt (text + image patches + reserved output tokens) exceeds Mk1's 32K context window, the API returns a `400` with a descriptive message. There is **no dedicated semantic error code** today — clients should pattern-match the message string to detect this case specifically.

**Example response:**

```json theme={null}
{
  "error": {
    "message": "The input (33000 tokens) is longer than the model's context length (32768 tokens).",
    "type": "BadRequestError",
    "code": "400"
  }
}
```

The same shape fires when `input_tokens + max_new_tokens > context_length`, with the message itemizing each term.

## Decision trees with runnable repros

### `invalid_image` (upload/source bytes)

* **Meaning:** the SDK could not decode the bytes you pointed it at before the request ever left your machine.
* **Fix checklist:**
  1. Make sure the path/bytes you pass into `caption()/detect()/ocr()` is a real bitmap (PNG/JPEG/WebP). When dealing with streams, flush the handle before re-reading it.
  2. Inspect `err.details['origin']` to confirm which file triggered the failure and `err.details['request_id']` (if the request made it to the API).
  3. If the asset lives on disk, open it with Pillow or Preview to confirm it is not truncated; if it is an HTTP URL, download it locally and retry.

```python theme={null}
from perceptron import caption
from perceptron.errors import SDKError

try:
    caption("/path/to/bad.docx", expects="text")  # intentionally not an image
except SDKError as err:
    origin = err.details.get("origin")
    request_id = err.details.get("request_id")
    print(f"Origin={origin}, request_id={request_id}")
    raise
```

### `anchor_missing` / `bounds_out_of_range` (strict pointing)

* **Meaning:** a `box()/point()/polygon()` tag is missing an explicit `image=` reference when multiple images exist, or the coordinates fall outside the detected width/height.
* **Fix checklist:**
  1. When using `@perceive(..., strict=True)` always pass the image node into every tag: `box(..., image=image_node)`. In single-image prompts, ensure the image appears before any tags.
  2. Use `result.points_to_pixels(width, height)` to sanity-check coordinate math before switching to strict mode.
  3. Log `err.details['code']` (either `anchor_missing` or `bounds_out_of_range`) to branch your retry logic.

```python theme={null}
from perceptron import box, perceive, text
from perceptron.errors import AnchorError

@perceive(expects="box", strict=True)
def missing_anchor():
    return text("Mark the defect") + box(0, 0, 10, 10)

try:
    missing_anchor()
except AnchorError as err:
    print(err.code, err.details)
```
