HTTP API reference¶
For client-side React integration contracts, see docs/ui-hook-api.md. For response helpers, validation, compatibility, and transport exceptions, see the API design standard.
This page lists every route that is part of Jarela's stable HTTP API. A
route is part of the contract only if its source file carries a @public
JSDoc header; everything else under app/api/v1/* is considered UI-internal
and may change between minor versions.
The contract follows the deprecation policy in
CONTRIBUTING.md → Public API surface:
deprecate in 0.X.0, remove no earlier than 0.(X+1).0.
Auth. All routes require a same-origin request (or the loopback bypass for the bin/CLI). See
lib/auth/access.ts. CORS is opened only on the page-capture endpoint, for the browser extension.Schemas. Request/response shapes are validated with
zodat the route boundary. The schema location is given per-route below — link directly into the source for the authoritative shape.Versioning. All routes live under
/api/v1. A v2 prefix would only appear on a major bump.
Agent advertisements (Unreleased, UI-internal)¶
All /api/v1/agent-advertisements methods require unlocked loopback access and
return Cache-Control: no-store. Strict input schemas reject unknown fields;
request bodies are bounded to 16 KiB. See
lib/agents/advertisement-contract.ts and the owning route.
GET ?agent_id=<saved-id>returns{ policy, advertisement }, checking current sources before exposing generated claims; omit the query for policy.PUT { policy }saves the full explicit generation policy, requiring an existing enabled named model if enabled. No provider fallback.POST { agent_id, action }, withregenerate,disableorenable, performs one operator control and returns current policy/state.
Policy: { enabled, model_config_name, max_calls_per_hour, max_input_chars,
max_output_tokens, timeout_ms }. Advertisement state includes agent ID,
generation ID, source/policy fingerprints, enabled flag, timestamp, safe error
code, pending|ready|failed|stale|disabled status and nullable generated data.
Generated data: { purpose, call_when, do_not_call_for, inputs, output, tools }.
Descriptions are descriptive untrusted data, not execution authority.
Config saves use committed sources and never accept caller-supplied fingerprints
or generated claims. Generation is separate asynchronous work; a successful
policy/config response is not a claim that generation succeeded.
Health¶
GET /api/v1/health¶
Runtime readiness diagnostics. Returns { status, agents, crypto, db_path? }.
The filesystem path is only included for loopback requests.
- Source:
app/api/v1/health/route.ts - Used by: browser extension heartbeat, external uptime monitors
- Auth: loopback or same-origin
GET /api/v1/health/liveness¶
Fast process liveness probe. Returns { status: "ok", activity: { active_runs } }
without opening SQLite or loading agent configuration. This endpoint confirms
the server process can answer requests; it does not claim that every
dependency is ready. The in-app status UI calls it before requesting readiness
diagnostics from /api/v1/health and also treats incoming active-run stream
events as evidence of a responsive server.
- Source:
app/api/v1/health/liveness/route.ts - Used by: in-app server status and crypto fallback warning
- Auth: loopback or same-origin
Threads¶
A thread is a single conversation; messages, tool calls, and run state all live inside one.
GET /api/v1/threads¶
List threads, newest first.
POST /api/v1/threads¶
Create a thread bound to an agent.
- Source:
app/api/v1/threads/route.ts - Body schema:
{ agent_id: string, title?: string }
GET /api/v1/threads/[thread_id]¶
Read messages (paged or "after a marker") for one thread. before/after
are message seq values (the server-assigned monotonic order), not
timestamps — pass the seq of the oldest/newest message you already have.
Pass view=metadata to read only thread metadata, context boundaries, summaries,
and activity, without querying or serializing messages or their usage snapshots.
This response omits messages and has_more. The default view=messages
preserves the full paged response. Unknown views return HTTP 400.
Both views use Cache-Control: no-store so live state stays current.
PATCH /api/v1/threads/[thread_id]/messages/[message_id]¶
Set whether a message appears in the chat window without removing it from
model history. Body: { "hidden_from_chat": boolean }. The message must
belong to the specified thread.
PATCH /api/v1/threads/[thread_id]¶
Rename or rebind the thread to a different agent.
DELETE /api/v1/threads/[thread_id]¶
Drop the thread, its messages, and its checkpoint state. Attachment and
generated files under <dataDir>/files/ that no other message or memory entry
references are deleted immediately rather than waiting for the retention GC.
POST /api/v1/threads/[thread_id]/run¶
Submit a run. Returns 202 { accepted, thread_id, queue_position } after the
user message and assistant draft are committed; output is streamed via the GET
on the same path.
GET /api/v1/threads/[thread_id]/run¶
Subscribe to the in-flight run as a Server-Sent Events stream. Reconnects
resume using the browser's Last-Event-ID header and run-scoped SSE event IDs,
replaying only events after the last received event. A fresh subscription
without a cursor receives the bounded replay buffer, or a full-text snapshot
if earlier events have already been evicted. If the run changed or
the cursor fell outside the buffer, the stream sends a stream_snapshot with
the current full text, available buffered tool events and a recovery notice.
Clients replace their live buffers without marking the agent complete, then
continue listening with the snapshot's cursor. Earlier tool details may be
unavailable. Recovery never restarts actions. Completed runs also send their
terminal event after the snapshot.
PATCH /api/v1/threads/[thread_id]/run¶
Steer the run that is already streaming (ADR-0080). Body:
{ "message": "…", "request_id": "<UUID>" }. request_id is optional for
older clients; clients should send a stable UUID per steering attempt so
network retries do not persist or deliver the same message twice. Reusing an
ID with different message text returns 409 { "steered": false,
"reason": "request_id_conflict" }.
The message is queued and delivered before the agent's next model call,
alongside the tool results it should react to — the run is not aborted.
Returns 409 { "steered": false, "reason": "no_active_run" } when no run is
active, in which case submit a normal POST instead.
DELETE /api/v1/threads/[thread_id]/run¶
Abort the in-flight run. This is the only interrupt; typing mid-run steers.
- Source:
app/api/v1/threads/[thread_id]/run/route.ts - Stream chunk types:
text,thinking,tool_call,tool_result,tool_progress(zero or more per call, live status from a still-running tool — ADR-0073),reset_text(discard prior live text before an output-validator replacement retry),usage,done(seelib/agents/base.tsfor the full union)
Agents¶
GET /api/v1/agents¶
List configured agents.
GET /api/v1/agents/recent-replies¶
Return each agent's latest non-empty, completed assistant reply as
{ agent_id, content } entries. Agents without a completed reply are omitted.
POST /api/v1/agents¶
Upsert an agent config. The full schema (identity, instructions, tools,
model_config_name, harness_id, delegation, etc.) lives in
lib/stores/agent-configs.ts.
warm_summary_limit controls recent summarized context (default 50 messages;
0 disables warm summaries) independently from history_limit's raw history
cap.
usage_strategy may be cost_saving, balanced, or high_reasoning; pass
null to inherit the global JARELA_USAGE_STRATEGY setting.
usage_profile accepts home, work, dev, or custom (Everything; the
default). Omission on update preserves the saved profile. This controls prompt
style, chat diagnostic presentation, and credential discovery, not permissions.
API updates only persist the profile: the agent editor separately submits its
style/citation defaults, which callers may also set explicitly.
Direct style fields are adaptive_empathy (warmth), adaptive_expressiveness,
adaptive_verbosity (response detail), and adaptive_persona_strength
(conversational-cue adaptation), each 0-100. adaptive_persona_enabled enables
these style targets; disabling it preserves their values and still applies the
profile's organization. Strength 0 disables cue-based adjustments.
adaptive_mbti is deprecated compatibility data: it is retained on existing
records but does not influence prompts or reset style sliders.
- Source:
app/api/v1/agents/route.ts
GET / PATCH / DELETE /api/v1/agents/[id]¶
Read, update, or delete one agent config.
- Source:
app/api/v1/agents/[id]/route.ts
POST /api/v1/agents/[id]/duplicate¶
Copy every setting of an agent into a new, non-default agent so a tuned setup
can be reused as a personal template. Optional body { "name": "…" }; the
default name is "<name> (copy)". Conversations are not copied. Returns
201 with the new agent, 404 for an unknown agent, 400 for a blank name.
Tools¶
GET /api/v1/tools¶
Returns every tool in the agent's pool with { name, description, source,
category, capability, group, mcp_server, stats }. Source is
builtin | external | mcp. Capability is read | write | execute.
mcp_server is populated for MCP-sourced tools so clients can render
MCP -> server -> tools permission hierarchies.
Query parameters:
-
q— optional case-insensitive search across name, description, category, capability, source, group, and MCP server. -
Source:
app/api/v1/tools/route.ts - Agent equivalent:
list_toolstool — same data, callable from inside an agent run, with the samequerysearch behavior.
Skills¶
Packaged built-in skills are always readable. User skills are layered on top
from the repos in the skill_repos DB table (ADR-0074) and override built-ins
with the same id. Repos are scanned in the order they were added — a later
repo's skill overrides an earlier repo's (and built-ins) on id collision, so
a team-shared repo can be layered under a personal one. Writes and deletes
always target the one repo flagged writable, so the rest can be read-only
clones; POST /api/v1/skills and friends 503 with "No writable skill repo is
configured" until at least one repo exists (the first repo created becomes
writable automatically unless writable: false is passed explicitly).
GET /api/v1/skills/repos¶
List configured skill repos: { repos: [{ id, path, label, writable, enabled, created_at, updated_at }] }.
POST /api/v1/skills/repos¶
Add a repo. Body: { path, label?, writable? }. path must already exist
and be a directory; 409 if a repo for that path already exists.
GET|PATCH|DELETE /api/v1/skills/repos/[id]¶
Fetch, update ({ label?, enabled?, writable? }), or remove one repo.
Setting writable: true on one repo clears it on any other.
- Sources:
lib/skills/index.ts,lib/stores/skill-repos.ts,app/api/v1/skills/route.ts,app/api/v1/skills/[id]/route.ts,app/api/v1/skills/repos/route.ts,app/api/v1/skills/repos/[id]/route.ts - Agent equivalent:
list_skills,read_skill, andwrite_skilltools (repo management has no agent tool yet — API only).
Checked execution and workflows (Unreleased)¶
These endpoints require unlocked loopback access, reject tailnet identity
headers and return Cache-Control: no-store. Reading resources is still inert.
Full-trust scripts are not sandboxed. The selected agent needs the concrete
execution permission; unsupported process ownership fails explicitly.
| Method and path | Request / result |
|---|---|
GET /api/v1/security/skill-execution |
Policy, safety mode, execution_available and discriminated ownership availability/backend or refusal reason. |
PUT /api/v1/security/skill-execution |
Existing strict policy contract; returns the same status shape. Does not itself execute code. |
POST /api/v1/skill-executions |
{agent_id, skill_id, input_json, workspace}. Exact invocation JSON remains a string. Returns live request ID and pending proposal, or a checked trusted-revision result. |
GET /api/v1/skill-executions/[id] |
Live exact invocation, captured files with hashes/base64, limits and result/error. Never reconstructs consent after restart. |
DELETE /api/v1/skill-executions/[id] |
Cancels a pending/running request; does not roll back effects. |
POST /api/v1/skill-executions/cleanup |
Conservative recognized dead-owner staging sweep; returns removed and retained entries/reasons. |
GET /api/v1/workflow-templates/[skill-id] |
Validates and returns inert selected bundle workflow.json as definitionText plus revision. |
GET /api/v1/workflow-approvals/[id] |
Exact live plan/invocation/captured script review, not a workflow result. |
POST /api/v1/workflows |
{agentId, definitionText, invocationJson?, workspace}; creates a durable run without executing it. |
GET /api/v1/workflows?agentId=...&limit=...&after=... |
Owning agent's runs; default 50, maximum 100. |
GET /api/v1/workflows/[id]?agentId=... |
Owning agent's durable run, current version, checked outputs/evidence and pending typed question. |
POST /api/v1/workflows/[id] |
{action, agentId, version} for advance, cancel, reconcile; answer additionally requires exact revision, stepId, answerJson. |
Use the existing pending-action approve/deny endpoints for execute_skill,
execute_workflow and authoring_operation; these kinds additionally enforce the local/unlocked
boundary. Pending is not success. Script plan consent expires after five minutes
and binds exact run, definition, invocation, workspace and captured revisions.
Typed workflow answers never substitute for it. Version conflicts refuse stale
updates. reconcile accepts no caller-supplied output: the authorized host must
observe through a declared read-back action and validate the original contract.
Encrypted durable state has terminal retention; live executable grants do not.
See lifecycle operations.
Reviewed authoring operations (Unreleased)¶
The same unlocked loopback/no-store boundary applies. Collection requests are strict JSON with a maximum 64 KiB body; caller-supplied approval fields are not accepted. See extension lifecycle.
| Method and path | Request / result |
|---|---|
POST /api/v1/extension-authoring |
{action:"create",agentId,workspace,input} captures an inert synthetic candidate; {action:"request",agentId,input:{id,stage,fixture_origin?}} proposes one effect. |
GET /api/v1/extension-authoring?id=...&agentId=... |
Owning agent's live session, revision and observed status; not a new grant. |
POST /api/v1/contribution-authoring |
{action:"create",agentId,workspace,input} proposes actual isolated tests; {action:"commit"\|"push"\|"draft_pr",agentId,operation_id} proposes that distinct effect. |
GET /api/v1/contribution-authoring?id=...&agentId=... |
Owning agent's live repository/source/test evidence and current stage. |
GET /api/v1/authoring-operations/[id]?type=extension |
Exact live files, installed baseline, destination, policy/permission effects and observed result/error. |
DELETE /api/v1/authoring-operations/[id]?type=extension |
Cancels pending/running work. Does not roll back effects; unresolved execution remains owned. |
Use type=contribution for contribution operation preview/cancellation.
Contribution stages reuse the operation ID but return a new pending proposal and
review revision each time. Complete source/diff previews remain local; the
agent-facing inspection tool returns status metadata only.
Pending-action approval results likewise contain metadata only, so subsequent
model proposal inspection cannot reveal the local code/diff. Approval binds the
trusted pending ID/agent to its exact stage and revision.
Extension stages are stage, test, install, configure, enable, verify,
disable, remove, restore and discard. Only configure accepts
fixture_origin. Restoration selects prior bytes as a candidate, not installed
code; staging/tests/install/enable/verification need fresh reviews. Approval is
the existing local pending-action operation and is consumed once. Sessions
expire after 30 minutes, pending reviews after five minutes. Restart invalidates
both without executing or replaying retained source.
Models¶
GET /api/v1/models¶
List model configs (per-model parameter presets that agents bind to). Each row includes is_default and enabled.
POST /api/v1/models¶
Upsert a model config. Omitting is_default keeps the existing flag; is_default: true clears the flag on every other row and re-enables the model.
- Source:
app/api/v1/models/route.ts
PATCH /api/v1/models/[name]¶
Change model state without rewriting params. Body: { "enabled"?: boolean, "is_default"?: true } (at least one field; enabled: false with is_default: true is rejected with 400). Each request runs in one transaction:
is_default: truemoves the default to this model. Returns 409 if the model is disabled.enabled: falsehides the model from model selection and runtime default/router resolution. Disabling the default promotes the first other enabled model. Disabling the last enabled model returns 409.- Returns 404 for an unknown model.
See ADR-0102.
Providers¶
GET /api/v1/providers¶
Lists every registered LLM provider name (built-in + external .cjs
plugins under ~/.jarela/providers/).
- Source:
app/api/v1/providers/route.ts - Agent equivalent:
list_providersanddescribe_providertools.
GET /api/v1/providers/{provider}/models¶
Lists models available from the selected provider. For OpenAI, Anthropic,
Gemini, DeepSeek, GitHub Copilot, and provider plugins that implement
listModels, Jarela discovers models from the provider API when credentials
are configured. Anthropic, Gemini, and DeepSeek use curated lists when no API
key is configured; GitHub Copilot may also use its curated list when live
discovery is unavailable. GET results are cached per provider for up to
10 minutes; add ?fresh=1 to bypass that server cache.
POST to the same route accepts { "params": { ... } } for credentials or
endpoint settings not yet saved in the model config. These responses are not
cached.
Page capture (browser extension)¶
POST /api/v1/page-capture¶
Receives a page-capture upload from the browser extension and routes it into the most recently active thread on the default agent.
Request body (JSON):
| field | type | required | notes |
|---|---|---|---|
url |
string | yes | Page URL the element was picked from. |
text |
string | yes | Element text content (truncated to 100 KB UTF-8 server-side). |
capturedAt |
string | yes | ISO-8601 timestamp. |
title |
string | no | Document title (≤500 chars). |
selector |
string | no | CSS selector path of the picked element (≤ 2000 chars). |
tagName |
string | no | Tag name of the picked element (≤ 64 chars). |
screenshot |
string | no | Base64-encoded PNG of just the picked element (≤ 4 MB encoded). No data: URL prefix. |
screenshotMediaType |
string | no | MIME type for the screenshot (default image/png). |
When screenshot is present the persisted user message is stored as a
multipart ContentPart[] of [text, image] so the chat UI renders the
picture inline and vision-capable models see it on the silent observer
turn that fires immediately after.
Response: { thread_id, msg_id, agent_id, agent_name, thread_title, created_thread, truncated, originalBytes }.
OPTIONS /api/v1/page-capture¶
CORS preflight.
- Source:
app/api/v1/page-capture/route.ts,lib/api/page-capture.ts - Auth: loopback only (
Host:must belocalhost/127.0.0.1); CORS reflects the requestOriginso the extension passes preflight.
Events (notification stream)¶
GET /api/v1/events¶
Server-Sent Events stream of in-process notifications and transient app state
updates. Run completion, watcher fires, queue progress, and bridge messages are
replayable notifications; activity_update is { type, active_runs }, a
snapshot on connection and a transient update when a run starts or finishes.
It has no replay timestamp and does not advance the replay cursor or consume
notification history.
- Source:
app/api/v1/events/route.ts - Event types: see
lib/notifications/bus.tsfor the full union (run_completed,task_completed,bridge_message_received, etc.)
Out of scope (UI-internal routes)¶
The following endpoints exist but are NOT part of this contract — they serve the in-app UI and are subject to change without notice:
/api/v1/health/probes/api/v1/events/test/api/v1/threads/[thread_id]/activity/api/v1/threads/[thread_id]/context-pin/api/v1/delegations/codex/[job_id]/api/v1/agents/[id]/compact,/display-filters,/thread/api/v1/models/[name]/api/v1/providers/[provider]/models,/github-copilot/auth/api/v1/extensions/.../api/v1/integrations/.../api/v1/mcp-servers/.../api/v1/scheduled-tasks/.../api/v1/watchers/.../api/v1/memory/.../api/v1/dashboard/.../api/v1/voice/.../api/v1/documents/.../api/v1/env-sync/.../api/v1/proxy-config/.../api/v1/access/.../api/v1/config/.../api/v1/harnesses/...
If you build something that depends on any of these, file an issue so we
can decide whether to promote them to @public.