Skip to content

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 zod at 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 }, with regenerate, disable or enable, 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.

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.


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.

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 (see lib/agents/base.ts for 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.

GET / PATCH / DELETE /api/v1/agents/[id]

Read, update, or delete one agent config.

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_tools tool — same data, callable from inside an agent run, with the same query search 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.

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.

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: true moves the default to this model. Returns 409 if the model is disabled.
  • enabled: false hides 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/).

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.


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.ts for 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.