Server and API

Run alforria as a local HTTP/SSE server, authenticate clients, and use the v1 and v2 APIs.

alforria serve runs the same session engine as the TUI behind an HTTP API. It serves the embedded web client at /, exposes v1 and v2 routes, streams events over SSE, and carries terminal sessions over PTY websockets.

alforria serve --port 4096

The default hostname is 127.0.0.1. The default port, 0, tries 4096 first and falls back to a random free port. The server prints the bound URL when it is ready.

Authentication

When OPENCODE_SERVER_PASSWORD is not set, serve generates a random 32-character password and prints the credentials once:

alforria server listening on http://127.0.0.1:4096
  username: alforria
  password: <generated-password>

To choose your own credentials, set them before starting:

OPENCODE_SERVER_PASSWORD='a-long-secret' alforria serve --port 4096
  • The username comes from OPENCODE_SERVER_USERNAME and defaults to alforria.
  • Setting OPENCODE_SERVER_PASSWORD to an empty string is an explicit opt-out. The server prints a warning and runs unauthenticated.
  • Auth uses HTTP Basic. Pass the credentials with curl -u, an Authorization: Basic ... header, or the browser prompt when opening the web client.

Only serve generates a password. The other commands that open a port do not: alforria web, alforria acp, alforria run without --attach (which binds 127.0.0.1 for the length of the run), and plain alforria given --port, --hostname, or --mdns. Those servers are unauthenticated unless OPENCODE_SERVER_PASSWORD is set. web prints OPENCODE_SERVER_PASSWORD is not set; server is unsecured when it starts without one.

Use alforria attach and alforria run --attach with -u and -p to reach a protected server.

Network exposure

# Reachable from the LAN
OPENCODE_SERVER_PASSWORD='a-long-secret' \
  alforria serve --hostname 0.0.0.0 --port 4096

# Allow a separate web origin
alforria serve --cors https://tools.example.com
  • Browser origins are allowed only when they are the server's own origin or were passed with --cors. Any other origin, including another localhost port, gets a 403 for write requests (anything but GET and HEAD) and no CORS headers on reads. --cors can be repeated.
  • Without a password, the server also checks the Host header. It accepts loopback names, the bound --hostname, the hosts of --cors origins, the --mdns-domain name when --mdns is set, and any IP address when bound to 0.0.0.0. With a password set, this check is off.
  • --mdns defaults the hostname to 0.0.0.0 and adds the --mdns-domain name to the accepted hosts. It does not advertise the service yet.
  • alforria does not terminate TLS. Put it behind a TLS reverse proxy or use an SSH tunnel before exposing it beyond a trusted local network.
  • If a reverse proxy sits in front, disable response buffering for SSE and forward WebSocket upgrades for PTY connections.

SSH tunnel example:

ssh -L 4096:localhost:4096 user@workstation
alforria attach http://localhost:4096

API versions

  • v1 routes are mounted at the root, for example GET /session and GET /config.
  • v2 routes are mounted under /api, for example GET /api/session and GET /api/health.
  • GET /doc returns the v1 OpenAPI document.
  • GET /openapi.json returns the v2 OpenAPI document.

Both documents are JSON. Render them with any OpenAPI viewer, or inspect them directly. The route tables are the authoritative reference; the server and the web client use the same shapes.

Common v1 routes

Method and path Purpose
GET /global/health Health check
GET /global/event Global SSE stream
GET /global/config / PATCH /global/config Read or update global config
GET /config / PATCH /config Read or update the project config
GET /provider List providers and models
GET /provider/auth List provider auth methods
GET /session / POST /session List or create sessions
GET /session/status Session statuses
GET /session/{id} / PATCH / DELETE Read, update, or delete a session
GET /session/{id}/message / POST /session/{id}/message Read the transcript or send a prompt
POST /session/{id}/prompt_async Send a prompt without waiting for the turn
POST /session/{id}/abort Abort the current turn
POST /session/{id}/fork Fork a session
POST /session/{id}/summarize Compact a session
POST /session/{id}/revert / unrevert Revert or restore changes
GET /session/{id}/todo / diff / children Todos, diffs, and subagents
GET /permission Pending permission requests
POST /permission/{id}/reply Answer a permission request
GET /question Pending questions
POST /question/{id}/reply / reject Answer or reject a question
GET /project / GET /project/current Projects and the current project
GET /file, GET /find File and search helpers
POST /log Client log ingestion

SSE streams

Path Scope
GET /global/event Global stream across projects and sessions
GET /event v1 instance stream
GET /api/event v2 event stream
GET /api/session/{id}/event v2 stream for one session

The v1 streams (/global/event, /event) send server.connected when they open and a server.heartbeat event every 10 seconds. /api/event sends server.connected, then a : heartbeat SSE comment right away and every 15 seconds. /api/session/{id}/event replays the session's stored events and continues live, with no server.connected and no heartbeat. Typical event types include:

  • message.updated, message.part.updated, and message.part.delta for streaming transcripts;
  • session.status, session.idle, session.error, and session.diff;
  • todo.updated;
  • permission.asked and question.asked;
  • pty.*, file.edited, and vcs.branch.updated.

Subscribe with curl -N or any SSE client. Keep the stream open and parse one JSON event per data: frame.

curl -N -u "alforria:$OPENCODE_SERVER_PASSWORD" \
  http://127.0.0.1:4096/global/event

Prompt example

Create a session, then send a prompt:

AUTH="alforria:$OPENCODE_SERVER_PASSWORD"
BASE=http://127.0.0.1:4096

SESSION=$(curl -s -u "$AUTH" -X POST "$BASE/session" \
  -H 'content-type: application/json' -d '{}' | jq -r .id)

curl -s -u "$AUTH" -X POST "$BASE/session/$SESSION/message" \
  -H 'content-type: application/json' \
  -d '{"parts":[{"type":"text","text":"summarize this repository"}]}'

The response returns when the turn settles. Use prompt_async and the SSE stream when you want to follow progress as it happens. Permission requests and questions arrive on the stream and must be answered through their reply routes before a blocked turn can continue.

PTY websockets

Terminal sessions are managed through the PTY routes:

  • GET /pty and POST /pty list and create terminals;
  • GET /pty/{id}, PUT /pty/{id}, and DELETE /pty/{id} manage one;
  • POST /pty/{id}/connect-token issues a short-lived token;
  • GET /pty/{id}/connect upgrades to a WebSocket.

The v2 equivalents live under /api/pty. PTY websockets refuse foreign origins. Use the connect token rather than putting credentials in a URL.

Embedded web client

alforria serve serves the web client from /. If you want to use your own client or host the UI separately, set OPENCODE_DISABLE_EMBEDDED_WEB_UI=1 before starting the server.

alforria web starts the same server and opens the browser for you, but it does not generate a password. See Web client for the interface.

Attaching from the TUI

alforria attach http://127.0.0.1:4096
alforria attach http://workstation:4096 -u alforria -p "$SECRET"

alforria run --attach <url> sends one prompt through a remote server. Both accept --dir, --session, --continue, and --fork, where they apply to the remote session store.

Operational notes

  • Sessions are stored in SQLite. The server and the TUI can share the same data directory.
  • The server has no built-in TLS or user database. Use one long random password per server, or put it behind an authenticated proxy.
  • alforria serve --print-logs --log-level DEBUG prints server logs to stderr. Other commands ignore --print-logs.
  • GET /global/health is the cheapest liveness check.