---
name: airanger-bot
description: Use when interacting with the AiRanger Bot API at https://bot.airanger.dev — self-register an agent account, mint a long-lived API token, list/probe hosted LLM models, manage the bot profile, and send chat completions via its JSON REST API or its OpenAI-compatible /v1 endpoints. Covers the self-service token flow, verified request schemas and field limits, the dual error model, and ready-to-run recipes.
version: 2.0.0
license: MIT
---

# AiRanger Bot Interface

## Overview
AiRanger (https://bot.airanger.dev) is an API-first LLM gateway ("AiRanger
ModelRouter") that fronts 100+ hosted models behind a JSON REST API. It is built
for programmatic/agent access: a machine-readable spec lives at `/api/bot/spec`
and a full OpenAPI 3.x schema at `/openapi.json` (both no-auth), plus a plain-text
agent guide at `/llms.txt`. Every authenticated endpoint expects
`Authorization: Bearer <token>`.

This skill is the interface layer. Field names, types, and limits below match the
published `/api/bot/spec` and `/openapi.json`; when in doubt, fetch those at
runtime — they are the source of truth.

## When to Use
- "Chat with model X on AiRanger" / "ask AiRanger …"
- Self-registering an agent account and minting an API token.
- Listing or probing available / operational models on the gateway.
- Creating or updating the bot profile (username / full name).
- Any task that names the `bot.airanger.dev` API.

## Don't Use For
- Look-alike hosts — verify the exact host `bot.airanger.dev` (the `.dev` TLD is a
  known lookalike risk). Use a minted API token in tooling, never an account password.
- A model not in `/api/models/available`. A model can be listed yet currently down;
  check `/api/availability` or `/api/models/unavailable` first.

## Auth Model (read first)
The recommended credential for automation is a **long-lived `ar_` API token** minted
by the account itself — no server flag required. You bootstrap once with a short-lived
Supabase JWT, mint the token, then use only the token thereafter.

Auth methods (from `/api/bot/spec`), in order of preference:
- **api_token (`ar_`)** — recommended. `POST /api/keys/generate` returns it once.
  Send as `Authorization: Bearer ar_...`. Never expires until revoked; no server flag.
- **supabase_jwt** — short-lived access token from Supabase email/password sign-in.
  Use it only to bootstrap (create profile, mint the `ar_` token), then stop using it.
- **query_param** — `?access_token=...` for SSE/EventSource endpoints that can't set
  headers (e.g. `/api/chat/stream`).
- **bot_token (`br_`)** — OPT-IN, disabled by default: `POST /api/bot/token` returns
  501 unless the server sets `BOT_ALLOW_API_KEY_BYPASS=1`. Do not rely on it; prefer `ar_`.

### Requirements enforced on bot.airanger.dev
- **verified_email** — a fresh account must click its Supabase verification email
  before the API works. Disposable/throwaway email domains are rejected at profile
  creation (`EMAIL_DISPOSABLE`).
- **bot_profile** — `PUT /api/bot/profile` (username + full name) before chat.

### Error model (two shapes — both declared in /openapi.json)
- **Non-validation errors** (401/403/404/409/429/5xx) → StructuredError envelope:
  `{"detail": "…", "error_code": "…"}`. Branch on `error_code`. Common codes:
  `AUTH_REQUIRED`, `AUTH_INVALID`, `EMAIL_NOT_VERIFIED`, `EMAIL_DISPOSABLE`,
  `PROFILE_INCOMPLETE`, `RATE_LIMIT_EXCEEDED`, `MODEL_NOT_FOUND`.
- **Request-validation errors** → HTTP **422** with FastAPI's array shape:
  `{"detail": [{"loc": [...], "msg": "…", "type": "…"}]}` (no `error_code`). Read `detail[]`.
- The OpenAI-compatible `/v1` endpoints instead return OpenAI-style errors:
  `{"error": {"message", "type", "code"}}`.

### Rate limits
- **Per-user**: each account is capped at `per_user_rpm` requests/min on `/api/chat*`
  (see `rate_limits` in `/api/bot/spec`). Over it → 429 `RATE_LIMIT_EXCEEDED`.
- **Global**: all accounts share a `global_rpm` ceiling to the upstream provider.
- On 429, honor `Retry-After` and back off ~60s.

## Field limits (verified)
- `prompt` / `system`: 1–20000 chars.
- `max_tokens`: 1–32768.
- `temperature`: 0–2.
- Multi-turn `messages`: capped (see `constraints` in the spec).

## Endpoint Catalog
No-auth: `/healthz`, `/api/public-config`, `/api/bot/spec`, `/llms.txt`,
`/.well-known/ai-plugin.json`, `/docs`, `/openapi.json`.
Auth (Bearer):

| Method | Path | Purpose |
|--------|------|---------|
| GET | `/api/me` | Current user |
| GET/PUT | `/api/bot/profile` | Get / create-or-update bot profile |
| POST | `/api/keys/generate` | **Mint a long-lived `ar_` API token (recommended)** |
| GET | `/api/keys` | List your API tokens (no secrets) |
| DELETE | `/api/keys/{key_id}` | Revoke a token |
| POST | `/api/bot/token` | Opt-in `br_` token (501 unless server bypass flag) |
| GET | `/api/models` | Catalog (filter: capability, publisher, search) |
| GET | `/api/models/available` | Operational models |
| GET | `/api/models/unavailable` | Errored/timed-out models |
| GET | `/api/publishers` | Publishers + model counts |
| GET | `/api/availability` | Per-model availability |
| GET | `/api/rate-limit` | Current global RPM status |
| POST | `/api/chat` | One-shot chat (simple prompt shape, not quota-metered) |
| GET | `/api/chat/stream` | SSE single-turn stream |
| POST | `/api/chat/stream/multi` | Multi-turn stream (messages array) |
| POST | `/v1/chat/completions` | **OpenAI-compatible, quota-metered** (see caveats) |
| GET | `/v1/models` | OpenAI-compatible model list |
| POST | `/api/probe/background` | Start probe job (returns job id) |
| GET | `/api/probe/job/{job_id}` | Poll probe result |
| GET | `/api/probe/state` | Probe state |

### OpenAI-compatible /v1 (drop-in for OpenAI SDKs)
Point an OpenAI SDK's `base_url` at `https://bot.airanger.dev/v1`. Caveats:
- **Requires an `ar_` API key** (`auth_method=api_token`). A raw Supabase JWT is
  rejected with 401 — mint a token first.
- **Quota-metered**: enforces entitlement, quota/credit, and per-user billing RPM.
- **Supported fields**: `model`, `messages`, `temperature`, `max_tokens`, `stream`.
- **Silently ignored**: `tools`, `functions`, `n>1`, `response_format`, `logprobs`.

Use `/api/chat` for the simple single-prompt convenience shape; use `/v1/chat/completions`
for OpenAI-SDK compatibility and metered billing.

## Recommended Workflow (self-registration → chat)
1. `GET /api/public-config` (no auth) → `supabase_url`, `supabase_anon_key`.
2. `POST {supabase_url}/auth/v1/signup` with `{email, password}` and header
   `apikey: <anon_key>`; confirm the verification email (non-disposable domain).
3. `POST {supabase_url}/auth/v1/token?grant_type=password` → `access_token` (JWT).
4. `PUT /api/bot/profile` with `{username, full_name}` using the JWT.
5. `POST /api/keys/generate` using the JWT → store the `ar_` token securely.
6. From here on, authenticate with `Authorization: Bearer <ar_token>`.
7. Confirm the target model in `/api/models/available`, then `POST /api/chat`
   (or `POST /v1/chat/completions`).
8. On 422 read `detail[]`; on 401/403 re-auth; on 429 back off.

## Security Notes
- **Never log the `ar_` token, JWT, or Supabase anon key.** Redact before any echo.
- Store the token in an env var / OS keychain — never inline in scripts or chat.
- Verify the host is exactly `bot.airanger.dev`. Use a minted token, not your password.

## One-Shot Recipe
```bash
BASE=https://bot.airanger.dev

# 1) public config (no auth)
CFG=$(curl -s "$BASE/api/public-config")
SUPABASE_URL=$(printf '%s' "$CFG" | python3 -c "import sys,json;print(json.load(sys.stdin)['supabase_url'])")
ANON_KEY=$(printf '%s' "$CFG" | python3 -c "import sys,json;print(json.load(sys.stdin)['supabase_anon_key'])")

# 2) sign in (assumes account exists + email verified) -> access_token (JWT)
JWT=$(curl -s -X POST "$SUPABASE_URL/auth/v1/token?grant_type=password" \
  -H "apikey: $ANON_KEY" -H "Content-Type: application/json" \
  -d '{"email":"YOU@example.com","password":"***"}' \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['access_token'])")

# 3) bot profile (required before chat)
curl -s -X PUT "$BASE/api/bot/profile" \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"username":"mybot","full_name":"My Bot"}'

# 4) mint a long-lived ar_ token (use this for everything after)
AR_TOKEN=$(curl -s -X POST "$BASE/api/keys/generate" \
  -H "Authorization: Bearer $JWT" -H "Content-Type: application/json" \
  -d '{"name":"my-agent"}' \
  | python3 -c "import sys,json;print(json.load(sys.stdin)['token'])")

# 5) chat (simple shape)
curl -s -X POST "$BASE/api/chat" \
  -H "Authorization: Bearer $AR_TOKEN" -H "Content-Type: application/json" \
  -d '{"model":"<model-id>","prompt":"Hello","max_tokens":512,"temperature":0.7}'

# 5b) or OpenAI-compatible (metered; ar_ token required)
curl -s -X POST "$BASE/v1/chat/completions" \
  -H "Authorization: Bearer $AR_TOKEN" -H "Content-Type: application/json" \
  -d '{"model":"<model-id>","messages":[{"role":"user","content":"Hello"}]}'
```

## Verification Checklist
- [ ] `GET /api/public-config` returns `supabase_url` + `supabase_anon_key`.
- [ ] Sign-in returns a non-empty `access_token`; `GET /api/me` succeeds with it.
- [ ] `PUT /api/bot/profile` succeeds (no `PROFILE_INCOMPLETE`).
- [ ] `POST /api/keys/generate` returns an `ar_` token; store it, never print it.
- [ ] Target model appears in `GET /api/models/available`.
- [ ] `POST /api/chat` returns a JSON object (not a 422 `detail[]`).
