# Error Handling & Retries

The Highflame SDK provides a structured error hierarchy so you can handle each failure mode distinctly. Transient network and server errors are retried automatically.

{% hint style="warning" %}
**`BlockedError` is only raised by `Shield` decorators/wrappers, not by `client.guard.evaluate()`.** Direct evaluate calls always return a `GuardResponse` — inspect `resp.denied` (Python) or `resp.decision === "deny"` (TypeScript) yourself. `BlockedError` carries the full `GuardResponse` as `e.response`.
{% endhint %}

### Error Hierarchy

All SDK errors extend a single base class:

```
HighflameError
├── BlockedError          — Shield decision was "deny"
└── APIError              — HTTP error from the service
    ├── AuthenticationError  — 401 Unauthorized
    └── RateLimitError       — 429 Too Many Requests
APIConnectionError        — Network failure or timeout (extends HighflameError)
```

### Python

```python
from highflame import (
    Highflame,
    GuardRequest,
    HighflameError,
    APIError,
    AuthenticationError,
    RateLimitError,
    APIConnectionError,
    BlockedError,
)

client = Highflame(api_key="hf_sk_...")

try:
    resp = client.guard.evaluate(GuardRequest(
        content=user_input,
        content_type="prompt",
        action="process_prompt",
    ))

except BlockedError as e:
    # Raised only by Shield decorators (@shield.prompt, @shield.tool, etc.)
    # Direct client.guard.evaluate() calls return GuardResponse and never raise on deny.
    print(f"Blocked: {e.response.policy_reason}")
    print(f"Signals: {[s.name for s in e.response.signals]}")

except AuthenticationError as e:
    # 401 — invalid or expired API key
    print(f"Auth failed: {e.status} {e.title} — {e.detail}")

except RateLimitError as e:
    # 429 — request quota exceeded
    print(f"Rate limited: {e.status} {e.title} — {e.detail}")

except APIError as e:
    # Any other non-2xx HTTP response
    print(f"API error {e.status}: {e.title} — {e.detail}")

except APIConnectionError as e:
    # Network failure, timeout, DNS error
    print(f"Connection error: {e}")

except HighflameError as e:
    # Catch-all for any other SDK error
    print(f"SDK error: {e}")
```

#### APIError Attributes

| Attribute | Type  | Description                             |
| --------- | ----- | --------------------------------------- |
| `status`  | `int` | HTTP status code                        |
| `title`   | `str` | Short machine-readable error title      |
| `detail`  | `str` | Human-readable description of the error |

#### BlockedError Attributes

| Attribute  | Type            | Description                              |
| ---------- | --------------- | ---------------------------------------- |
| `response` | `GuardResponse` | The full evaluation response from Shield |

### TypeScript

```typescript
import {
  Highflame,
  HighflameError,
  APIError,
  AuthenticationError,
  RateLimitError,
  APIConnectionError,
  BlockedError,
} from "@highflame/sdk";

const client = new Highflame({ apiKey: "hf_sk_..." });

// Direct client.guard.evaluate() — never throws on deny, always resolves
try {
  const resp = await client.guard.evaluate({
    content: userInput,
    content_type: "prompt",
    action: "process_prompt",
  });

  if (resp.decision === "deny") {
    console.log("Blocked:", resp.policy_reason);
  }

} catch (err) {
  if (err instanceof AuthenticationError) {
    console.error(`Auth failed [${err.status}]: ${err.detail}`);

  } else if (err instanceof RateLimitError) {
    console.error(`Rate limited [${err.status}]: ${err.detail}`);

  } else if (err instanceof APIError) {
    console.error(`[${err.status}] ${err.title}: ${err.detail}`);

  } else if (err instanceof APIConnectionError) {
    console.error(`Connection failed: ${err.message}`);

  } else if (err instanceof HighflameError) {
    console.error(`SDK error: ${err.message}`);
  }
}
```

#### Catching BlockedError from Shield wrappers

```typescript
import { Shield, BlockedError } from "@highflame/sdk";

const shield = new Shield(client);
const chat = shield.prompt(async (msg: string) => llm.complete(msg));

try {
  const reply = await chat(userMessage);
} catch (err) {
  if (err instanceof BlockedError) {
    // err.response is the full GuardResponse
    console.error("Blocked:", err.response.policy_reason);
    return { error: "Request blocked by security policy" };
  }
  throw err;
}
```

{% hint style="info" %}
`BlockedError` is only raised by `Shield` decorators (Python) and `Shield` wrappers (TypeScript). When you call `client.guard.evaluate()` directly, the call always resolves — check `resp.denied` (Python) or `resp.decision === "deny"` (TypeScript) yourself.
{% endhint %}

### Automatic Retries

The SDK automatically retries failed requests on transient errors. Retries use exponential backoff with jitter.

**Retried status codes:** `429`, `500`, `502`, `503`, `504`

**Default retry count:** `2` (3 total attempts)

```python
# Python — configure at client level
client = Highflame(
    api_key="hf_sk_...",
    max_retries=3,   # increase for flaky networks
)

# Disable retries entirely
client = Highflame(api_key="hf_sk_...", max_retries=0)
```

```typescript
// TypeScript — configure at client level
const client = new Highflame({
  apiKey: "hf_sk_...",
  maxRetries: 3,
});

// Disable retries
const client = new Highflame({ apiKey: "hf_sk_...", maxRetries: 0 });
```

`AuthenticationError` (401) is not retried — a 401 indicates a credential problem that retrying will not fix.

### Timeouts

The default per-request timeout is 30 seconds (30,000ms in TypeScript).

```python
# Python — client-level timeout (seconds)
client = Highflame(api_key="hf_sk_...", timeout=10.0)
```

```typescript
// TypeScript — client-level timeout (milliseconds)
const client = new Highflame({ apiKey: "hf_sk_...", timeout: 10_000 });

// Per-request timeout override
const resp = await client.guard.evaluate(request, { timeout: 5_000 });
```

### Handling Rate Limits Manually

If you want to implement your own backoff on top of the SDK's automatic retries:

```python
import time
from highflame import RateLimitError

max_attempts = 5
for attempt in range(max_attempts):
    try:
        resp = client.guard.evaluate(request)
        break
    except RateLimitError:
        if attempt == max_attempts - 1:
            raise
        wait = 2 ** attempt
        time.sleep(wait)
```

```typescript
async function withBackoff(fn: () => Promise<unknown>, maxAttempts = 5) {
  for (let attempt = 0; attempt < maxAttempts; attempt++) {
    try {
      return await fn();
    } catch (err) {
      if (err instanceof RateLimitError && attempt < maxAttempts - 1) {
        await new Promise(resolve => setTimeout(resolve, 2 ** attempt * 1000));
        continue;
      }
      throw err;
    }
  }
}
```

### Self-Hosted Deployments

For self-hosted deployments, `APIConnectionError` indicates the service is unreachable. Check that `base_url` and `token_url` are configured correctly and that the service is healthy.

```python
client = Highflame(
    api_key="hf_sk_...",
    base_url="https://shield.internal.example.com",
    token_url="https://auth.internal.example.com/api/cli-auth/token",
)
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.highflame.ai/api-reference/error-handling-and-retries.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
