# AIRelays

AIRelays is a local OpenAI-compatible HTTP server with provider-scoped runtimes. The default runtime uses an AIRelays-owned ChatGPT subscription login and balances requests across multiple of your own OpenAI accounts by default. The Claude runtime balances your own subscriptions through AIRelays-owned local `claude` CLI profiles (never your personal Claude Code sign-in) for `claude:*` and `claude-*` models, with chat, completions and Responses, images, files, conversations and tool calls (see docs/capabilities.md). AIRelays protects `/v1/*` and `/no-tools/v1/*` with its own relay bearer token by default and can run all enabled providers in open local relay mode. A cross-platform desktop app (macOS, Windows, Linux) wraps the same relay with a system tray and dashboard.

## Key Docs

- [README.md](README.md): overview, install (CLI and desktop), quick start, compatibility layer
- [docs/getting-started.md](docs/getting-started.md): setup, custom CLI configuration, multi-account sign-in, verification
- [docs/configuration.md](docs/configuration.md): config file shape, environment overrides, and traffic-log retention
- [docs/security.md](docs/security.md): relay auth, open local relay mode, Claude guardrails
- [docs/capabilities.md](docs/capabilities.md): what works on GPT models and on Claude models, side by side
- [docs/api.md](docs/api.md): routes, compatibility adaptations, provider limits
- [docs/architecture.md](docs/architecture.md): request flow and module boundaries
- [docs/subscription-status.md](docs/subscription-status.md): usage reporting for OpenAI and Claude, desktop account details via **?**
- [docs/faq.md](docs/faq.md): common questions and limits
- [docs/troubleshooting.md](docs/troubleshooting.md): symptoms, causes, fixes
- [docs/adr/README.md](docs/adr/README.md): durable design decisions
- [desktop/README.md](desktop/README.md): combined connection/access card, hide-on-blur dashboard, tray controls, build, supervision
- [DISCLAIMER.md](DISCLAIMER.md): independence and intended use

## Install

- Headless relay and CLI (macOS, Linux): `curl -fsSL https://raw.githubusercontent.com/lpalbou/AIRelays/main/scripts/install-headless.sh | bash`
- Desktop app (macOS Apple Silicon, Linux x86_64): `curl -fsSL https://raw.githubusercontent.com/lpalbou/AIRelays/main/scripts/install-desktop.sh | bash`
- Desktop app (Windows x64, PowerShell): `irm https://raw.githubusercontent.com/lpalbou/AIRelays/main/scripts/install-desktop.ps1 | iex`
- PyPI: `python -m pip install airelays`; `AIRELAYS_VERSION=X.Y.Z` pins a release for any installer

## Main Commands

- `airelays init` / `airelays init --no-auth`
- `airelays login` (additive; repeat to enroll more accounts) / `airelays login --device` (headless)
- `airelays logout <email>` / `airelays accounts` (list, order, refresh)
- `airelays serve` / `airelays serve --no-auth`
- `airelays status` / `airelays doctor` / `airelays models` / `airelays logs`
- `airelays token show` / `airelays token rotate`
- `airelays claude set-token` (optional headless setup token; account `default`, inference-only)
- `airelays claude login` (additive) / `airelays claude accounts`
- `airelays claude login --replace ACCOUNT` / `airelays claude logout ACCOUNT` (or `--all`)
- `claude setup-token` (on any machine with a browser, before `airelays claude set-token`)

## Important Notes

- Shared CLI options (`--config`, `--data-dir`, `--logs-dir`, `--auth-storage`, `--bearer-token-file`) work before or after subcommands; a later explicit value wins. `--auth-storage` is accepted for compatibility only.
- Sign-ins (OpenAI, relay token, Claude setup token) are owner-only files in the data directory on every OS; AIRelays does not use the OS keychain. OpenAI sign-ins that 0.16.0 or earlier kept in the keychain need a new sign-in (`airelays doctor` lists them under `openai_keyring_sign_ins`).

- Traffic logs default to 7 days, 1024 MiB total, and hourly/50 MiB rotation. `airelays logs`, GET/PUT `/v1/relay/logging`, and tray Settings inspect or change the saved policy; see `docs/configuration.md` for limits and permanent-deletion behavior.

