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

# Errors & Refunds

> Every error format, status code, and error type the LLMLayer API returns - plus when you are automatically refunded.

## Error Formats

LLMLayer returns errors in two shapes, depending on where the request fails.

### 1. Structured Errors (most endpoint errors)

Endpoint-level failures return a structured envelope inside FastAPI's `detail` field:

```json theme={null}
{
  "detail": {
    "error_type": "validation_error",
    "error_code": "missing_json_schema",
    "message": "json_schema is required when 'json' is in modes. ...",
    "details": { "url": "https://www.ycombinator.com" }
  }
}
```

| Field | Description |
| - | - |
| `error_type` | Error category — see the [table below](#error-types) |
| `error_code` | Machine-readable code, stable per failure case |
| `message` | Human-readable explanation |
| `details` | Optional context (URL, provider, status code, ...) |

### 2. Plain-String Errors (account-level guards)

Account-level checks (API key, balance, rate limit) return a plain string in `detail`:

```json theme={null}
{ "detail": "insufficient funds on LLMLAYER, please top up your account" }
```

These occur **before** your request is processed, so they never incur any charge.

***

## HTTP Status Codes

| Status | Meaning | Charged? | Retry? |
| - | - | - | - |
| `400` | Invalid request — missing or malformed parameter | No | After fixing the request |
| `401` | Missing or invalid API key | No | After fixing the key |
| `402` | Insufficient credits | No | After topping up |
| `403` | Blocked target (private IP, blocked domain or port) | No | No — target not allowed |
| `422` | Unprocessable — e.g. empty page content (refunded) or extraction output truncated | Depends — see [Refunds](#automatic-refunds) | After adjusting the request |
| `423` | Billing reconciliation in progress for this key | No | Yes, after a short wait |
| `429` | Rate limit exceeded for your [tier](/rate-limits) | No | Yes, with backoff |
| `500` | Internal error or upstream fetch failure | Depends — see [Refunds](#automatic-refunds) | Yes |
| `502` | Upstream service failure (e.g. brand profile, model output invalid) | Depends — see [Refunds](#automatic-refunds) | Yes |
| `504` | Operation timeout (e.g. PDF scrape, DNS resolution) | — | Yes |

***

## Error Types

The `error_type` field groups errors into categories:

| `error_type` | Used for | Typical statuses |
| - | - | - |
| `validation_error` | Bad or missing request parameters | 400 |
| `authentication_error` | Missing API key header | 401 |
| `provider_error` | An upstream AI provider failed (rate limit, outage, auth) | 401, 429, 500 |
| `scraping_error` | Scrape/PDF/URL-validation failures (Scraper, PDF, URL checks) | 400, 403, 500, 504 |
| `search_error` | Web search failures (Answer API) | 500 |
| `extraction_error` | Extract API failures (fetch, empty content, truncation, brand) | 422, 500, 502 |
| `map_error` | Map endpoint failures | 4xx/5xx (passes upstream status) |
| `internal_error` | Unexpected server errors | 500 |

<Info>
  Streaming endpoints (`/answer_stream`, `/crawl_stream`) deliver mid-stream failures as SSE frames of the form `{"type": "error", "error": "..."}` instead of HTTP error responses, because the HTTP status is already committed once streaming starts.
</Info>

***

## Automatic Refunds

<Card title="You only pay when the work happens" icon="rotate-left">
  The **Extract API** debits your selected modes up front, then **automatically refunds the full amount** if the request fails before any AI cost is incurred:

  | Failure | Error code | Status | Refunded |
  | - | - | - | - |
  | Target page could not be fetched | `page_fetch_failed` | 500 (or the target's status) | ✅ Full refund |
  | Page returned no extractable content | `empty_page_content` | 422 | ✅ Full refund |
  | Brand profile could not be built | `brand_fetch_failed` | 502 | ✅ Full refund |

  Refunded errors say so explicitly: *"You have not been charged for this request."*
</Card>

<Note>
  Failures that happen **after** AI processing has started (e.g. an upstream model outage, or `output_truncated` when extracted JSON exceeds the size limit) are not refunded, because the model cost was already incurred.
</Note>

Account-level rejections (401, 402, 423, 429) always happen **before** any charge.

***

## SDK Exception Mapping

Both official SDKs raise typed exceptions so you can branch cleanly:

| Condition | Python (`llmlayer.exceptions`) | TypeScript (`llmlayer`) |
| - | - | - |
| `validation_error`, or status 400 / 422 | `InvalidRequest` | `InvalidRequest` |
| `authentication_error`, or status 401 / 403 | `AuthenticationError` | `AuthenticationError` |
| `provider_error` | `ProviderError` | `ProviderError` |
| Status 429 | `RateLimitError` | `RateLimitError` |
| `internal_error`, `scraping_error`, `search_error`, `map_error`, or status ≥ 500 | `InternalServerError` | `InternalServerError` |
| Anything else (e.g. 402 insufficient credits, 423) | `LLMLayerError` (base class) | `LLMLayerError` (base class) |

All exceptions inherit from `LLMLayerError`, so `except LLMLayerError` / `catch` on the base class catches everything.

### Recommended Retry Strategy

<CodeGroup>
  ```typescript TypeScript theme={null}
  import {
    LLMLayerClient,
    InvalidRequest,
    AuthenticationError,
    RateLimitError,
    InternalServerError,
  } from 'llmlayer';

  async function withRetries<T>(fn: () => Promise<T>, maxRetries = 3): Promise<T> {
    for (let attempt = 0; ; attempt++) {
      try {
        return await fn();
      } catch (error) {
        // Never retry: fix the request or the key instead
        if (error instanceof InvalidRequest) throw error;
        if (error instanceof AuthenticationError) throw error;

        // Retry with backoff: rate limits and server-side failures
        const retryable = error instanceof RateLimitError || error instanceof InternalServerError;
        if (!retryable || attempt >= maxRetries - 1) throw error;
        await new Promise(r => setTimeout(r, 2 ** attempt * 1000));
      }
    }
  }
  ```

  ```python Python theme={null}
  import time
  from llmlayer.exceptions import (
      InvalidRequest,
      AuthenticationError,
      RateLimitError,
      InternalServerError,
  )

  def with_retries(fn, max_retries=3):
      for attempt in range(max_retries):
          try:
              return fn()
          except (InvalidRequest, AuthenticationError):
              raise  # never retry: fix the request or the key instead
          except (RateLimitError, InternalServerError):
              if attempt == max_retries - 1:
                  raise
              time.sleep(2 ** attempt)  # backoff, then retry
  ```
</CodeGroup>

***

## Common Error Codes by Endpoint

<AccordionGroup>
  <Accordion title="All endpoints">
    | Code | Status | Meaning |
    | - | - | - |
    | `missing_llmlayer_api_key` | 401 | No `Authorization: Bearer <key>` header |
    | *(plain string)* `LLMLAYER API KEY NOT VALID` | 401 | Key is malformed, revoked, or unknown |
    | *(plain string)* `insufficient funds on LLMLAYER...` | 402 | Credit balance too low — [top up](https://app.llmlayer.ai) |
    | *(plain string)* `rate N/min exceeded on LLMLAYER` | 429 | Over your tier's requests-per-minute |
  </Accordion>

  <Accordion title="Extract API">
    | Code | Status | Refunded |
    | - | - | - |
    | `missing_modes` / `missing_json_schema` / `missing_query` / `pdf_not_supported` | 400 | Nothing charged |
    | `page_fetch_failed` | 500 / target status | ✅ Yes |
    | `empty_page_content` | 422 | ✅ Yes |
    | `brand_fetch_failed` | 502 | ✅ Yes |
    | `output_truncated` | 422 | No (AI ran) — reduce schema fields or page size |
    | `model_json_invalid` | 502 | No (AI ran) — safe to retry |
  </Accordion>

  <Accordion title="Scraper / PDF / Crawl / Map">
    | Code | Status | Meaning |
    | - | - | - |
    | `invalid_url` / `unsupported_scheme` / `credentials_in_url` | 400 | URL malformed or not http(s) |
    | `blocked_domain` / `blocked_ip` / `blocked_port` | 403 | Target not allowed (security policy) |
    | `dns_resolution_failed` | 400 | Host did not resolve |
    | `url_scrape_failed` / `pdf_scrape_failed` | 500 | Upstream fetch failed — retry or use `advanced_proxy` |
    | `pdf_scrape_timeout` | 504 | PDF took too long |
    | `pdf_not_supported` | 400 | PDF URL sent to a non-PDF endpoint — use `/get_pdf_content` |
  </Accordion>

  <Accordion title="Answer API">
    | Code | Status | Meaning |
    | - | - | - |
    | `missing_query` / `missing_model` / `missing_json_schema` | 400 | Required parameter absent |
    | `invalid_search_parameters` | 400 | Bad search\_type/date\_filter combination |
    | `query_generation_error` / `search_context_error` | 500 | Search pipeline failure — retry |
    | `<provider>_rate_limit` / `<provider>_auth_error` / `<provider>_error` | 429 / 401 / 500 | Upstream AI provider issue |
  </Accordion>
</AccordionGroup>

***

## Need Help?

<CardGroup cols={2}>
  <Card title="Discord Community" icon="discord" href="https://discord.gg/EqQF4cjTq5">
    Chat with other developers
  </Card>

  <Card title="Email Support" icon="envelope" href="mailto:support@llmlayer.ai">
    [support@llmlayer.ai](mailto:support@llmlayer.ai)
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.