Skip to main content
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:
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.
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:
let textModels = try await client.models(
    filter: ChappieModelFilter(
        supportedInAPI: true,
        inputModality: "text"
    )
)
ChappieModelFilter accepts any combination of:
supportedInAPI
Bool?
Include only models the API accepts for ChappieResponseRequest. Set this to true for any model you plan to use with send or stream.
inputModality
String?
Filter by input modality string, e.g. "text". A model is included if its inputModalities array contains this value.
supportsSearchTool
Bool?
Include only models that support (or don’t support) the web search tool.
supportsParallelToolCalls
Bool?
Include only models that support (or don’t support) parallel tool calls.
reasoningLevel
String?
Include only models that list the given reasoning effort level in their supportedReasoningLevels array.

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.
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:
// 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:
PropertyTypeDescription
slug / idStringModel identifier used in API requests. id is an alias for slug.
displayNameStringHuman-readable name for display in your UI.
descriptionString?Optional longer description of the model’s strengths.
supportedInAPIBoolWhether the model can be used in ChappieResponseRequest.
priorityIntServer-assigned ranking. Higher values appear first in the sorted list.
contextWindowInt?Maximum context size in tokens, if reported by the server.
supportedReasoningLevels[String]Reasoning effort levels the model accepts (e.g. ["minimal", "low", "medium", "high"]).
supportsSearchToolBoolWhether the model can use the web search tool.
supportsParallelToolCallsBoolWhether 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:
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:
  • primaryChappieUsageLimitWindow with usedPercent, windowMinutes, and resetsAt.
  • secondary — An additional window some plans expose (e.g., a monthly cap alongside an hourly cap).
  • creditsChappieCreditsSnapshot 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:
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):
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:
rawValuedisplayName
freeFree
goGo
plusPlus
proPro
Unknown raw values are title-cased automatically (my_custom_planMy 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.
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:
plan
ChappiePlan?
The plan associated with the limit that was reached.
resetsAt
Date?
When the quota window resets. Display this to users so they know how long to wait.
resetsInSeconds
TimeInterval?
The same reset as a countdown in seconds, when available.
message
String?
A human-readable message from the server describing the limit.
activeLimit
String?
The name of the active limit bucket that was reached (sourced from the x-codex-active-limit response header).
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.