- OpenAI runtime: first-class `responses`, `chat.completions`, and `completions`
- Claude runtime: `chat.completions`, `completions` and `responses`; images, PDFs and text files as input; local files and conversations; function tool calls returned to the application (AIRelays never runs a tool; forced calls are refused; strict tools run non-strict, disclosed)
- Claude uses only AIRelays-owned accounts: browser profiles with their own `CLAUDE_CONFIG_DIR` under `~/.airelays/claude/accounts/<id>/`, plus an optional setup token (`default`, inference-only, no usage). The personal Claude Code sign-in (`~/.claude`), an inherited `CLAUDE_CONFIG_DIR`, and `CLAUDE_CODE_OAUTH_TOKEN` are ignored; with no account, `claude:*` requests return 503 `provider_auth_error`. Signing out never touches Claude Code. Selection honors fresh usage and model eligibility, with failover only before response bytes reach the client.
- Errors use OpenAI's error object (`{"error": {message, type, param, code}}` plus a `detail` copy); unknown models return 404 `model_not_found`; quota rejections carry `Retry-After`; Claude streams start only once the CLI produces output. Non-empty `stop`, `logprobs`, `top_logprobs` and `logit_bias` are rejected with 422 (neither upstream honors them); `seed` is stripped and disclosed. `finish_reason` includes `content_filter`; refusals are returned in `message.refusal`. Unrecognized chat/completions parameters are dropped and listed in `x-airelays-ignored-parameters`; every response carries `x-request-id`
- Claude failures are classified before any account is paused: request errors pause nothing, a model one account cannot serve pauses only that model there (`unavailable_models` in relay status), a rejected sign-in marks the account as needing a new sign-in; messages name each account's state and the action to take
- Chat accepts `content: null` with `tool_calls`, `file` content parts, `allowed_tools`, `verbosity`, `prompt_cache_key` and the deprecated `functions` interface; `json_schema` is rewritten to strict form only with `strict: true`; conversations keep their whole history; `GET /v1/files` paginates like OpenAI; `GET /v1/models/{model}` returns one model
- Chat and completions responses report `usage` in OpenAI's Chat Completions shape on both runtimes (`prompt_tokens` includes cached tokens; details only when reported; omitted when unknown); with `stream_options.include_usage` chunks carry `usage: null` and a final `choices: []` chunk holds the usage; `/v1/responses` keeps Responses usage
- One launcher starts every `claude` process: it finds `claude` on PATH and in the usual install locations (path used as found), or uses `[providers.claude].command` / `AIRELAYS_CLAUDE_COMMAND` (JSON array) as a full argv; Windows requires a real `claude.exe` and refuses npm `.cmd` shims; system prompts go through a private file when supported; over-long arguments return 413; `doctor`/`status` show the CLI path
- models starting with `claude:` or discovered concrete `claude-*` ids route to the Claude runtime when it is enabled
- model discovery follows the installed Codex client version and Claude CLI catalog; `GET /v1/models?refresh=true` bypasses provider caches, and Claude alias resolutions appear in `airelays.resolved_model`
- OpenAI catalogs form a union; balancing, affinity, and failover stay inside the requested model's supporting account subset; `airelays.account_availability` exposes coverage counts and `catalog_visibility` preserves upstream-hidden status
- other model ids route to the OpenAI runtime when it is enabled
- Claude runtime follows the relay's protected or open local auth mode and requires loopback binding
- `GET /v1/subscription/status` reports OpenAI usage by default and Claude usage with `?provider=claude`, both in one normalized shape; `?all_accounts=true` always returns the per-account list, with a per-account `error` when a probe fails
- Claude usage includes explicitly named scoped caps (including Fable), credit/spend metadata with declared currency scales, and fetch timestamps; scoped exhaustion does not imply all-model exhaustion, and quota percentages are not token counts
- Claude usage comes from each profile's Claude Code via the SDK `get_usage` control request (no inference; verified with 2.1.292+), refreshed every five minutes; the relay never reads Claude credentials. Readings without live data keep the last good values marked stale with `stale_reason` (`usage_unavailable`, `nonessential_traffic_disabled`, `cli_unsupported`, `cli_failed`) and never unblock or rank an account
- the upstream rejects `temperature`/`top_p`/penalty parameters; the relay strips them on both runtimes and discloses it via the `x-airelays-ignored-parameters` header. Output-token limits (`max_completion_tokens`, `max_tokens`, `max_output_tokens`) are honoured on both runtimes: GPT models enforce them upstream (at least 16), and a Claude run stops at the limit; a cut answer reports `finish_reason: "length"`.
- `reasoning_effort` works on both runtimes; each model's supported modes and default are published in `/v1/models` under `airelays.reasoning`, using provider catalog metadata when available
- `response_format` structured outputs work on chat completions for both runtimes (Claude: `json_schema` enforced natively by the CLI's `--json-schema`, `json_object` checked to be one JSON object; OpenAI: `json_schema` and native `json_object`); supported types per model are published under `airelays.structured_output`
- failed upstream OpenAI calls retry automatically with exponential backoff (default 3 retries, 5s/20s/60s; configurable, `0` disables) before returning an OpenAI-shaped error with the real HTTP status
- `airelays doctor` runs local setup checks plus live OpenAI `/models` and optional `/responses` probes

## Optional

- [CHANGELOG.md](CHANGELOG.md): release history
- [ACKNOWLEDGEMENTS.md](ACKNOWLEDGEMENTS.md): credits
- [CONTRIBUTING.md](CONTRIBUTING.md): contributor workflow
- [SECURITY.md](SECURITY.md): vulnerability reporting
