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

# Selecting Models and Monitoring Your Usage in Chappie

> Fetch the available model list, switch models at runtime, read usage-limit snapshots, and handle plan-level rate limits with built-in error payloads.

Chappie gives you programmatic control over which model handles each conversation and live visibility into how much of your usage quota remains. Models are fetched from the server at runtime, so your app always reflects the latest available options rather than a hard-coded list. Usage data arrives both as an on-demand snapshot from the rate-limits endpoint and as a mid-stream event during active responses.

## Default Model

The default model is `gpt-5.5`. `ChappieClient.defaultModel` exposes this constant if you want to reference it programmatically:

```swift theme={null}
let defaultSlug = ChappieClient.defaultModel  // "gpt-5.5"
```

## Listing Available Models

Call `models()` to fetch the server's current model list. The results are sorted by the server-assigned `priority` field, so the best available model appears first.

```swift theme={null}
let models = try await client.models()
for model in models {
    print(model.displayName, model.slug)
}
```

### Filtering Models

Pass a `ChappieModelFilter` to narrow the list to models that match your requirements:

```swift theme={null}
let textModels = try await client.models(
    filter: ChappieModelFilter(
        supportedInAPI: true,
        inputModality: "text"
    )
)
```

`ChappieModelFilter` accepts any combination of:

<ParamField path="supportedInAPI" type="Bool?">
  Include only models the API accepts for `ChappieResponseRequest`. Set this to `true` for any model you plan to use with `send` or `stream`.
</ParamField>

<ParamField path="inputModality" type="String?">
  Filter by input modality string, e.g. `"text"`. A model is included if its `inputModalities` array contains this value.
</ParamField>

