# AnchorShell public technical documentation AnchorShell builds self-hostable and managed infrastructure for AI applications. Its first product, Relay, is an LLM gateway for routing, queueing, limits, and observability. Self-host Relay for free. Get ordered model routing, queueing, pacing, fallback, guardrails, and realtime observability—or start with our hosted free tier. Canonical documentation: https://anchorshell.com/docs/getting-started Hosted OpenAPI: https://anchorshell.com/openapi.json Self-hosted inference OpenAPI: https://anchorshell.com/openapi-self-hosted.json --- # Getting Started Canonical HTML: https://anchorshell.com/docs/getting-started Add a provider, choose a model, and send your first request with hosted AnchorShell Relay. Last updated: 2026-09-17 Relay sits between your application and its model providers. Start with one provider and model, then create a Group when you want a stable request name with ordered alternatives. ## Dashboard 1. [Sign in to AnchorShell](https://app.anchorshell.com/login). 2. Open **Model Relay → Setup**. 3. Enter your provider's name, base URL, API key, and model ID. 4. Click **Save and Continue**. 5. Add the model to a group if you want one name for several models. 6. Review limits and pricing. Send the test request. 7. Open **Playground** to edit the request and inspect the response. The order is **provider → model → optional group → request**. Your provider supplies the model and charges for its use. ## API Create an inference key in **Account → API keys**. Replace `` below; see [API authentication](https://anchorshell.com/docs/api) for optional management scopes. ### Direct model Use the call name from Providers. This example assumes you configured and enabled this model. ```bash curl -X POST 'https://api.anchorshell.com/v1/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "model": "openai/gpt-5-mini", "messages": [ { "role": "user", "content": "Hello" } ] }' ``` **Response — 200 OK** Example non-streaming response (selected fields). Content, model, and usage depend on the selected provider. ```json { "id": "", "object": "chat.completion", "model": "gpt-5-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Relay waits for the preferred model before considering fallback." }, "finish_reason": "stop" } ] } ``` ### Group After creating `agentic`, select it in the same `model` field: ```bash curl -X POST 'https://api.anchorshell.com/v1/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "model": "agentic", "messages": [ { "role": "user", "content": "Hello" } ] }' ``` **Response — 200 OK** Example non-streaming response (selected fields). Content, model, and usage depend on the selected provider. ```json { "id": "", "object": "chat.completion", "model": "gpt-5-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Relay waits for the preferred model before considering fallback." }, "finish_reason": "stop" } ] } ``` No routing header or query parameter is needed. Configure setup through [Providers](https://anchorshell.com/docs/providers) and [Groups](https://anchorshell.com/docs/groups). ## Next [Self-hosted](https://anchorshell.com/docs/self-hosting). --- # Self-hosted Canonical HTML: https://anchorshell.com/docs/self-hosting Install AnchorShell Relay, find your local keys, and send a request from your own machine. Last updated: 2026-09-17 ## Install and start You need Git, Make, Go 1.26.4 or a newer Go 1.26 patch, and Node.js 22.19.0 with npm. ### Build from source ```bash git clone https://github.com/anchorshell/bouncer.git anchorshell-relay cd anchorshell-relay make deps make build ./bin/relay ``` Or run `make run` after `make deps`; it builds the dashboard and starts Relay. For development with hot reload: ```bash make install-dev-tools make dev ``` The development dashboard uses `http://localhost:3030`. The API uses `http://localhost:11730`. There is no supported NPX installer in the current source. ## Open the dashboard 1. Open `http://localhost:11730` after starting the built app. 2. Enter the generated management token. 3. Open **Setup**. 4. Add your provider, credential, and model. 5. Add a group if needed. 6. Open **Playground** and enter your inference token to send a test request. For the guided flow, use [Setup](https://anchorshell.com/docs/setup). ## Local configuration First startup creates `.env` and prints newly generated keys once. | Setting | Purpose | | --- | --- | | `RELAY_API_TOKEN` | Authenticates inference requests | | `RELAY_ADMIN_TOKEN` | Unlocks the dashboard and management API | | `RELAY_MASTER_KEY` | Protects saved provider credentials | Relay stores local configuration and usage data in `relay.db`. Keep the database and master key backed up together. Do not share or commit `.env`. Set `RELAY_HTTP_ADDR=127.0.0.1:11730` to listen only on your machine. The default listens on all interfaces. Use HTTPS and network access controls for remote access. An explicitly empty `RELAY_API_TOKEN=` disables inference authentication; do not use that on a public deployment. ## Send a request Replace the token placeholder with your inference token and use a Group you configured: ```bash curl -X POST 'http://localhost:11730/v1/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "model": "agentic", "messages": [ { "role": "user", "content": "Hello" } ] }' ``` **Response — 200 OK** Example non-streaming response (selected fields). Content, model, and usage depend on the selected provider. ```json { "id": "", "object": "chat.completion", "model": "gpt-5-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Relay waits for the preferred model before considering fallback." }, "finish_reason": "stop" } ] } ``` For a direct model, use its `provider/model` call name instead. ## Management API ```bash curl 'http://localhost:11730/api/providers' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "id": "", "name": "OpenAI", "slug": "openai", "base_url": "https://api.openai.com/v1", "auth_mode": "bearer_static", "enabled": true } ] ``` The management and inference tokens are separate. See [API authentication](https://anchorshell.com/docs/api#self-hosted) for using the feature examples locally. ## Next [API](https://anchorshell.com/docs/api). --- # API Canonical HTML: https://anchorshell.com/docs/api Authenticate hosted management and inference calls with scoped API keys. Use separate tokens for self-hosted Relay. Last updated: 2026-09-17 Use an API key to call AnchorShell without the dashboard. ## Hosted 1. Open **Account → API keys**. 2. Name your key. Enable **Allow management API access using my current permissions** if you need management operations; leave it unchecked for inference only. 3. Create the key and copy its secret once. ```bash curl 'https://api.anchorshell.com/v1/models' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Example enabled model; the catalog depends on your configuration. ```json { "object": "list", "data": [ { "id": "gpt-5-mini", "object": "model", "owned_by": "anchorshell-relay" } ] } ``` The same key can authenticate management calls when its scopes permit them: ```bash curl 'https://api.anchorshell.com/api/relay/providers' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "id": "", "name": "OpenAI", "slug": "openai", "base_url": "https://api.openai.com/v1", "auth_mode": "bearer_static", "enabled": true } ] ``` Authorization uses the key owner's current user, organization, and permissions, narrowed by the key's selected scopes. Keys cannot grant access their owner lacks. Existing inference-only keys stay inference-only; create a management-enabled key when needed. The public API origin is `https://api.anchorshell.com`: inference uses `/v1/*`, Relay management uses `/api/relay/*`, and account/team operations use `/api/account/*` and `/api/team/*`. ## Self-hosted Use the independent inference token: ```bash curl 'http://localhost:11730/v1/models' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Example enabled model; the catalog depends on your configuration. ```json { "object": "list", "data": [ { "id": "gpt-5-mini", "object": "model", "owned_by": "anchorshell-relay" } ] } ``` Use the management token for configuration: ```bash curl 'http://localhost:11730/api/providers' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "id": "", "name": "OpenAI", "slug": "openai", "base_url": "https://api.openai.com/v1", "auth_mode": "bearer_static", "enabled": true } ] ``` For self-hosted equivalents of Relay management examples, replace `https://api.anchorshell.com/api/relay` with `http://localhost:11730/api` and use ``. Hosted account/team APIs and Smart Groups are not standalone features. ## Errors - **401 Unauthorized:** missing, invalid, expired, disabled, or revoked API key. - **403 Forbidden:** the key or its owner lacks permission, or the plan does not allow the operation. - **400 Bad Request:** invalid input. **404 Not Found:** a resource is absent from your permitted scope where that operation supports a not-found response. - **409 Conflict:** for example, a conflicting resource or an invitation beyond available team seats. - Inference may also return **429** for capacity constraints or **5xx** for unavailable routing/upstream services. Inspect the response; do not blindly retry mutations. Example hosted authentication failure: ```json { "error": "authentication required" } ``` Feature examples show successful responses. IDs are placeholders and samples marked “selected fields” are abbreviated, not exhaustive schemas. A **204 No Content** response has no JSON body. ## Reference [Hosted OpenAPI](https://anchorshell.com/openapi.json) · [Self-hosted inference OpenAPI](https://anchorshell.com/openapi-self-hosted.json). Password/email changes, session logout, and staff-only operations still require a browser session. API keys do not perform browser sign-in. ## Next [Dashboard](https://anchorshell.com/docs/dashboard). --- # Dashboard Canonical HTML: https://anchorshell.com/docs/dashboard Check Relay setup, current activity, model health, and usage at a glance. Last updated: 2026-09-17 The Dashboard brings setup readiness, current request activity, and model health together. Use it to find the next action, with detailed history available in Usage and Logs. ## Dashboard 1. Open **Dashboard**. 2. Check setup progress and follow any unfinished setup step. 3. Review request activity, model health, and usage. 4. Open the related page when a model or limit needs attention. An empty history is normal before your first request. Use **Setup** to connect a provider, then send a request in **Playground**. ## API Use an appropriately scoped [API key](https://anchorshell.com/docs/api) for these requests. ### Read the request summary ```bash curl 'https://api.anchorshell.com/api/relay/stats/summary' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "providers": 1, "endpoints": 2, "lanes": 1, "requests": 12, "request_states": { "completed": 10, "failed": 1, "cancelled": 1 } } ``` ## Next [Realtime](https://anchorshell.com/docs/realtime). --- # Realtime Canonical HTML: https://anchorshell.com/docs/realtime Watch requests wait, run, and finish across your configured models. Last updated: 2026-09-17 Realtime visualizes requests moving through waiting, dispatch, and completion. Watch preferred-model waiting and fallback across your configured capacity. ## Dashboard 1. Open **Realtime**. 2. Send a request from **Playground** or your application. 3. Watch the request move through the queue and selected model. 4. Inspect a request to see timing, route, and limit details. Waiting is not always an error. Relay can wait for a preferred model when it is temporarily busy or capped. Hosted users can view their own activity. Team-wide views require the relevant plan and permission. Realtime is a dashboard feature. Use [Queue](https://anchorshell.com/docs/queue) for a current request list or [Logs](https://anchorshell.com/docs/logs) for completed requests. ## Next [Providers](https://anchorshell.com/docs/providers). --- # Providers Canonical HTML: https://anchorshell.com/docs/providers Understand providers, credentials, and configured models; add them in the dashboard or through the API. Last updated: 2026-09-17 A **Provider** is the upstream service Relay calls. A **Credential** authenticates Relay to that service. A **Model** is a configured upstream model, with its supported request type, pricing, and limits. Clients use the model's displayed `provider/model` call name. Configuring several providers lets [Groups](https://anchorshell.com/docs/groups) choose among their models. ## Dashboard 1. In **Providers**, choose **Add API Key Provider** to open Setup. 2. Import the provider's curl request or enter its connection details. Add its API key in the credential field. 3. Review the model, request type, pricing, and available limits; save the configuration. 4. On Providers, use **Add model** to add another model to that provider. Edit, duplicate, or delete through the row actions. Hosted **Subscription Plan Providers** use a separate account-connection flow. OpenAI Codex is available where enabled: authorize the account, verify it, then enable discovered models. This does not mean arbitrary subscriptions are supported. Ordinary API-key providers remain available when self-hosting. ## API Use a key with the relevant permissions; see [API authentication](https://anchorshell.com/docs/api). Replace placeholders with IDs from the corresponding list or create response. Management requires `relay:providers:read` or `relay:providers:manage`. List and create responses contain public `id` values. There is currently no separate `GET /providers/` or `GET /endpoints/`; find the item in its list response. ### List providers ```bash curl 'https://api.anchorshell.com/api/relay/providers' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "id": "", "name": "OpenAI", "slug": "openai", "base_url": "https://api.openai.com/v1", "auth_mode": "bearer_static", "enabled": true } ] ``` ### Create a provider ```bash curl -X POST 'https://api.anchorshell.com/api/relay/providers' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "OpenAI", "base_url": "https://api.openai.com/v1", "auth_mode": "bearer_static", "enabled": true }' ``` **Response — 201 Created** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "name": "OpenAI", "slug": "openai", "base_url": "https://api.openai.com/v1", "auth_mode": "bearer_static", "enabled": true } ``` ### Update a provider ```bash curl -X PUT 'https://api.anchorshell.com/api/relay/providers/' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "OpenAI production", "base_url": "https://api.openai.com/v1", "auth_mode": "bearer_static", "enabled": true }' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "name": "OpenAI production", "slug": "openai", "base_url": "https://api.openai.com/v1", "auth_mode": "bearer_static", "enabled": true } ``` ### Save a credential ```bash curl -X POST 'https://api.anchorshell.com/api/relay/credentials' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "provider_id": "", "name": "OpenAI key", "secret": "", "enabled": true }' ``` **Response — 201 Created** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "provider_id": "", "name": "OpenAI key", "has_secret": true, "enabled": true } ``` The credential response exposes metadata, not its secret. Save its `id` for the model. ### List models ```bash curl 'https://api.anchorshell.com/api/relay/endpoints' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "id": "", "provider_id": "", "credential_id": "", "name": "gpt-5-mini", "upstream_model": "gpt-5-mini", "route_kind": "chat", "enabled": true } ] ``` ### Create a model ```bash curl -X POST 'https://api.anchorshell.com/api/relay/endpoints' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "provider_id": "", "credential_id": "", "name": "gpt-5-mini", "upstream_model": "gpt-5-mini", "route_kind": "chat", "enabled": true }' ``` **Response — 201 Created** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "provider_id": "", "credential_id": "", "name": "gpt-5-mini", "upstream_model": "gpt-5-mini", "route_kind": "chat", "enabled": true } ``` Use an upstream model your provider account can access. ### Update a model ```bash curl -X PUT 'https://api.anchorshell.com/api/relay/endpoints/' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "Support model", "upstream_model": "gpt-5-mini", "route_kind": "chat", "enabled": true }' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "provider_id": "", "credential_id": "", "name": "Support model", "upstream_model": "gpt-5-mini", "route_kind": "chat", "enabled": true } ``` ### Delete a model ```bash curl -X DELETE 'https://api.anchorshell.com/api/relay/endpoints/' \ -H 'Authorization: Bearer ' ``` **Response — 204 No Content** No response body. ### Delete a provider ```bash curl -X DELETE 'https://api.anchorshell.com/api/relay/providers/' \ -H 'Authorization: Bearer ' ``` **Response — 204 No Content** No response body. Deletion removes that resource from routing. Check affected groups before deleting. ## Next [Groups](https://anchorshell.com/docs/groups). --- # Groups Canonical HTML: https://anchorshell.com/docs/groups Create ordered regular Groups or request-aware Smart Groups, arrange models, and call either through the model field. Last updated: 2026-09-17 A **Group** gives several models one request name. A regular Group contains models you choose, ranked in the order you want Relay to consider them. Relay waits for the preferred model when it can become available within the configured wait budget; with fallback enabled, it considers lower-ranked alternatives when that wait is too long. A **Smart Group** chooses an approved model order based on the request. Use one when coding, writing, or other requests should select different models automatically. Smart Groups are hosted functionality; ordinary Groups and fallback remain available when self-hosting. ## Dashboard ### Regular Groups 1. Open **Groups → Add Group**. Enter a name and cooldown maximum wait, then create it. 2. Open **Edit group**. Drag models from **Available models** into the ranked list. 3. Drag assigned models to reorder them, or move one back to remove it. Membership/order changes save as you make them. 4. Adjust the group's name, enabled state, or wait setting and save those settings. The current editor saves fallback enabled. The API also exposes the stored `allow_fallback` setting. Add upstream models through [Providers](https://anchorshell.com/docs/providers) first. ### Smart Groups 1. Choose **Add Smart Group**, enter a name, and create it. 2. Open its editor. Review the automatically prepared assignments: one primary model and up to two fallbacks for each intent. 3. Adjust assignments, the default routing list, confidence threshold, and cooldown maximum wait; choose **Save Smart Group**. Low-confidence requests use the default list. The selected list still obeys access, limits, and availability. Copy the displayed call name, such as `smart/support`; Smart Groups support generation requests, not embeddings. ## API Use a key with the relevant permissions; see [API authentication](https://anchorshell.com/docs/api). Replace placeholders with IDs from the corresponding list or create response. Use `relay:groups:read` for reads and `relay:groups:manage` for writes. The existing management API still calls regular Groups `routing-lanes` and their model assignments `lane-memberships`. These names configure saved Groups; **they are never inference parameters**. A cleaner public naming API is not yet available. ### List Groups ```bash curl 'https://api.anchorshell.com/api/relay/routing-lanes' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "id": "", "name": "agentic", "slug": "agentic", "default_max_wait_ms": 60000, "allow_fallback": true, "enabled": true } ] ``` To retrieve one Group, find its `id` in this response. A single-Group GET endpoint is not implemented. ### Create a Group ```bash curl -X POST 'https://api.anchorshell.com/api/relay/routing-lanes' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "agentic", "default_max_wait_ms": 60000, "allow_fallback": true, "enabled": true }' ``` **Response — 201 Created** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "name": "agentic", "slug": "agentic", "default_max_wait_ms": 60000, "allow_fallback": true, "enabled": true } ``` ### Update a Group ```bash curl -X PUT 'https://api.anchorshell.com/api/relay/routing-lanes/' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "agentic", "default_max_wait_ms": 30000, "allow_fallback": true, "enabled": true }' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "name": "agentic", "slug": "agentic", "default_max_wait_ms": 30000, "allow_fallback": true, "enabled": true } ``` ### List model assignments ```bash curl 'https://api.anchorshell.com/api/relay/lane-memberships' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "id": "", "lane_id": "", "endpoint_id": "", "manual_rank": 1, "enabled": true } ] ``` Find assignments whose `lane_id` equals the Group ID. Each assignment has its own `id`. ### Add a model ```bash curl -X POST 'https://api.anchorshell.com/api/relay/lane-memberships' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "lane_id": "", "endpoint_id": "", "manual_rank": 1, "enabled": true }' ``` **Response — 201 Created** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "lane_id": "", "endpoint_id": "", "manual_rank": 1, "enabled": true } ``` ### Reorder a model ```bash curl -X PUT 'https://api.anchorshell.com/api/relay/lane-memberships/' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "manual_rank": 2, "enabled": true }' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "lane_id": "", "endpoint_id": "", "manual_rank": 2, "enabled": true } ``` Update each affected assignment's `manual_rank` to give the list distinct ranks. The API has no atomic whole-list reorder operation. ### Remove a model ```bash curl -X DELETE 'https://api.anchorshell.com/api/relay/lane-memberships/' \ -H 'Authorization: Bearer ' ``` **Response — 204 No Content** No response body. ### Delete a Group ```bash curl -X DELETE 'https://api.anchorshell.com/api/relay/routing-lanes/' \ -H 'Authorization: Bearer ' ``` **Response — 204 No Content** No response body. Deleting a Group removes its memberships, not the provider models. ### Call a Group ```bash curl -X POST 'https://api.anchorshell.com/v1/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "model": "agentic", "messages": [ { "role": "user", "content": "Explain why queue-first routing preserves better models." } ] }' ``` **Response — 200 OK** Example non-streaming response (selected fields). Content, model, and usage depend on the selected provider. ```json { "id": "", "object": "chat.completion", "model": "gpt-5-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Relay waits for the preferred model before considering fallback." }, "finish_reason": "stop" } ] } ``` Use the Smart Group call name in the same `model` field when applicable. ## Next [Guardrails](https://anchorshell.com/docs/guardrails). --- # Guardrails Canonical HTML: https://anchorshell.com/docs/guardrails Apply an HTTP policy before a model call or after its response, with explicit bindings and failure behavior. Last updated: 2026-09-17 Guardrails call a policy service and apply your configured verdict: allow, block, or a supported response replacement. They do not include a built-in moderation model. A binding attaches a guardrail to a Group, Provider, or Model. Pre-dispatch checks can stop a request before provider usage. Post-response checks run after the provider has completed, so its usage and cost still count. Effective guardrails require non-streaming generation; they do not apply to embeddings. ## Dashboard 1. Open **Guardrails**, add a guardrail, and choose a preset or custom HTTP service. 2. Configure its URL, protected credential, stage, request template, and response rules. 3. Choose what happens on service failure: allow, block, or return an error. 4. Test with representative input, inspect the verdict, add bindings, then enable it. Testing can call the configured external policy service. Review its data handling before sending content. ## API Use a key with the relevant permissions; see [API authentication](https://anchorshell.com/docs/api). Replace placeholders with IDs from the corresponding list or create response. Reads require `relay:settings:view`; mutations and tests require `relay:settings:manage`. ### List guardrails ```bash curl 'https://api.anchorshell.com/api/relay/guardrails' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "id": "", "name": "Content policy", "preset_slug": "custom-http", "enabled": false, "has_credential": false, "binding_count": 0 } ] ``` ### Get one guardrail ```bash curl 'https://api.anchorshell.com/api/relay/guardrails/' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "guardrail": { "id": "", "name": "Content policy", "preset_slug": "custom-http", "enabled": false, "has_credential": false, "binding_count": 0 }, "bindings": [] } ``` ### Create a disabled draft ```bash curl -X POST 'https://api.anchorshell.com/api/relay/guardrails' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "Content policy", "preset_slug": "custom-http", "base_url": "https://policy.example.com/check", "http_method": "POST", "auth_mode": "none", "pre_dispatch_enabled": true }' ``` **Response — 201 Created** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "name": "Content policy", "preset_slug": "custom-http", "enabled": false, "has_credential": false, "binding_count": 0 } ``` Replace the illustrative URL with your service. New guardrails are always disabled until their full configuration is validated. Configure templates and rules in the editor before enabling. ### Update a guardrail ```bash curl -X PUT 'https://api.anchorshell.com/api/relay/guardrails/' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "Production content policy", "enabled": false }' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "name": "Production content policy", "preset_slug": "custom-http", "enabled": false, "has_credential": false, "binding_count": 0 } ``` ### Replace bindings ```bash curl -X PUT 'https://api.anchorshell.com/api/relay/guardrails//bindings' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "bindings": [ { "provider_id": "", "enabled": true } ] }' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "id": "", "guardrail_id": "", "provider_id": "", "enabled": true } ] ``` This replaces the whole binding list. Each binding targets exactly one resource. ### Test a configured guardrail ```bash curl -X POST 'https://api.anchorshell.com/api/relay/guardrails//test' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "stage": "pre_dispatch", "request_text": "A harmless sample request." }' ``` **Response — 200 OK** Example configured policy allowing the test input. This calls the policy service, not an LLM. ```json { "response_status": 200, "latency_ms": 42, "result": { "decision": "allow", "guardrail_uuid": "", "stage": "pre_dispatch" } } ``` The saved guardrail must have valid templates and response rules before this test can run. ### Delete a guardrail ```bash curl -X DELETE 'https://api.anchorshell.com/api/relay/guardrails/' \ -H 'Authorization: Bearer ' ``` **Response — 204 No Content** No response body. ## Next [Limits](https://anchorshell.com/docs/limits). --- # Limits Canonical HTML: https://anchorshell.com/docs/limits Control requests, tokens, spending, and concurrency without confusing resource limits with individual budgets. Last updated: 2026-09-17 Limits define how much capacity Relay may use. Provider/model limits protect shared resources; hosted user and API-key policies restrict a particular person or workload. Applicable limits combine—the tightest boundary wins. Requests may wait for capacity within the Group's wait budget. Spend limits use configured pricing, not a provider invoice. Request pacing spreads eligible short-window requests rather than releasing a burst. ## Dashboard 1. Open **Limits** and choose the provider/model, user, or API-key section. 2. Add a policy. Choose its target, metric, time window, value, and enabled state. 3. Save, then inspect remaining capacity and reset times while requests run. Provider/resource limits are available when self-hosting. Hosted individual budgets support requests, tokens, and spend, targeted globally or to a provider/model. User limits cover that user's browser and key traffic; exact-key limits cover only that key. ## API Use a key with the relevant permissions; see [API authentication](https://anchorshell.com/docs/api). Replace placeholders with IDs from the corresponding list or create response. Shared policy reads require `relay:limits:read:organization`; mutations require `relay:limits:manage`. Individual reads respect self/organization visibility. ### List resource policies ```bash curl 'https://api.anchorshell.com/api/relay/limit-policies' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "id": "", "scope_type": "global", "metric": "requests", "period": "minute", "limit_value": 60, "enabled": true } ] ``` ### Create a resource policy ```bash curl -X POST 'https://api.anchorshell.com/api/relay/limit-policies' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "scope_type": "global", "metric": "requests", "period": "minute", "limit_value": 60, "enabled": true }' ``` **Response — 201 Created** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "scope_type": "global", "metric": "requests", "period": "minute", "limit_value": 60, "enabled": true } ``` ### Update a resource policy ```bash curl -X PUT 'https://api.anchorshell.com/api/relay/limit-policies/' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "scope_type": "global", "metric": "requests", "period": "minute", "limit_value": 120, "enabled": true }' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "id": "", "scope_type": "global", "metric": "requests", "period": "minute", "limit_value": 120, "enabled": true } ``` ### Delete a resource policy ```bash curl -X DELETE 'https://api.anchorshell.com/api/relay/limit-policies/' \ -H 'Authorization: Bearer ' ``` **Response — 204 No Content** No response body. ### List hosted user policies ```bash curl 'https://api.anchorshell.com/api/relay/pro/user-limit-policies' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "policies": [ { "id": "", "limit_uuid": "", "user_uuid": "", "target_type": "global", "metric": "tokens", "period": "day", "limit_value": 100000, "enabled": true } ] } ``` ### Create a user budget ```bash curl -X POST 'https://api.anchorshell.com/api/relay/pro/user-limit-policies' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "user_uuid": "", "target_type": "global", "metric": "tokens", "period": "day", "limit_value": 100000, "enabled": true }' ``` **Response — 200 OK** No policy ID is returned. List policies to obtain its limit_uuid. ```json { "ok": true } ``` User-policy writes use POST upsert: include the `limit_uuid` from the policy list to update that policy. ### Update a user budget ```bash curl -X POST 'https://api.anchorshell.com/api/relay/pro/user-limit-policies' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "limit_uuid": "", "user_uuid": "", "target_type": "global", "metric": "tokens", "period": "day", "limit_value": 200000, "enabled": true }' ``` **Response — 200 OK** No policy ID is returned. List policies to obtain its limit_uuid. ```json { "ok": true } ``` ### Delete a user budget ```bash curl -X DELETE 'https://api.anchorshell.com/api/relay/pro/user-limit-policies?limit_uuid=' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** The policy was removed. ```json { "ok": true } ``` ### List hosted API-key policies ```bash curl 'https://api.anchorshell.com/api/relay/api-key-limit-policies' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "policies": [ { "id": "", "limit_uuid": "", "api_key_uuid": "", "owner_user_uuid": "", "target_type": "global", "metric": "requests", "period": "day", "limit_value": 2000, "enabled": true } ] } ``` ### Create an API-key budget ```bash curl -X POST 'https://api.anchorshell.com/api/relay/api-key-limit-policies' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "api_key_uuid": "", "target_type": "global", "metric": "requests", "period": "day", "limit_value": 2000, "enabled": true }' ``` **Response — 200 OK** No policy ID is returned. List policies to obtain its limit_uuid. ```json { "ok": true } ``` To update an exact-key policy, POST its `limit_uuid` from the policy list with the same key/target/metric/window and the new value. ### Delete an API-key budget ```bash curl -X DELETE 'https://api.anchorshell.com/api/relay/api-key-limit-policies?limit_uuid=' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** The policy was removed. ```json { "ok": true } ``` Use the public key ID, never its secret, in policy bodies. Replacing a key creates a new ID; its old policy does not automatically transfer. ## Next [Usage](https://anchorshell.com/docs/usage). --- # Usage Canonical HTML: https://anchorshell.com/docs/usage Review request counts, token use, and configured costs by model, provider, user, or API key. Last updated: 2026-09-17 Usage shows how requests consume models, tokens, and configured spending over time. Compare workload patterns and providers; hosted attribution also separates users and API keys. ## Dashboard 1. Open **Usage**. 2. Choose the date range. 3. Review requests, input and output tokens, and cost. 4. Filter by provider or model. Hosted attribution can show users and API keys. Organization-wide views require the appropriate plan and permission. An agent using its own key appears under that key. Costs use your configured pricing. They are not a provider invoice. Missing pricing can produce a zero cost; it does not prove the provider call was free. ## API Use an appropriately scoped [API key](https://anchorshell.com/docs/api) for these requests. ### Current usage ```bash curl 'https://api.anchorshell.com/api/relay/stats/usage' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Counters are returned by scope, metric, and window. Selected response fields shown. ```json [ { "scope_type": "global", "scope_id": "global", "metric": "requests", "period": "day", "used_value": 12, "window_start": "2026-09-17T00:00:00Z" } ] ``` ### Spending summary ```bash curl 'https://api.anchorshell.com/api/relay/stats/spend' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** used_value is in microdollars: 125000 = $0.125. Selected response fields shown. ```json [ { "scope_type": "global", "scope_id": "global", "metric": "spend", "period": "day", "used_value": 125000, "window_start": "2026-09-17T00:00:00Z" } ] ``` Results respect your self/organization visibility and entitlements. ## Next [Queue](https://anchorshell.com/docs/queue). --- # Queue Canonical HTML: https://anchorshell.com/docs/queue Inspect requests waiting for capacity and cancel work you no longer need. Last updated: 2026-09-17 The Queue contains requests waiting for eligible model capacity. It makes planned waiting visible: cooldowns, pacing, and individual budgets can delay a request without making the provider unhealthy. ## Dashboard 1. Open **Queue**. 2. Find the waiting request. 3. Inspect its target, wait time, and limiting condition. 4. Click **Cancel** if you no longer need it. Requests can wait because of provider capacity, cooldowns, pacing, or configured limits. A wait does not necessarily mean the provider is unavailable. Cancellation requires queue-management permission. Waiting requests do not survive a Relay restart. ## API Use an appropriately scoped [API key](https://anchorshell.com/docs/api) for these requests. ### List queued requests ```bash curl 'https://api.anchorshell.com/api/relay/queue/items' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json [ { "task_id": "", "request_id": "", "incoming_model": "agentic", "state": "queued", "wait_ms": 1500 } ] ``` ### Cancel a request ```bash curl -X POST 'https://api.anchorshell.com/api/relay/queue/cancel/' \ -H 'Authorization: Bearer ' ``` **Response — 204 No Content** No response body. Use the task ID from the queue. Cancellation requires `relay:queue:manage`; listing respects self/organization visibility. ## Next [Logs](https://anchorshell.com/docs/logs). --- # Logs Canonical HTML: https://anchorshell.com/docs/logs Find a request and inspect its result, timing, selected model, usage, and guardrail outcome. Last updated: 2026-09-17 Logs explain an individual request: its original target, selected provider/model, timing, outcome, and accounted usage. Investigate failed routes or reconcile estimated costs with completed requests. ## Dashboard 1. Open **Logs**. 2. Use the available request filters. 3. Open a request. 4. Review its route, status, timing, tokens, cost, and guardrail result. Request details also show request characterization, such as intent and complexity, when available. Request bodies appear only when payload storage was enabled and access is allowed. Enabling storage does not recover older bodies. Hosted classification feedback can review and correct a request classification. This does not automatically retrain a model. ## API Use an appropriately scoped [API key](https://anchorshell.com/docs/api) for these requests. ### List recent requests ```bash curl 'https://api.anchorshell.com/api/relay/logs/requests?limit=20&offset=0' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "items": [ { "request_id": "", "incoming_model": "agentic", "selected_upstream_model": "gpt-5-mini", "status_code": 200, "task_state": "completed", "wait_ms": 0 } ], "total": 1, "limit": 20, "offset": 0, "next_offset": 1, "has_more": false } ``` ### Inspect one request ```bash curl 'https://api.anchorshell.com/api/relay/logs/requests/' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "request_id": "", "incoming_model": "agentic", "selected_upstream_model": "gpt-5-mini", "status_code": 200, "task_state": "completed", "wait_ms": 0 } ``` Use the returned `request_id`, not the row's `id`. Results require the corresponding self/organization log permission. ## Next [Playground](https://anchorshell.com/docs/playground). --- # Playground Canonical HTML: https://anchorshell.com/docs/playground Send a test request, inspect the response, and copy a curl command for your application. Last updated: 2026-09-17 Playground sends the same inference payload your application would send and shows the route Relay actually selected. Check a configured model or Group before integrating a client. ## Dashboard 1. Open **Playground**. 2. Choose a group or `provider/model` in **Target**. 3. Edit **Request body**. 4. Click **Send Request**. 5. Inspect the response, selected model, wait time, and usage. 6. Click **Copy** beside the curl command. Hosted requests can use your signed-in browser session when the key field is empty. To test a particular User API key, enter that key first. A copied curl command does not include your login cookie. The shared UI calls the field **Relay API Key**. Hosted users must enter a User API key from **Account → API keys**. Self-hosted Relay users enter their deployment's inference token. ## API Use an appropriately scoped [API key](https://anchorshell.com/docs/api) for these requests. ### Send a request ```bash curl -X POST 'https://api.anchorshell.com/v1/chat/completions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "model": "agentic", "messages": [ { "role": "user", "content": "Hello" } ] }' ``` **Response — 200 OK** Example non-streaming response (selected fields). Content, model, and usage depend on the selected provider. ```json { "id": "", "object": "chat.completion", "model": "gpt-5-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Relay waits for the preferred model before considering fallback." }, "finish_reason": "stop" } ] } ``` Use a Group you configured. A copied command containing a key is a secret: keep it private. See [Self-hosted](https://anchorshell.com/docs/self-hosting) for local URLs and tokens. ## Next [Settings](https://anchorshell.com/docs/settings). --- # Settings Canonical HTML: https://anchorshell.com/docs/settings Adjust Relay defaults, estimate-based admission, and request-payload storage. Last updated: 2026-09-17 Settings control runtime defaults, estimation, and optional request-payload storage. They affect future requests; storing bodies is separate from keeping usage metadata. ## Dashboard 1. Open **Settings**. 2. Review the settings you want to change. 3. Set wait, retry, or cooldown defaults as needed. 4. Choose whether to reserve estimated tokens and spending against limits. 5. Save your changes. Leave **Store request payloads** off unless you need body inspection. Payloads can contain sensitive data. Hosted Settings also includes limit-view preferences and request characterization. View preferences do not change the limits themselves. Self-hosted environment settings are covered in [Self-hosted](https://anchorshell.com/docs/self-hosting). ## API Use an appropriately scoped [API key](https://anchorshell.com/docs/api) for these requests. ### Read settings ```bash curl 'https://api.anchorshell.com/api/relay/settings' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** value_json is serialized JSON text, not a native boolean. ```json [ { "key": "store_requests", "value_json": "false", "updated_at": "2026-09-17T12:00:00Z" } ] ``` ### Turn off payload storage ```bash curl -X PUT 'https://api.anchorshell.com/api/relay/settings/store_requests' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "value": false }' ``` **Response — 204 No Content** No response body. Reads require `relay:settings:view`; changes require `relay:settings:manage`. ## Next [Setup](https://anchorshell.com/docs/setup). --- # Setup Canonical HTML: https://anchorshell.com/docs/setup Use the guided setup to connect a provider and model, choose grouping, and send a first request. Last updated: 2026-09-17 Setup walks through provider connection, model configuration, optional grouping, limits, pricing, and a first test. It creates the same resources you can edit later on their dedicated pages. ## Dashboard 1. Open **Setup**. 2. Enter a provider URL, credential, and model ID. You can import a provider curl example. 3. Click **Save and Continue**. 4. Choose a group or keep the model as a direct target. 5. Review request, token, and spending limits. 6. Enter provider pricing if you want cost estimates. 7. Send the test request. The wizard saves configuration as you complete each step. When self-hosting, supply your inference token for the test. In hosted Relay, the test can use your signed-in session. ## API There is no separate setup endpoint. Use [Providers](https://anchorshell.com/docs/providers) to configure a connection and model, then [Groups](https://anchorshell.com/docs/groups) for ordered routing. ## Next [Account](https://anchorshell.com/docs/account). --- # Account Canonical HTML: https://anchorshell.com/docs/account Update your profile, verify a new email address, and choose whether to receive product updates. Last updated: 2026-09-17 Your account holds your profile, login identity, and email preferences. Changing your display name does not rename the shared Team or alter anyone else's access. ## Dashboard 1. Open **Account → Account**. 2. Edit your name and save. 3. Use the email-change action if you need a new address. 4. Open the verification email sent to the new address and confirm the change. Password accounts can change email with their current password. For an account created with a login provider, use that provider's account settings. The email-preference switch controls product updates. Turning it off does not disable verification, password-reset, or security messages. ## API Use an appropriately scoped [API key](https://anchorshell.com/docs/api) for these requests. ### Read your account ```bash curl 'https://api.anchorshell.com/api/account' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "user": { "id": "", "name": "Your name", "email": "you@example.com", "status": "active", "marketing_opt_in": false }, "org": { "id": "", "name": "Your team" }, "role": "owner" } ``` ### Update your name ```bash curl -X PATCH 'https://api.anchorshell.com/api/account' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "Your name" }' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "user": { "id": "", "name": "Your name", "email": "you@example.com", "status": "active", "marketing_opt_in": false } } ``` ### Change email preference ```bash curl -X PATCH 'https://api.anchorshell.com/api/account/preferences' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "marketing_opt_in": false }' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "user": { "id": "", "name": "Your name", "email": "you@example.com", "status": "active", "marketing_opt_in": false } } ``` These operations require `cloud:account:manage`. Email changes remain in the dashboard because they use the password and browser-session security workflow. ## Next [Security](https://anchorshell.com/docs/security). --- # Security Canonical HTML: https://anchorshell.com/docs/security Change your password and remove access from sessions you no longer recognize. Last updated: 2026-09-17 Sessions represent signed-in browsers, independently of application API keys. Revoke an unfamiliar session to stop its dashboard access; revoke a key separately when an application should lose access. ## Dashboard 1. Open **Account → Security**. 2. For a password account, use **Change password**. 3. Review **Sessions**. 4. Click **Revoke** beside a session you do not recognize. 5. Review **Account → Activity** for recent account actions. Changing your password signs out your other sessions. Password reset signs out existing sessions. Accounts created with a login provider manage their password with that provider. ## API Use an appropriately scoped [API key](https://anchorshell.com/docs/api) for these requests. ### List your sessions ```bash curl 'https://api.anchorshell.com/api/account/sessions' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "sessions": [ { "id": "", "current": false, "browser": "Chrome", "os": "macOS", "device": "Desktop" } ] } ``` ### Revoke a session ```bash curl -X DELETE 'https://api.anchorshell.com/api/account/sessions/' \ -H 'Authorization: Bearer ' ``` **Response — 204 No Content** No response body. These operations require `cloud:account:manage`. Password changes and browser logout require an authenticated browser session; password recovery uses the existing email flow. ## Next [API Keys](https://anchorshell.com/docs/api-keys). --- # API Keys Canonical HTML: https://anchorshell.com/docs/api-keys Create, scope, rename, and revoke individually attributable hosted API keys. Last updated: 2026-09-17 An API key authenticates a workload as its owner. Requests retain the owner's organization, current permissions, and plan entitlements, narrowed by the key's scopes. Revoking a key stops new authentication without changing other keys. Use one key per application or agent for independent attribution and [budgets](https://anchorshell.com/docs/limits). Self-hosted Relay instead has one deployment-wide `RELAY_API_TOKEN`; its separate `RELAY_ADMIN_TOKEN` manages configuration. ## Dashboard 1. Open **Account → API keys** and name the workload. 2. For inference, leave management access unchecked. For permitted management operations, select **Allow management API access using my current permissions**. 3. Create the key and copy the secret once into your application's secret manager. 4. Use **Edit name** or **Revoke** on an existing key. Existing keys do not gain scopes automatically. The management option captures your current permissions; later removals take effect immediately, while new grants require a new key. Users with the appropriate security permission can also inspect and revoke organization keys. ## API Use a key with the relevant permissions; see [API authentication](https://anchorshell.com/docs/api). Replace placeholders with IDs from the corresponding list or create response. Managing your own keys requires `cloud:account:manage`. A key cannot create another key with broader scopes than itself. Team delegation additionally requires a key carrying all of its owner's current permissions. ### List your keys ```bash curl 'https://api.anchorshell.com/api/account/api-keys' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "api_keys": [ { "id": "", "user_uuid": "", "name": "support-agent", "key_prefix": "", "product_key": "model_relay", "scopes": [ "relay:request" ], "status": "active", "last_used_at": null, "expires_at": null, "revoked_at": null } ] } ``` ### List your available scopes ```bash curl 'https://api.anchorshell.com/api/account/api-key-scopes' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Example permitted scopes; the list varies with the owner and calling key. ```json { "scopes": [ "relay:request", "relay:providers:read", "relay:providers:manage", "cloud:account:manage" ] } ``` ### Create an inference key ```bash curl -X POST 'https://api.anchorshell.com/api/account/api-keys' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "support-agent", "scopes": [ "relay:request" ] }' ``` **Response — 201 Created** The api_key secret is returned only here. Copy it now; key.id is the public ID. ```json { "api_key": "", "key": { "id": "", "user_uuid": "", "name": "support-agent", "key_prefix": "", "product_key": "model_relay", "scopes": [ "relay:request" ], "status": "active", "last_used_at": null, "expires_at": null, "revoked_at": null } } ``` The response's `api_key` field contains the secret once; `key.id` is its public ID. Save the secret securely. List responses never reveal it. ### Create a narrowly scoped management key ```bash curl -X POST 'https://api.anchorshell.com/api/account/api-keys' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "provider-automation", "scopes": [ "relay:providers:manage" ] }' ``` **Response — 201 Created** The api_key secret is returned only here. Copy it now; key.id is the public ID. ```json { "api_key": "", "key": { "id": "", "user_uuid": "", "name": "provider-automation", "key_prefix": "", "product_key": "model_relay", "scopes": [ "relay:providers:read", "relay:providers:manage" ], "status": "active", "last_used_at": null, "expires_at": null, "revoked_at": null } } ``` ### Rename a key ```bash curl -X PATCH 'https://api.anchorshell.com/api/account/api-keys/' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "name": "support-agent-production" }' ``` **Response — 204 No Content** No response body. ### Revoke a key ```bash curl -X DELETE 'https://api.anchorshell.com/api/account/api-keys/' \ -H 'Authorization: Bearer ' ``` **Response — 204 No Content** No response body. Key scopes are immutable after creation. Create a replacement when required access changes. ## Next [Billing](https://anchorshell.com/docs/billing). --- # Billing Canonical HTML: https://anchorshell.com/docs/billing View your plan, review changes, manage seats, and open billing history. Last updated: 2026-09-17 Billing manages your AnchorShell plan, paid interval, and human seats. It is separate from provider model charges, which remain with your provider account. ## Dashboard 1. Open **Account → Billing**. 2. Review your current plan and capacity. 3. Choose a plan and billing interval to review a change. 4. Check the preview before you confirm. 5. Use the billing portal for supported payment and invoice actions. Team plans can add human seats. Review the seat-change preview before confirming. Provider charges are separate from your AnchorShell subscription. Use **Billing history** to review recorded billing activity. Some changes take effect at renewal; the preview shows when they apply. Billing actions require billing permission. ## API Use an appropriately scoped [API key](https://anchorshell.com/docs/api) for these requests. ### Read billing history ```bash curl 'https://api.anchorshell.com/api/billing/history' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Example account with no billing history. ```json { "invoices": [], "payments": [], "refunds": [], "disputes": [], "activities": [] } ``` Use a key with `cloud:billing` and an owner authorized for billing. For purchases, the dashboard presents the price and effective date before confirmation. ## Next [Team](https://anchorshell.com/docs/team). --- # Team Canonical HTML: https://anchorshell.com/docs/team Invite teammates, assign access, and manage shared Relay configuration without sharing credentials. Last updated: 2026-09-17 A Team shares one AnchorShell workspace and Relay configuration. Each member has their own login, permissions, and API keys. Permissions control which operations a member can perform; [limits](https://anchorshell.com/docs/limits) separately control how much they can use. Available seats and plan entitlements constrain membership. Inviting someone does not share your password or turn their API keys into your keys. ## Dashboard 1. Open **Team**, invite a member by email, and choose a role. 2. The recipient accepts the invitation before its seven-day expiry and completes account setup. 3. Open a member to review Relay and workspace permissions. 4. Adjust permissions or remove the member when access is no longer needed. Use scoped grants for provider management, Group configuration, usage, logs, queues, or limits. Owner access cannot be edited as an ordinary member permission preset. ## API Use a key with the relevant permissions; see [API authentication](https://anchorshell.com/docs/api). Replace placeholders with IDs from the corresponding list or create response. Team reads require `cloud:team:read`; changes require `cloud:team:manage` and the user's normal role checks. Because these writes can delegate access, use a management key containing all of your current permissions. ### List members ```bash curl 'https://api.anchorshell.com/api/team/members' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "members": [ { "id": "", "name": "Your name", "email": "you@example.com", "role": "owner", "status": "active" } ], "team_members": [ { "id": "", "name": "Your name", "email": "you@example.com", "role": "owner", "status": "active" } ] } ``` ### Invite a member ```bash curl -X POST 'https://api.anchorshell.com/api/team/invitations' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "email": "teammate@example.com", "role": "developer" }' ``` **Response — 201 Created** Invitation created and invitation email queued; expires_at is authoritative. ```json { "invitation": { "id": "", "email": "teammate@example.com", "role": "developer", "expires_at": "2026-09-24T12:00:00Z", "accepted_at": null, "revoked_at": null } } ``` ### Read permissions ```bash curl 'https://api.anchorshell.com/api/team/members//permissions' \ -H 'Authorization: Bearer ' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "member": { "id": "", "name": "Your name", "email": "you@example.com", "role": "developer", "status": "active" }, "mode": "custom", "permissions": [ "relay:request", "relay:usage:read:self" ], "editable": true } ``` ### Replace Relay permissions ```bash curl -X PUT 'https://api.anchorshell.com/api/team/members//permissions' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "permissions": [ "relay:request", "relay:usage:read:self" ] }' ``` **Response — 200 OK** Selected response fields shown; IDs and values are illustrative. ```json { "member": { "id": "", "name": "Your name", "email": "you@example.com", "role": "developer", "status": "active" }, "mode": "custom", "permissions": [ "relay:request", "relay:usage:read:self" ], "editable": true } ``` The `permissions` array replaces custom Relay grants, rather than adding to them. Omit `platform_permissions` to leave workspace grants unchanged. ### Remove a member ```bash curl -X DELETE 'https://api.anchorshell.com/api/team/members/' \ -H 'Authorization: Bearer ' ``` **Response — 204 No Content** No response body. ### Apply a user token budget ```bash curl -X POST 'https://api.anchorshell.com/api/relay/pro/user-limit-policies' \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{ "user_uuid": "", "target_type": "global", "metric": "tokens", "period": "day", "limit_value": 100000, "enabled": true }' ``` **Response — 200 OK** No policy ID is returned. List policies to obtain its limit_uuid. ```json { "ok": true } ``` Budget changes require `relay:limits:manage`; they do not change the member's permissions. ## Next [Send your first managed request](https://anchorshell.com/guides/managed-first-request). --- # Send your first managed request Canonical HTML: https://anchorshell.com/guides/managed-first-request Create a User API key and send one request through the managed AnchorShell Relay API. Last updated: 2026-08-18 ## Purpose Create one user-owned API key. Use it to send a request through hosted AnchorShell Relay. ## Prerequisites You need access to an organization with Relay enabled. You also need permission to create your own User API key, one accessible Model or Group, and `curl` or another HTTP client. ## Steps 1. Sign in to AnchorShell. 2. Open **Account → API Keys**. 3. Create a key for Relay. 4. Use a workload-specific name, such as `support-agent-production`. 5. Keep the `relay:request` scope. 6. Copy the complete `as_live_` secret. AnchorShell shows it once. 7. Store the secret in a secret manager or local environment variable. 8. Open **Relay → Providers** or **Relay → Groups**. 9. Copy one Model, Standard Group, or Smart Group call name that the key owner can use. 10. Replace `YOUR_MODEL_OR_GROUP` in the request. ```sh curl https://api.anchorshell.com/v1/chat/completions \ -H 'Authorization: Bearer ' \ -H "Content-Type: application/json" \ -d '{ "model": "YOUR_MODEL_OR_GROUP", "messages": [ {"role": "user", "content": "Return the word ready."} ] }' ``` **Response — 200 OK** Example non-streaming response (selected fields). Content, model, and usage depend on the selected provider. ```json { "id": "", "object": "chat.completion", "model": "gpt-5-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Relay waits for the preferred model before considering fallback." }, "finish_reason": "stop" } ] } ``` ## Expected result The API returns a successful Chat Completions response. AnchorShell attributes the request to the User API key, its owner, and the organization. ## Verification Open **Relay → Logs**. Find the request under the User API-key identity. Confirm the selected Group, provider, Model, token use, timing, and final status. ## Recovery If authentication fails, confirm that the key is active and that you copied the complete secret. If routing fails, confirm that the Model is enabled, the key owner has provider access, and no applicable limit is exhausted. Create a replacement key if the secret is lost. ## Next [Create and call a Smart Group](https://anchorshell.com/guides/create-smart-group). --- # Create and call a Smart Group Canonical HTML: https://anchorshell.com/guides/create-smart-group Create intent assignments, review the Model order, and call a Smart Group. Last updated: 2026-08-18 ## Purpose Create one Smart Group. Assign an approved Model order to each characterized action. ## Prerequisites You need `relay:groups:manage`, organization characterization enabled, and at least one enabled Chat Completions or Responses Model. Smart Groups do not support embeddings. ## Steps 1. Open **Relay → Groups**. 2. Select **Add Smart Group**. 3. Enter a name, such as `Production`. 4. Save the Group. 5. Copy its call name, such as `smart/production`. 6. Open the Smart Group editor. 7. Review the `unknown` assignment. 8. Review the `needs_coder` assignment. 9. Review each primary action used by your workloads. 10. Assign no more than three enabled Models to each action. 11. Put the preferred Model first. 12. Save the assignments. 13. Send a request with the Smart Group call name. ```sh curl https://api.anchorshell.com/v1/chat/completions \ -H 'Authorization: Bearer ' \ -H "Content-Type: application/json" \ -d '{ "model": "smart/production", "messages": [ { "role": "user", "content": "Turn the incident notes into a short update for an executive audience." } ] }' ``` **Response — 200 OK** Example non-streaming response (selected fields). Content, model, and usage depend on the selected provider. ```json { "id": "", "object": "chat.completion", "model": "gpt-5-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Relay waits for the preferred model before considering fallback." }, "finish_reason": "stop" } ] } ``` ## Expected result Relay characterizes the request. It selects the operator-approved assignment for the routing action. Relay Core then applies access, limits, cooldowns, queueing, and fallback. ## Verification Open **Relay → Logs**. Confirm the classified action, confidence, routing action, default status, assignment rank, provider, Model, and final route outcome. ## Recovery If the request uses `unknown`, review the organization threshold and classifier state. If no Model is eligible, review Model route support, provider use grants, limits, cooldowns, and Group assignments. Use a Standard Group when the request type is embeddings. ## Next [Connect a provider account](https://anchorshell.com/guides/connect-provider-account). --- # Connect a provider account Canonical HTML: https://anchorshell.com/guides/connect-provider-account Authorize a supported model account, verify it, and enable discovered Models in AnchorShell Pro. Last updated: 2026-08-18 ## Purpose Connect a supported provider account. Verify the account and enable selected Models for Relay routing. ## Prerequisites Open **Relay → Providers** and confirm that the connection type is available. Current types include OpenAI Codex where enabled, OpenRouter where enabled, and supported developer API accounts when that feature is enabled. You need `relay:connections:create:personal` for a personal account. You need `relay:connections:manage:self` to verify, refresh, enable Models, or manage personal access. You need `relay:connections:manage:organization` for an organization account. ## Steps ### Subscription Plan Provider 1. Open **Subscription Models**. 2. Select **Add subscription plan provider**. 3. Select OpenAI Codex. 4. Complete the displayed device authorization or supported local pairing flow. 5. Return to AnchorShell. 6. Verify the account. 7. Review the discovered Models. 8. Enable only the Models that the organization intends to route. ### API Key Provider 1. Open **API Key Models**. 2. Select **Add provider**. 3. Select an available provider. 4. Enter the credential in the protected form. 5. Verify the connection. 6. Review discovered or configured Models. 7. Enable the required Models. OpenRouter can use provider authorization to create an API key. It remains an API Key Provider. ## Expected result The Providers page shows the connected provider and each enabled Model. An enabled Model can join a Standard Group or Smart Group. AnchorShell stores the provider credential in encrypted form. ## Verification Send one controlled request through an enabled Model. Confirm the provider, Model, status, usage, and connection attribution in **Relay → Logs**. ## Recovery If authorization fails, restart the connection flow and complete it before the transaction expires. If verification fails, confirm the provider account, credential, deployment flag, and provider availability. Do not expose the credential in logs or support messages. ## Next [Create an agent User API key and limit](https://anchorshell.com/guides/create-agent-api-key-limit). --- # Create an agent User API key and limit Canonical HTML: https://anchorshell.com/guides/create-agent-api-key-limit Create one user-owned agent key and enforce an exact request, token, or spend policy. Last updated: 2026-08-18 ## Purpose Create one User API key for an agent. Apply a limit to that exact key. ## Prerequisites You need access to create a Relay User API key. You need `relay:limits:manage` to create the policy. A User API key belongs to one user. A user policy includes browser traffic and all keys owned by that user. An exact-key policy includes only the selected `api_key_uuid`. ## Steps 1. Open **Account → API Keys**. 2. Create a Relay key. 3. Use a workload name, such as `openclaw-production`. 4. Keep the `relay:request` scope. 5. Copy the complete secret once. 6. Store the secret in the agent's secret manager. 7. Open **Relay → Limits**. 8. Open **API Key Limits**. 9. Add a policy. 10. Select the exact User API key. 11. Select all Relay traffic, one provider, or one incoming Model or Group key. 12. Select requests, tokens, or spend. 13. Select second, minute, hour, day, or month. 14. Enter the limit. 15. Enable and save the policy. Example: set `openclaw-production` to 2,000 requests per day across all Relay traffic. ## Expected result Requests from the selected key consume the exact-key policy. The key owner's user policies and all broader policies also apply. The most restrictive applicable boundary wins. ## Verification Send a request with the new key. Confirm exact-key attribution in **Relay → Logs**. Confirm the policy state in **Relay → Limits**. Filter **Relay → Usage** by the key when organization visibility is available. ## Recovery If the policy does not apply, confirm that the request used the selected key and that Logs attributes it to the expected API-key ID. If you replace the key, recreate exact-key policies and use grants for the replacement UUID before you revoke the old key. An exact-key denial does not place a shared Model or provider in cooldown. It does not block another key. ## Next [Grant provider access](https://anchorshell.com/guides/grant-provider-access). --- # Grant provider access Canonical HTML: https://anchorshell.com/guides/grant-provider-access Grant organization, user, or exact-key use of a connected subscription account. Last updated: 2026-08-18 ## Purpose Allow an organization, one user, or one exact User API key to send requests through a connected subscription account. ## Prerequisites The provider connection must be active. You need the connection-management permission for its owner scope. The target user or User API key must be active and verified by AnchorShell. A product permission authorizes an operation. A provider use grant authorizes requests through one provider connection. The principal needs both `relay:request` and provider access. ## Steps 1. Open **Relay → Providers**. 2. Find the connected provider account. 3. Open its access management action. 4. Select **Organization**, **User**, or **API key** when that scope is available. 5. Select the verified target. 6. Confirm that the grant scope is **Use**. 7. Save the grant. Use the narrowest scope that meets the requirement. Prefer an exact-key grant for one agent. Use a user grant when the user's browser traffic and all owned keys need the same connection. A user grant includes active User API keys owned by that user. An exact-key grant includes only the selected key. The connection owner has implicit browser access. The owner's active User API keys can use the owner's subscription connection. Personal non-subscription connections accept only a verified exact-key grant for a User API key owned by the same user. ## Expected result The granted principal can see eligible Models from the connection and can use them in Relay requests. Ungranted principals do not receive those Models in their usable candidate set. ## Verification Authenticate as the granted principal. Send one controlled request. Confirm the provider connection, Model, user, and optional exact-key attribution in **Relay → Logs**. Confirm that an ungranted principal cannot use the connection. ## Recovery If the Model remains hidden, confirm `relay:request`, the grant target, active connection state, and Model enablement. If access is too broad, revoke the grant and create a narrower exact-key or user grant. Organization connection managers can see sanitized management data. They do not receive provider secrets. ## Next [Self-host Relay from source](https://anchorshell.com/guides/self-host-bouncer). --- # Self-host Relay from source Canonical HTML: https://anchorshell.com/guides/self-host-bouncer Clone the public Relay repository, start the embedded application, and protect its first-run secrets. Last updated: 2026-08-18 ## Purpose Run AnchorShell Relay from the public source repository. This procedure starts the Go service and its embedded admin interface. ## Prerequisites Install these tools before you continue: | Tool | Required version | Use | | --- | --- | --- | | Go | `1.26.4` | Build and run Relay | | Node.js | `22.19.0` | Build the Nuxt interface | | Git | Current supported release | Clone the repository | | Make | Current supported release | Run the supported workflow | ## Start Relay 1. Clone the public repository. ```sh git clone https://github.com/anchorshell/bouncer.git anchorshell-relay ``` 2. Enter the repository. ```sh cd anchorshell-relay ``` 3. Install the Go and frontend dependencies. ```sh make deps ``` 4. Start the production-style application. ```sh make run ``` 5. Save the generated `RELAY_ADMIN_TOKEN`, `RELAY_API_TOKEN`, and `RELAY_MASTER_KEY`. Relay prints generated values once and writes them to a mode-`0600` `.env` file. > **Warning:** Keep `RELAY_MASTER_KEY` outside your database backup. You cannot recover existing encrypted provider credentials if you lose this key. 6. Open `http://localhost:11730/`. 7. Enter `RELAY_ADMIN_TOKEN` when the interface asks you to unlock administration. ## Expected result The Dashboard opens. Relay serves the admin interface and the OpenAI-compatible gateway from port `11730`. The management token protects the dashboard and management API. Inference separately requires `RELAY_API_TOKEN`. An explicitly empty `RELAY_API_TOKEN=` disables inference authentication; use that only in a controlled environment. Keep HTTPS and network access controls for remote deployments. ## Verification Open `http://localhost:11730/` and confirm that the admin unlock screen loads. After configuration, send a request and confirm it appears in **Logs**. ## Use hot reload Install the development watcher once. ```sh make install-dev-tools ``` Start the Go and Nuxt development servers. ```sh make dev ``` Open `http://127.0.0.1:3030` for frontend work. The backend and API remain at `http://127.0.0.1:11730`. ## Recovery - If port `11730` is busy, stop the conflicting process or set an explicit backend address. - If Node has the wrong version, run `nvm use 22.19.0` and repeat `make deps`. - If Relay rejects the master key, restore the exact key that encrypted the stored provider credentials. - If the UI build is stale, run the supported `make run` workflow again. It rebuilds and embeds the Nuxt output. ## Next [Send a request through the dummy model](https://anchorshell.com/guides/dummy-model-first-request). --- # Send a request through the dummy model Canonical HTML: https://anchorshell.com/guides/dummy-model-first-request Configure Relay's built-in dummy provider and verify the routed gateway without an external account. Last updated: 2026-08-18 ## Purpose Send a complete routed request without using a paid provider. The built-in dummy provider returns deterministic responses for local verification. ## Prerequisites - Relay is running at `http://localhost:11730`. - You unlocked the admin interface. ## Add the provider 1. Open **Providers**. 2. Add an API Key Provider with these values: | Field | Value | | --- | --- | | Provider name | `Local Dummy` | | Base URL | `http://localhost:11730/v1/dummy` | | Authentication | Bearer, using your `RELAY_API_TOKEN` | | Enabled | Yes | > **Caution:** Do not use `http://localhost:11730/v1`. That address is Relay's routed gateway. Relay rejects this self-referential provider configuration. 3. Add a model under `Local Dummy`. 4. Set the model name and upstream model to `dummy`. 5. Select a Chat-compatible route kind. 6. Enable the model. ## Send the request ```sh curl http://localhost:11730/v1/chat/completions \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"model":"dummy","messages":[{"role":"user","content":"wait_0"}]}' ``` **Response — 200 OK** Example non-streaming response (selected fields). Content, model, and usage depend on the selected provider. ```json { "id": "", "object": "chat.completion", "model": "dummy", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "I waited 0 seconds." }, "finish_reason": "stop" } ] } ``` ## Expected result Relay selects `Local Dummy/dummy`. The response contains a deterministic dummy completion. Open **Logs** and confirm these fields: - Selected provider and model. - Queue and provider timing. - Input and output token counts. - Completion status. ## Verification Open **Logs**. Confirm the selected Provider and Model, queue and provider timing, token counts, and completion status. ## Recovery - If Relay reports no route, confirm that the provider and model are enabled. - If Relay rejects the provider URL, use the required `/v1/dummy` suffix. - If the dummy provider rejects the prompt, use `wait_0`, `wait_5`, or a documented rate-limit command. ## Next [Add a provider, model, and group](https://anchorshell.com/guides/add-provider-model-group). --- # Add a provider, model, and group Canonical HTML: https://anchorshell.com/guides/add-provider-model-group Connect an upstream account and expose its model through an ordered Relay group. Last updated: 2026-08-18 ## Purpose Create the three objects that Relay needs for grouped routing: a Provider, one or more Models, and a Group with ordered memberships. ## Prerequisites Collect the upstream base URL, authentication method, secret, and exact model identifier. Confirm that the upstream implements the OpenAI-compatible API surface that you plan to call. ## Add the provider 1. Open **Providers**. 2. Select **Add provider**. 3. Enter a clear provider name. 4. Enter the upstream API root in **Base URL**. 5. Select the authentication method. 6. Enter the secret only in the credential field. 7. Enable the provider. Relay encrypts the stored credential. Relay does not return the plaintext credential through the admin API. ## Add a model 1. Select **Add model** on the provider row. 2. Enter the display name. 3. Enter the exact upstream model identifier. 4. Select the supported route kind. 5. Add configured pricing if you need cost estimates or spend limits. 6. Add known request, token, spend, or concurrency limits. 7. Enable pacing only when you want Relay to spread second- or minute-level request capacity. 8. Save the model. ## Create the group 1. Open **Groups**. 2. Select **Add group**. 3. Enter a stable group name, such as `production`. 4. Set the maximum wait that callers accept. 5. Enable fallback if Relay may examine lower-ranked memberships. 6. Add model memberships in the required order. A group membership rank controls grouped routing. A model's direct-route rank does not replace its rank inside a group. ## Send a grouped request ```sh curl http://localhost:11730/v1/chat/completions \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"model":"production","messages":[{"role":"user","content":"Return a short status update."}]}' ``` **Response — 200 OK** Example non-streaming response (selected fields). Content, model, and usage depend on the selected provider. ```json { "id": "", "object": "chat.completion", "model": "gpt-5-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Relay waits for the preferred model before considering fallback." }, "finish_reason": "stop" } ] } ``` ## Expected result Relay resolves `production` to its ordered memberships and dispatches the first eligible Model. The upstream receives its exact configured model identifier. ## Verification Open **Logs**. Confirm that the incoming group is `production` and that Relay recorded the selected `provider/model`. ## Recovery If no candidate is available, verify the provider, model, membership, route kind, health state, limits, and group wait budget. Relay does not silently replace an explicitly missing group with a direct model. ## Next [Build a free, local, and paid model order](https://anchorshell.com/guides/free-local-paid-group). --- # Build a free, local, and paid model order Canonical HTML: https://anchorshell.com/guides/free-local-paid-group Create an explicit model order that uses free or local capacity before a paid fallback. Last updated: 2026-08-18 ## Purpose Create a Standard Group that prefers selected free or local capacity and uses a paid model only when the wait budget permits fallback. Relay follows the order that you configure. Relay does not discover the cheapest model or optimize this order automatically. ## Prerequisites Add and verify these model types: 1. A free or subscription-backed model with known capacity. 2. A local OpenAI-compatible model. 3. A paid API model. Add configured pricing if you want Relay to estimate cost or enforce spend limits. ## Create the order 1. Open **Groups**. 2. Add a Standard Group named `free-local-paid`. 3. Enable fallback. 4. Set the maximum wait that the caller accepts. 5. Add the free model at rank `1`. 6. Add the local model at rank `2`. 7. Add the paid model at rank `3`. 8. Save the group. ## Add capacity controls 1. Add the known request or token limit to the free model. 2. Enable request pacing for second- or minute-level request limits when smooth spacing is useful. 3. Add a concurrency limit to the local model if local hardware has a fixed parallel capacity. 4. Add a maximum-latency value when a hanging upstream must be cancelled. ## Understand selection | Condition | Relay action | | --- | --- | | Rank 1 is eligible now | Select rank 1 | | Rank 1 becomes eligible inside the wait budget | Wait for rank 1 | | Rank 1 exceeds the budget and rank 2 fits | Select rank 2 | | Ranks 1 and 2 exceed the budget and rank 3 fits | Select rank 3 | | No candidate fits | Return `429` with `Retry-After` | An upstream `429` for an active task cools the selected model and requeues that same task. It does not automatically move that task to the next rank. ## Expected result Relay prefers the configured free or local Models. It selects the paid Model only when the higher-ranked Models cannot serve inside the wait budget. ## Verification Use **Realtime** to confirm each model's limit, cooldown, and selected state. Use **Logs** to confirm the group, candidate evaluation, selected route, wait time, and cost. ## Recovery If the paid model receives traffic too early, increase the group wait budget or inspect the higher-ranked models' limits and cooldowns. ## Next [Tune wait budgets, pacing, and cooldowns](https://anchorshell.com/guides/tune-wait-pacing-cooldowns). --- # Tune wait budgets, pacing, and cooldowns Canonical HTML: https://anchorshell.com/guides/tune-wait-pacing-cooldowns Control when Relay waits for a preferred model and when it evaluates a fallback. Last updated: 2026-08-18 ## Purpose Set a wait budget that protects response time without wasting preferred capacity. Configure pacing only where Relay can release requests at useful intervals. ## Prerequisites - A Group contains at least two ranked Models. - The Models have known or observed limits. - Fallback is enabled when lower-ranked Models are acceptable. ## Set the wait budget 1. Open **Groups**. 2. Edit the target Group. 3. Set **Maximum wait** to the longest queue delay that callers accept. 4. Save the Group. Relay waits for the preferred Model when its predicted eligibility is inside this budget. Relay evaluates lower-ranked Models when the preferred wait exceeds this budget and fallback is enabled. ## Configure request pacing 1. Open **Limits**. 2. Add a request limit with a `second` or `minute` period. 3. Enable pacing on the Model. 4. Send a controlled request series. 5. Open **Realtime** and confirm that requests move at the expected interval. Pacing applies only to request limits over second and minute periods. Hour, day, and month request limits are hard caps. Token and spend limits are not paced. ## Read cooldown state A cooldown records the next time that a resource can be eligible. Relay can derive a cooldown from: - Configured or observed request capacity. - Provider `Retry-After` information. - Bounded throttle backoff. - Provider health after connection, timeout, or `5xx` failures. A user-specific or API-key-specific limit does not cool the shared Provider or Model. It defers or rejects only that principal's request. ## Test two outcomes ### Wait for the preferred Model Use a cooldown shorter than the Group maximum wait. Confirm that the request remains queued for the preferred Model. ### Select a fallback Use a predicted preferred wait longer than the Group maximum wait. Confirm that Relay selects the next eligible ranked membership. ## Expected result Relay waits when the preferred Model fits the wait budget. It evaluates a fallback only when the preferred wait exceeds that budget. ## Verification Inspect **Queue**, **Realtime**, and **Logs**. The selected route, predicted eligibility, wait duration, and fallback count must describe the same decision. ## Recovery - If requests reject too early, inspect the effective wait budget and tighter applicable limits. - If requests wait too long, reduce the Group maximum wait. - If capacity releases later than expected, inspect conservative long-window buckets and observed limits. ## Next [Configure guardrails before and after a model call](https://anchorshell.com/guides/configure-guardrails). --- # Configure guardrails before and after a model call Canonical HTML: https://anchorshell.com/guides/configure-guardrails Bind an external policy service to a Group, Provider, or Model and verify both lifecycle stages. Last updated: 2026-08-18 ## Purpose Call an external or locally hosted HTTP policy service before provider dispatch, after a non-streaming response, or at both stages. Relay applies the action that you configure. The policy service supplies the verdict. Relay does not include a built-in moderation classifier. ## Prerequisites Collect the policy service URL, authentication method, request template, verdict mapping, timeout, and failure policy. Decide whether the Guardrail binds to one Group, Provider, or Model. ## Create the Guardrail 1. Open **Guardrails**. 2. Add a Guardrail. 3. Select an editable preset or a custom HTTP service. 4. Enter the service URL and authentication settings. 5. Select **Pre-dispatch**, **Post-response**, or both. 6. Map the service response to allowed and blocked verdicts. 7. Select the action for a matched rule. 8. Select `fail_open`, `fail_closed`, or `return_error` for service failures. 9. Bind the Guardrail to exactly one Group, Provider, or Model. 10. Save and enable the Guardrail. ## Test before provider dispatch 1. Use the explicit Guardrail test action with representative bounded input. 2. Send a non-streaming request that matches the configured block rule. 3. Open **Logs**. A pre-dispatch block must show that the Provider was not called. No provider usage or cost occurs. ## Test after the model response 1. Enable a post-response rule. 2. Send a non-streaming request that produces a matching response. 3. Open **Logs**. A post-response block withholds or replaces the response. Provider usage and cost remain because the Provider already completed the request. ## Streaming boundary If an effective Guardrail applies, Relay rejects streaming before provider dispatch. The response uses `guardrails_require_non_streaming`. ## Expected result Relay applies the configured service verdict at the selected stage. A pre-dispatch block prevents provider use. A post-response block retains provider use. ## Verification Confirm the stage, service result, action, failure policy, binding, and final request status in **Logs**. Guardrail results must not mark the Provider unhealthy or trigger model fallback. ## Recovery If the service fails, inspect its URL, timeout, authentication, template, and verdict mapping. Confirm that the chosen failure policy matches the intended security posture. ## Next [Handle provider 429 responses](https://anchorshell.com/guides/handle-provider-429s). --- # Handle provider 429 responses Canonical HTML: https://anchorshell.com/guides/handle-provider-429s Test Relay cooldown and requeue behavior without creating a retry storm. Last updated: 2026-08-18 ## Purpose Verify that Relay controls provider retries after an upstream rate limit. Callers must not add an independent retry loop around this behavior. ## Prerequisites - Relay is running. - The built-in dummy provider uses `http://localhost:11730/v1/dummy`. - The dummy provider credential uses your `RELAY_API_TOKEN` (its endpoint shares inference protection). - A model named `dummy` routes to the dummy provider. - You can inspect Realtime, Queue, and Logs. ## Trigger a controlled 429 1. Send a request that tells the dummy provider to return `429`. ```sh curl -i http://localhost:11730/v1/chat/completions \ -H 'Authorization: Bearer ' \ -H 'Content-Type: application/json' \ -d '{"model":"dummy","messages":[{"role":"user","content":"ratelimit_429"}]}' ``` **Response — 200 OK** Example non-streaming response (selected fields). Content, model, and usage depend on the selected provider. ```json { "id": "", "object": "chat.completion", "model": "gpt-5-mini", "choices": [ { "index": 0, "message": { "role": "assistant", "content": "Relay waits for the preferred model before considering fallback." }, "finish_reason": "stop" } ] } ``` 2. Open **Realtime**. 3. Find the selected model. Its state changes to a rate-limited cooldown. 4. Open **Queue**. The same task remains under scheduler control while the model cools. 5. Open **Logs**. Confirm that the provider result and cooldown reason are present without a stored provider secret or authorization value. ## Expected result A provider `429` does not cause immediate fallback for the affected task. Relay uses this order to calculate the delay: 1. Use a valid `Retry-After` header. 2. Otherwise, derive spacing from a known request limit. 3. Otherwise, use the bounded fallback delay. Relay marks the selected model as cooling down. Relay then requeues the same task on that model. Later candidate selection can avoid the model while its cooldown is active. Connection failures, maximum-latency timeouts, and upstream `5xx` responses use a different health and failure-fallback path. ## Prevent a retry storm - Let Relay own the retry schedule. - Set realistic configured limits when you know the provider contract. - Keep observed-limit learning enabled when the provider sends usable rate headers. - Set each group wait budget to the maximum delay that its callers accept. - Add ranked alternatives only when fallback is appropriate for that workload. ## Verification The model stays in cooldown until its next eligible time. The queued task does not create independent provider calls during that interval. ## Recovery If requests remain delayed after the expected time, inspect the model cooldown, configured limits, observed limits, and group wait budget. A tighter limit can extend eligibility beyond the provider header. ## Next [Inspect a request trace and reconcile cost](https://anchorshell.com/guides/inspect-request-trace). --- # Inspect a request trace and reconcile cost Canonical HTML: https://anchorshell.com/guides/inspect-request-trace Use Logs and optional OpenTelemetry traces to explain routing, timing, tokens, and cost. Last updated: 2026-08-18 ## Purpose Explain one Relay request from route resolution through terminal accounting. Use the request log as the canonical usage record. ## Prerequisites - Relay has completed at least one request. - The selected Model has configured pricing if you need a cost value. - You can open **Logs**. ## Inspect the request 1. Open **Logs**. 2. Filter by result, characterization, Guardrail state, requester, or route when those filters are available. 3. Select one request. 4. Confirm the incoming target and route kind. 5. Confirm the selected Provider and Model. 6. Inspect candidate evaluation and fallback count. 7. Read each reported timing stage. 8. Compare estimated tokens and cost with actual terminal values. ## Interpret the values | Value | Meaning | | --- | --- | | Queue wait | Time under scheduler control before dispatch | | Characterization | Reported classification time when characterization ran | | Guardrails pre | Time spent before provider dispatch | | Provider latency | Time spent in the selected upstream call | | Guardrails post | Time spent after a non-streaming provider response | | Total | Complete reported Relay duration | | Estimated usage | Capacity reserved before dispatch | | Actual usage | Provider-reported or resolved terminal usage | | Cost | Configured price applied to resolved usage | A configured price is an operator value. Relay does not automatically synchronize every provider price. ## Check response metadata Trusted callers can inspect safe `X-Relay-*` response headers for the selected route, wait, fallback count, token counts, and cost when those values are available. These headers do not contain prompts, responses, credentials, cookies, or API-key secrets. ## Export a distributed trace Set an OTLP/HTTP endpoint to enable OpenTelemetry export. Relay traces request context verification, route selection, scheduler wait and admission, database operations, provider HTTP time, and terminal persistence. Do not enable SQL statement export unless you accept placeholder SQL text in the telemetry system. Relay excludes SQL bound values. ## Expected result The request log identifies the caller, routing decision, reported timing stages, terminal usage, and configured cost for one request. ## Verification The request log, response metadata, usage totals, and trace must identify the same selected Provider and Model. Actual input plus output tokens must equal actual total tokens when all values are available. ## Recovery - If actual usage is absent, inspect the provider response and adapter mapping. - If cost is absent, configure Model pricing. - If payloads are absent, check whether payload capture was enabled before the request. Operational metadata remains available when capture is off.