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_USERNAMEand defaults toalforria. - Setting
OPENCODE_SERVER_PASSWORDto 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, anAuthorization: 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 anotherlocalhostport, gets a 403 for write requests (anything butGETandHEAD) and no CORS headers on reads.--corscan be repeated. - Without a password, the server also checks the
Hostheader. It accepts loopback names, the bound--hostname, the hosts of--corsorigins, the--mdns-domainname when--mdnsis set, and any IP address when bound to0.0.0.0. With a password set, this check is off. --mdnsdefaults the hostname to0.0.0.0and adds the--mdns-domainname 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 /sessionandGET /config. - v2 routes are mounted under
/api, for exampleGET /api/sessionandGET /api/health. GET /docreturns the v1 OpenAPI document.GET /openapi.jsonreturns 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, andmessage.part.deltafor streaming transcripts;session.status,session.idle,session.error, andsession.diff;todo.updated;permission.askedandquestion.asked;pty.*,file.edited, andvcs.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 /ptyandPOST /ptylist and create terminals;GET /pty/{id},PUT /pty/{id}, andDELETE /pty/{id}manage one;POST /pty/{id}/connect-tokenissues a short-lived token;GET /pty/{id}/connectupgrades 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 DEBUGprints server logs to stderr. Other commands ignore--print-logs.GET /global/healthis the cheapest liveness check.