serve binds 127.0.0.1 by default and refuses a non-loopback --bind unless
authentication is configured. Set a bearer token with --token /
RUNEWARD_API_TOKEN, or point RUNEWARD_AUTHZ_FILE at a JSON store of named
principals (each with its own token, launch scope, optional approval-profile
scope, and admin flag) for multi-principal RBAC. When set, the token is required
on every request except /healthz and the static dashboard shell — pass it as
Authorization: Bearer <token> or an X-Runeward-Token header. The browser
obtains a short-lived, single-use scoped ticket for the terminal WebSocket, so
long-lived credentials never appear in URLs. Non-loopback listeners require TLS
via --tls-cert/--tls-key unless --allow-insecure-http explicitly
acknowledges a trusted TLS-terminating proxy; request bodies are capped at 16
MiB. See the
Security model.
Under RBAC, a non-admin principal sees and can act on only its tenant's
resources; admins see all. Different agent principals can share a tenant while
remaining independently attributable. OIDC is enabled with
RUNEWARD_OIDC_ISSUER and RUNEWARD_OIDC_AUDIENCE; signed Rune Box claims map
to the same authorization model as local tokens.
Create a Citadel. Supports profile, admin-only copy_from, and optional parent_citadel, run_id, agent, provider, and model lineage. A child's profile must be the parent's Charter or provably no broader (Charter subsumption); otherwise 403.
GET
/v1/citadels
List Citadels (scoped to the caller under RBAC).
GET
/v1/citadels/{id}
Get one Citadel (includes owner, declared/inferred capabilities, and cumulative usage).
DELETE
/v1/citadels/{id}
Kill and remove a Citadel.
GET
/v1/citadels/{id}/workspace
Download the isolated workspace as a tar archive.
GET
/v1/citadels/{id}/evidence
Download resolved policy + signed audit as portable evidence JSON.
POST
/v1/citadels/{id}/fork
Copy the workspace into a new, independent Citadel (profile, run_id, agent, provider, model, nano_cpus, memory_bytes). A Charter not provably within the source's (Charter subsumption) needs an administrator.
POST
/v1/citadels/{id}/sleep
Put a persistent Citadel to sleep now (409 citadel_busy while calls, terminals or agent turns are active).
POST
/v1/citadels/{id}/wake
Wake a sleeping Citadel (any governed call also wakes it).
GET
/v1/citadels/{id}/workspace/status
Workspace state of a persistent Citadel (running/sleeping, idle timeout, last sleep/wake, forked_from).
GET/POST
/v1/citadels/{id}/schedules
List or create scheduled agent runs for the Citadel.
Runs are durable, provider-neutral lineage records. They survive Citadel
teardown and include tenant, actor, Charter, agent/provider/model, optional
parent run, timestamps, and terminal status.
Method
Path
Description
GET
/v1/runs
List accessible Runs (tenant-scoped under RBAC). Delegated children and forks carry charter_lineage (exact, subsumed or admin-override), parent_charter and charter_proof.
Run Python when the Charter provides the python capability.
POST
/v1/citadels/{id}/code/node
Run Node when the Charter provides the node capability.
POST
/v1/citadels/{id}/file/read
Read a file.
POST
/v1/citadels/{id}/file/write
Write a file.
POST
/v1/citadels/{id}/file/list
List files.
POST
/v1/citadels/{id}/file/search
Search files.
POST
/v1/citadels/{id}/usage
Report model usage. Body: {"tokens":123,"cost_usd":0.04}; accrues toward the Charter's rationing.max_tokens/max_cost_usd budget.
GET
/v1/citadels/{id}/perimeter
Egress (Perimeter) decision log for the Citadel.
GET
/v1/citadels/{id}/terminal
WebSocket terminal (same-origin only).
POST
/v1/citadels/{id}/terminal-ticket
Mint a short-lived terminal ticket.
GET
/v1/citadels/{id}/conversation
Bounded, redacted Live chat history (after_id, limit).
POST
/v1/citadels/{id}/conversation
Publish a user, assistant, tool, or system turn.
GET
/v1/citadels/{id}/conversation/stream
Read-only Live chat WebSocket; use a scoped conversation ticket.
POST
/v1/citadels/{id}/agent-sessions
Launch a durable, observable agent command.
GET
/v1/agent-sessions
List agent sessions, optionally filtered by cohort_id.
GET
/v1/agent-sessions/{id}
Read agent-session metadata and terminal status.
GET
/v1/agent-sessions/{id}/events
Read reconnectable redacted stdout/stderr events after a sequence cursor.
GET
/v1/agent-sessions/{id}/stream
Replay every event after after (or Last-Event-ID), then follow live events over server-sent events.
POST
/v1/agent-sessions/{id}/turns
Start the next turn (command, optional model); 409 session_busy while a turn is active.
POST
/v1/agent-sessions/{id}/cancel
Cancel the active turn (does not rewind).
GET
/v1/citadels/{id}/ide / /ide/{path...}
Experimental browser IDE reverse proxy (HTTP + WebSocket).
POST
/v1/citadels/{id}/ide-ticket
Mint a short-lived IDE ticket.
POST
/v1/tickets
Mint a short-lived, single-use ticket. Body: {"kind":"ide","sandbox_id":"<id>"}; kind may also be terminal or conversation (both need sandbox_id) or download.
Opt-in GUI against /workspace via code-server inside the environment. Requires
RUNEWARD_ENABLE_EXPERIMENTAL_IDE=1, Charter [ide] enabled = true, and an
IDE-capable target (ide or ide-agents) from deploy/Dockerfile.ide. Mint
kind=ide via POST /v1/tickets (or /ide-ticket), then open
/v1/citadels/{id}/ide?ticket=…. The ticket is single-use; a session cookie
covers subsequent asset loads. List/get may include ide / ide_agents.
Limits (summary): not per-keystroke policy; no Cursor/Claude Desktop/Codex
GUIs inside the environment; no first-class GitHub Copilot (Open VSX). Full write-up:
Browser IDE.
Browser automation is experimental and disabled by default. Set
RUNEWARD_ENABLE_EXPERIMENTAL_BROWSER=1 only in a trusted deployment after
reviewing the security model.
Fire a schedule now (policy, precheck and budgets still apply).
A schedule body has exactly one of cron (5 fields, UTC) or every (a duration such as 15m or 1h30m, at least 1m), plus command, optional prompt, precheck, agent and model. See Workspaces and context.
Non-governance failures include a stable code: authentication_required,
authz_denied, not_found, conflict, rate_limited, invalid_request, or
internal_error. Policy decisions remain {"verdict":"deny",...} and approval
pauses remain {"verdict":"require-approval","approval_id":"..."}. SDKs expose
authorization and policy denials as different exception types.
AUTH=(-H"Authorization: Bearer $RUNEWARD_API_TOKEN")# omit when serving without a token: AUTH=()SB=$(curl-s"${AUTH[@]}"-XPOSTlocalhost:8080/v1/citadels-d'{"profile":"ns-auto"}'|jq-r.id)curl-s"${AUTH[@]}"-XPOST"localhost:8080/v1/citadels/$SB/shell/exec"-d'{"command":["echo","hi"]}'curl-s"${AUTH[@]}"-XPOST"localhost:8080/v1/citadels/$SB/usage"-d'{"tokens":1200,"cost_usd":0.03}'curl-s"${AUTH[@]}""localhost:8080/v1/chronicle/verify"curl-s"${AUTH[@]}"-XDELETE"localhost:8080/v1/citadels/$SB"