Runtime API Design

The rationale and design decisions behind Alice's local HTTP API.

Why a Local API?

Alice runs bundled skills as subprocess scripts. These scripts need to interact with the running connector — send images, manage tasks, query state. Direct Go interop isn't possible from shell scripts, so Alice exposes a local HTTP API.

Design Principles

1. Local-Only by Default

The API binds to 127.0.0.1 (configurable via runtime_http_addr). It is not designed to be exposed to the network. If you need remote access, use a reverse proxy or SSH tunnel — but this is not the intended use case.

2. Bearer Token Auth

Every request (except /healthz) requires:

Authorization: Bearer <token>

The token is auto-generated at startup. Environment variables (ALICE_RUNTIME_API_TOKEN) inject it into skill scripts automatically.

3. Defense in Depth

Multiple layers of protection:

  • Auth rate limiting: 120 requests per minute per token
  • Body size limit: 1 MB per request
  • File validation: Uploaded files must be readable, non-empty regular files
  • Feishu size limits: Uploads are still subject to Feishu's file size and type restrictions

4. No Text Send Endpoint

The runtime API does not have a plain text send endpoint. Why?

Text replies normally go through the main reply pipeline — they need session context, proper threading, and reply metadata. The runtime API is designed for side-effects (sending images, files, managing tasks), not for bypassing the reply pipeline.

If a skill needs to send a text message as an automation task, it creates a send_text task via the automation API. The automation engine handles the rest.

API Surface

Messages

  • POST /api/v1/messages/image — upload and send an image
  • POST /api/v1/messages/file — upload and send a file

Both accept multipart/form-data with an optional caption field.

Automation

  • Full CRUD for automation tasks: create, list, get, update, delete

Goal

  • Goal lifecycle management: get, create, pause, resume, complete, delete

Health

  • GET /healthz — no auth, responds 200 if the server is running

Environment Variable Injection

Skills don't need to know the API address or token. Alice injects them:

ALICE_RUNTIME_API_BASE_URL="http://127.0.0.1:7331"
ALICE_RUNTIME_API_TOKEN="<auto-generated>"
ALICE_RUNTIME_BIN="/usr/local/bin/alice"

Additional context variables:

ALICE_RECEIVE_ID_TYPE="chat_id"
ALICE_RECEIVE_ID="oc_xxxxxxxxxxxxx"
ALICE_SOURCE_MESSAGE_ID="om_xxxxxxxxxxxxx"
ALICE_ACTOR_USER_ID="ou_xxxxxxxxxxxxx"
ALICE_ACTOR_OPEN_ID="ou_xxxxxxxxxxxxx"
ALICE_CHAT_TYPE="group"
ALICE_SESSION_KEY="chat_id:oc_xxx|scene:chat"

Multi-Bot API Ports

In multi-bot mode, each bot gets its own Runtime API server on an auto-incremented port:

Bot IndexPort
First7331
Second7332
Third7333
......

Skills target the correct bot by inheriting environment variables from the conversation scope they're triggered in.

Graceful Shutdown

When Alice receives a shutdown signal, the Runtime API server:

  1. Stops accepting new connections
  2. Waits up to runtime_api_shutdown_timeout_secs (default: 5s) for in-flight requests to complete
  3. Force-closes remaining connections after timeout

Extending the API

New endpoints can be added in internal/runtimeapi/. See the Contributing guide and Architecture Overview for developer guidance.