<ParamField path="supportsSearchTool" type="Bool?">
  Include only models that support (or don't support) the web search tool.
</ParamField>

<ParamField path="supportsParallelToolCalls" type="Bool?">
  Include only models that support (or don't support) parallel tool calls.
</ParamField>

<ParamField path="reasoningLevel" type="String?">
  Include only models that list the given reasoning effort level in their `supportedReasoningLevels` array.
</ParamField>

## Selecting a Model

Change the active model for a client instance at any time. All subsequent turns will use the new model until you change it again.

```swift theme={null}
await client.selectModel("gpt-5.5")
let selected = await client.selectedModel()
```

`selectModel(_:)` is `@discardableResult` — it returns the new `ChappieClientContext` snapshot, but you can ignore it if you only need the side effect.

## Best Available and Fallback Helpers

Two convenience methods resolve a model from the live list without you having to iterate manually:

```swift theme={null}
// First API-accessible text model, sorted by priority
let best = try await client.bestAvailableModel()

// The preferred slug if available, otherwise the first result
let fallback = try await client.fallbackModel(preferred: "gpt-5.5")
```

Both methods default to filtering for `supportedInAPI: true` and `inputModality: "text"`. Pass a custom `ChappieModelFilter` to change the criteria.

## ChappieModel Properties

Each `ChappieModel` in the returned array exposes the following properties:

| Property                    | Type       | Description                                                                              |
| --------------------------- | ---------- | ---------------------------------------------------------------------------------------- |
| `slug` / `id`               | `String`   | Model identifier used in API requests. `id` is an alias for `slug`.                      |
| `displayName`               | `String`   | Human-readable name for display in your UI.                                              |
| `description`               | `String?`  | Optional longer description of the model's strengths.                                    |
| `supportedInAPI`            | `Bool`     | Whether the model can be used in `ChappieResponseRequest`.                               |
| `priority`                  | `Int`      | Server-assigned ranking. Higher values appear first in the sorted list.                  |
| `contextWindow`             | `Int?`     | Maximum context size in tokens, if reported by the server.                               |
| `supportedReasoningLevels`  | `[String]` | Reasoning effort levels the model accepts (e.g. `["minimal", "low", "medium", "high"]`). |
| `supportsSearchTool`        | `Bool`     | Whether the model can use the web search tool.                                           |
| `supportsParallelToolCalls` | `Bool`     | Whether the model can execute multiple tool calls in a single turn.                      |
| `inputModalities`           | `[String]` | Input types the model accepts, e.g. `["text"]`.                                          |

## Checking Usage Limits

Call `usageLimits()` to fetch a live `ChappieUsageLimitSnapshot` from the rate-limits endpoint:

```swift theme={null}
let usage = try await client.usageLimits()
if let primary = usage.primary {
    print("Used: \(primary.usedPercent)%")
    if let resetsAt = primary.resetsAt {
        print("Resets at: \(resetsAt.formatted())")
    }
}
```

`ChappieUsageLimitSnapshot` has three window fields:

* **`primary`** — `ChappieUsageLimitWindow` with `usedPercent`, `windowMinutes`, and `resetsAt`.
* **`secondary`** — An additional window some plans expose (e.g., a monthly cap alongside an hourly cap).
* **`credits`** — `ChappieCreditsSnapshot` with `hasCredits`, `unlimited`, and `balance`.

You also receive a `.usageLimits(snapshot)` event mid-stream during active responses, so you can keep a live counter without an extra network call.

## Account and Plan Info

Read account details and the current plan from the locally cached credential:

```swift theme={null}
let account = try client.account()
print(account.plan?.displayName ?? "Unknown")
print(account.email ?? "No email")
```

To refresh the plan from the server (which calls `usageLimits()` internally and updates the stored credential):

```swift theme={null}
let refreshed = try await client.refreshAccount()
print(refreshed.plan?.displayName ?? "Unknown")
```

### ChappiePlan Values

Plans are represented by `ChappiePlan`, a struct with `rawValue` and `displayName` properties. The SDK maps the following raw values to display names:

<Tabs>
  <Tab title="Consumer">
    | `rawValue` | `displayName` |
    | ---------- | ------------- |
    | `free`     | Free          |
    | `go`       | Go            |
    | `plus`     | Plus          |
    | `pro`      | Pro           |
  </Tab>

  <Tab title="Business & Enterprise">
    | `rawValue`   | `displayName` |
    | ------------ | ------------- |
    | `team`       | Team          |
    | `business`   | Business      |
    | `enterprise` | Enterprise    |
    | `edu`        | Edu           |
  </Tab>
</Tabs>

Unknown raw values are title-cased automatically (`my_custom_plan` → `My Custom Plan`).

## Handling Usage Limit Errors

When a request is rejected because the user has hit their quota, the SDK throws `ChappieClientError.usageLimitReached(_:)` with a `ChappieUsageLimit` payload. The payload includes the plan, a reset timestamp, and a human-readable message.

```swift theme={null}
do {
    let reply = try await client.send("Hello")
} catch ChappieClientError.usageLimitReached(let limit) {
    print(limit.plan?.displayName ?? "Unknown plan")
    print(limit.resetsAt as Any)
    print(limit.message as Any)
}
```

`ChappieUsageLimit` exposes:

<ParamField path="plan" type="ChappiePlan?">
  The plan associated with the limit that was reached.
</ParamField>

<ParamField path="resetsAt" type="Date?">
  When the quota window resets. Display this to users so they know how long to wait.
</ParamField>

<ParamField path="resetsInSeconds" type="TimeInterval?">
  The same reset as a countdown in seconds, when available.
</ParamField>

<ParamField path="message" type="String?">
  A human-readable message from the server describing the limit.
</ParamField>

<ParamField path="activeLimit" type="String?">
  The name of the active limit bucket that was reached (sourced from the `x-codex-active-limit` response header).
</ParamField>

<Warning>
  Rate-limit errors on streaming responses are thrown from the `for try await` loop, not returned as an event. Always wrap streaming iterations in a `do/catch` and handle `ChappieClientError.usageLimitReached` alongside your other error cases.
</Warning>
