Quickstart

This guide walks you through a complete end-to-end Arona setup on a single machine using the built-in mock upstream — no real model weights, GPU or external API account required. By the end you will have:

Prerequisites

1. Set the environment

Arona reads its configuration from environment variables at process startup. Two are mandatory: DATABASE_URL and JWT_SECRET — the server refuses to start without them (unless MOCK_MODE=1). ARONA_ADMIN_TOKEN is strongly recommended: without it, every /api/admin/* route returns 401.

bash
1
2
3
4
5
6
7
export DATABASE_URL="postgres://arona:CHANGE_ME@127.0.0.1:5432/arona"
export JWT_SECRET="CHANGE_ME-a-long-random-string"
export ARONA_ADMIN_TOKEN="CHANGE_ME-an-admin-token"

# Optional: open self-service sign-up (truthy values: 1, true, yes, on).
# The first registered user becomes the admin either way.
export ARONA_REGISTRATION_OPEN=1

These variables are read once when the process starts — if you change them, restart the server. See Configuration for the full variable reference.

2. Migrate and start the server

bash
1
2
cargo run -p _cli -- migrate    # run database migrations explicitly
cargo run -p _cli -- serve      # start the gateway

serve alone is enough for a fresh database: it auto-migrates on startup. The server binds 0.0.0.0:8420 by default (override with ARONA_HOST / ARONA_PORT).

3. Start the mock upstream

In a second terminal:

bash
1
python3 scripts/mock/server.py

The mock is an aiohttp server that listens on 127.0.0.1:8429 by default (ARONA_MOCK_PORT overrides the port). It prints its API key on startup and also serves GET /api/test-key, which returns {"api_key": ..., "base_url": ...}. It exposes a handful of model ids — including gpt-5.5, used below — and answers both plain and streaming chat completions.

Capture the printed key:

bash
1
export MOCK_KEY="<the API key printed on mock startup>"

4. Register the mock as an external backend

Backends are registered through the admin HTTP API:

bash
1
2
3
4
5
6
7
8
9
10
curl -X POST http://127.0.0.1:8420/api/admin/backends \
  -H "Authorization: Bearer $ARONA_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "external",
    "url": "http://127.0.0.1:8429",
    "api_key": "'"$MOCK_KEY"'",
    "name": "mock",
    "models": ["gpt-5.5"]
  }'

The backend is probed immediately on registration and flips healthy within ~1-2 seconds; until that probe completes it stays in a fail-closed "not probed yet" state (see the troubleshooting box below). The configuration is persisted, so the backend survives a restart.

5. Register an account and log in

Accounts live on the JSON-RPC plane, POST /api/rpc. Because ARONA_REGISTRATION_OPEN=1 is set, auth.register is open; the first registered user becomes the admin.

bash
1
2
3
4
5
6
7
8
9
10
curl -X POST http://127.0.0.1:8420/api/rpc \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "id": 1, "method": "auth.register",
    "params": {
      "email": "dev@example.com",
      "password": "Test-password1",
      "name": "Dev"
    }
  }'

Passwords must be at least 8 characters and contain at least 3 of the 4 character categories (uppercase, lowercase, digit, special). Then log in to obtain the JWT pair:

bash
1
2
3
4
5
6
curl -X POST http://127.0.0.1:8420/api/rpc \
  -H "Content-Type: application/json" \
  -d '{
    "jsonrpc": "2.0", "id": 2, "method": "auth.login",
    "params": {"email": "dev@example.com", "password": "Test-password1"}
  }'

Export the access_token from the response:

bash
1
export JWT="<access_token from the login response>"

6. Create an API key

keys.create is JWT-authenticated and returns the full arona-{uuid} secret exactly once — the database only stores its SHA-256 hash, so copy it now:

bash
1
2
3
4
curl -X POST http://127.0.0.1:8420/api/rpc \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 3, "method": "keys.create", "params": {"name": "dev"}}'
bash
1
export AR_KEY="<the arona-... secret returned by keys.create>"

7. Chat (non-streaming)

bash
1
2
3
4
curl -X POST http://127.0.0.1:8420/v1/chat/completions \
  -H "Authorization: Bearer $AR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-5.5", "messages": [{"role": "user", "content": "Hello!"}]}'

You get back an OpenAI-style completion object with a choices[0].message and a usage block.

8. Chat (streaming)

The same endpoint with "stream": true answers with server-sent events: one data: chunk per token, terminated by a final data: [DONE] chunk:

bash
1
2
3
4
curl -N -X POST http://127.0.0.1:8420/v1/chat/completions \
  -H "Authorization: Bearer $AR_KEY" \
  -H "Content-Type: application/json" \
  -d '{"model": "gpt-5.5", "messages": [{"role": "user", "content": "Hello!"}], "stream": true}'

9. Verify usage

Every chat turn records a usage row under the key's prefix. Query it with the JWT:

bash
1
2
3
4
curl -X POST http://127.0.0.1:8420/api/rpc \
  -H "Authorization: Bearer $JWT" \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc": "2.0", "id": 4, "method": "usage.list", "params": {}}'

You should see one or more records for the gpt-5.5 requests made above.

Troubleshooting

Next steps