API Notes¶
Compatibility Adaptations (read this first)¶
The verified upstream is the ChatGPT subscription backend, not the public
platform API. AIRelays adapts requests on the three text-generation routes
(/v1/responses, /v1/chat/completions, /v1/completions) rather than
letting them fail. Parameter stripping is always disclosed; other
compatibility normalizations are documented here and may log dedicated
adaptation records when the request shape itself is repaired:
- Removed unsupported parameters:
temperature,top_p,presence_penalty, andfrequency_penaltyare stripped before the upstream call because the upstream rejects them ("Unsupported parameter: temperature"). Output-token limit fields (max_tokens,max_completion_tokens,max_output_tokens) and caller-supplied end-user identifiers (user,safety_identifier) are also stripped because the verified subscription backend does not honor them. The names of removed parameters are returned in thex-airelays-ignored-parametersresponse header and logged as acompatibility_adaptationtraffic record with the reason. Generation runs with the upstream's own defaults. The same adaptation applies on the Claude routes: the localclaudeCLI exposes no sampling or token limit controls, so these parameters are stripped and disclosed there too instead of failing the request. - Reasoning effort:
reasoning_effort(chat completions and completions) andreasoning.effort(responses) are forwarded verbatim to OpenAI models; onclaude:*modelsreasoning_effortmaps to the local CLI's--effortflag on both text routes. An explicit JSONnullis treated as absent. Each model's supported modes and default are published in/v1/modelsunderairelays.reasoning. Unsupported Claude values are rejected with the supported list (the CLI would silently ignore them); unsupported OpenAI values surface the upstream's own error. Requests that omit the parameter run OpenAI models at upstream effortnone— lower than themediumthe official ChatGPT apps use — and Claude models at their adaptive default. - Structured outputs:
response_format.type=json_schemais forwarded (translated) to OpenAI models; onclaude:*chat completions bothjson_schemaandjson_objectare honored via the claude CLI's native--json-schemaenforcement. Supported types per model are published in/v1/modelsunderairelays.structured_output. - Cursor chat-route compatibility: AIRelays accepts two malformed
request families currently observed from Cursor custom OpenAI endpoints
on
/v1/chat/completions: full Responses-style bodies (input,instructions, flattools, etc.) and flat Responses-stylecustomtool definitions / tool choices / assistant tool calls (for example theApplyPatchtool). AIRelays normalizes those requests locally, sends the canonical Responses shape upstream, and translates upstreamcustom_tool_callitems back into chat-completionstool_callson both streaming and non-streaming responses. Mixed-shape requests that contain bothmessagesandinput, or tool outputs that do not reference a preceding assistant tool call in the same request, are rejected loudly. - Rejected loudly instead of adapted:
store=true,n>1, andbest_of/echo/logprobs/suffixon/v1/completions. These change semantics in ways silent stripping would hide, so they return a clear error. - OpenAI model admission: when AIRelays can fetch the live ChatGPT
Codex model catalog for the authenticated OpenAI account(s), OpenAI
requests are forwarded only for model ids that appear in that catalog
or in
[providers.openai].extra_models. Other ids are rejected locally with a clear 422 and suggestions from/v1/models, instead of failing later upstream with an account-scoped unsupported-model error. In multi-account mode, the same cache is invalidated when the enrolled OpenAI account set changes, so admission follows the current shared model intersection instead of a stale catalog snapshot. - Account affinity: with multiple OpenAI accounts, a conversation is
pinned to the account that served its first turn (protects upstream
prompt caching); failover to another account happens only at turn
boundaries, logged as an
account_failovertraffic record. - Upstream failures and automatic retry: failed OpenAI calls are
retried automatically with exponential backoff (
retry_attempts, default 3 retries waiting 5s/20s/60s; each retry re-runs account failover) while no response byte has reached the client — non-streaming requests and the pre-header phase of streaming ones. On final failure the client gets OpenAI-shaped error JSON ({"error": {...}}) with the real HTTP status: the upstream's own code (e.g.server_is_overloaded) and, for quota errors,resets_in_seconds. After a stream has started, failures surface as an in-banddata: {"error": ...}event (chat/completions) or a verbatimresponse.failed/errorevent (responses passthrough); mid-stream failures are not retried. Retries are logged asretry_backofftraffic records (retry_skippedexplains deliberate non-retries), and upstream failure events asupstream_stream_errorrecords.
GET /v1/models¶
Returns an OpenAI-style models list built from the enabled provider runtimes.
- OpenAI models come from the verified ChatGPT subscription backend when that runtime is ready.
- Claude models are explicit
claude:*ids. - models starting with
claude:route to the Claude runtime when it is enabled - other model ids route to the OpenAI runtime when it is enabled
- Each model record includes an
airelaysextension block with provider identity, route capabilities, areasoningblock (parameter, supportedmodes,default), and astructured_outputblock (parameter, supportedtypesforresponse_formaton chat completions). - Successful OpenAI upstream model-list responses are cached in memory for
models_cache_ttl_secondsseconds. The default is 300 seconds;0disables the cache. - Cached OpenAI model lists are scoped to the current local OpenAI auth account and ignored after logout or account changes.
GET /v1/subscription/status¶
Returns a normalized subscription-usage snapshot with per-window usage percentages, window labels ("5h", "weekly", derived from each window's duration), and reset times. Which windows appear is plan-dependent upstream policy; only reported windows are returned.
- default provider is OpenAI (source:
chatgpt.com/backend-api/wham/usage) ?provider=claudereturns Claude subscription usage in the same normalized shape (see Subscription Status)?account=<email-or-prefix>selects one enrolled OpenAI account?all_accounts=truereturns one entry per enrolled OpenAI account (folds to the single-account shape when only one exists)?raw=trueincludes the raw upstream payload (OpenAI only)
GET /v1/account/rate_limits is an alias.
POST /v1/relay/accounts/refresh¶
Re-checks every enrolled OpenAI account's capacity immediately and returns
the refreshed account list. Releases are evidence-gated: an account's
usage-limit hold is lifted only when its fresh usage report shows capacity,
so live traffic can never slip onto a still-exhausted account during the
re-check. Use it when you know an account has recovered and don't want to
wait for the scheduled reset. CLI equivalent: airelays accounts refresh.
GET /v1/relay/status¶
Returns relay diagnostics, provider readiness, provider cache status, and
requests_total — the count of real (non-monitoring) requests served by
this process, usable as a lightweight activity signal. OpenAI model-list
cache diagnostics live under providers.openai.models_cache.
CLI Diagnostics¶
airelays status reports local config, relay-token, and provider readiness
state. airelays doctor runs the same local checks and also probes the OpenAI
upstream /models route plus a tiny /responses smoke request when the OpenAI
runtime is enabled and logged in. Use airelays doctor --skip-response to skip
the response smoke request. airelays models lists every model id the running
relay accepts, grouped by provider (--json supported).
POST /v1/responses¶
OpenAI runtime:
- general OpenAI Responses envelope
stream=true|false- local conversations
- local files and verified
input_fileforms
Current OpenAI limits:
store=truerejected- output-token limit fields rejected explicitly
Claude runtime:
- rejected explicitly on this route
POST /v1/chat/completions¶
OpenAI runtime:
- current AIRelays OpenAI compatibility path
- standard chat-completions
messagesrequests supported - Responses-shaped request bodies also accepted on this route for Cursor compatibility and translated back to chat-completions responses
- tool support includes both OpenAI
functiontools andcustomtools; AIRelays also accepts Cursor's flatter Responses-stylecustomtool shape on this route
Claude runtime:
- explicit
claude:*models only - text-only
system,developer,user, andassistantmessages stream=true|false- no tools
- no files, images, or audio
- no AIRelays local conversation reuse
reasoning_effortsupported (low,medium,high,xhigh,max), mapped to the CLI's--effortflag; omitted means the model's adaptive default- structured outputs supported:
response_format.type=json_schemaandjson_objectmap to the CLI's--json-schemaflag (native schema enforcement;json_objectenforces the permissive{"type": "object"}schema). The responsecontentis the enforced JSON only — a run that produces no schema-conforming output fails loudly instead of returning prose. On streaming requests the JSON text streams as the content deltas. Enforcement runs as an internal tool turn upstream, so schema-enforced calls bill some additional output tokens. Unsupportedresponse_formatshapes (including a missingtype) are rejected with a 422; serialized schemas are capped at 200 KB (they travel on the local CLI's argv). Two documented leniencies versus OpenAI:json_schema.nameis optional (metadata the CLI does not use), andstrictis ignored because the CLI's native enforcement is always strict — clients can only get stricter behavior than asked, never weaker. - sampling parameters stripped and disclosed via
x-airelays-ignored-parameters(theclaudeCLI has no sampling controls); other unsupported generation controls rejected locally
POST /v1/completions¶
OpenAI runtime:
- current AIRelays OpenAI compatibility path
Claude runtime:
- explicit
claude:*models only - text-only prompt-in, text-out
stream=true|false- no files, images, audio, or tools
reasoning_effortsupported (same modes and mapping as chat completions)response_formatrejected loudly (not part of the completions API; ignoring it would silently hand unenforced text to a client that asked for JSON — use/v1/chat/completionsfor structured outputs)- sampling parameters stripped and disclosed via
x-airelays-ignored-parameters; other unsupported generation controls rejected locally
POST /v1/files¶
Local AIRelays file storage for the OpenAI runtime compatibility path.
POST /v1/conversations¶
Local AIRelays conversation storage for the OpenAI runtime compatibility path.
The Claude runtime is stateless and does not use local conversations.
Unsupported Routes¶
These currently return 501 unsupported_error:
- embeddings
- image generation
- audio
- realtime sessions
Claude models are also rejected on any route that is not part of their published subset.