Painted Wolf Code API reference

API reference for people building local integrations with Painted Wolf Code.

REST + SSE contract for the Painted Wolf Code local service.

  • JSON field names use snake_case on the wire.
  • Resource IDs are UUID strings unless noted.
  • Timestamps are RFC 3339 date-time strings.
  • SSE EventHub payloads use the envelope in docs/schemas/events/envelope.json.

Download the OpenAPI specification or read the SDK guide.

Endpoints

GET /health
Health check
GET /v1/approval-asks
Approval asks by reason over a recent windowA local, on-device view of which reasons have been asking and how they were answered, read from the authorization ledger across every session. Presentation only: nothing here changes whether an ask occurs. A reason a person allowed every time is a reason worth quieting, and the row says where that control is.
GET /v1/approval-grants
List active revocable approval leasesHost-minted chat, project, and device leases — including exact local-service (socket_path) grants. Built for review and revocation; search and sort client-side on title, scope, category, pattern, paths, and granted_at. Unqualified, the list includes chat-scoped leases across every chat, each carrying chat_session_id and session_title for grouping. Pass session_id to narrow chat-scoped rows to one chat.
POST /v1/approval-grants
Create one durable grant from SettingsDeliberate Settings-only creation of one project or device grant: an exact local-service (socket_path) grant after host resolution of a single absolute AF_UNIX socket, or a network host family (host) lease over a registrable site, optionally bound to one tunnel port. Execution cards mint their own ladders; models cannot call this method.
POST /v1/approval-grants/resolve-socket
Resolve one absolute AF_UNIX socket for Settings confirmationValidates type and resolves approved/target paths without writing a grant. Used by the deliberate durable creation confirmation step.
POST /v1/approval-grants/revoke
Revoke approval leases in bulkOne call for the Saved approvals group actions. Each id resolves independently and reports its own outcome; socket grants update the next-process boundary only.
GET /v1/attention
Cross-project attention snapshotEvery session, in every project, that is waiting on the human, running, or errored. Unscoped by project: this answers “what needs me right now” for the projects the user is not looking at, which the project-scoped SSE stream cannot. Idle sessions are omitted, so the row count is bounded by live work rather than by history. Live updates arrive on the attention SSE topic, which is device-scoped and therefore delivered on every open stream regardless of its project filter.
GET /v1/backup
Download a local durable-install backup archiveStreams a format-1 zip of the restorable durable config set plus a online SQLite snapshot of store.db and a manifest.json (replacement scope, size, and sha256 per file). Live-process and behavior-installing control-plane files, including MCP connections and extension setup, are excluded, as are managed credential stores. Conversation and retained project content is preserved without redaction and can contain secrets supplied by the user. A new device needs credentials and integrations configured again; an in-place restore preserves device credentials. Distinct from the scrubbed diagnostics bundle. Local only — nothing is uploaded.
GET /v1/backup/capabilities
Read archive format and transfer limitsAvailable during normal operation and store recovery.
POST /v1/backup/restore
Stage a backup archive for apply on next launchApplies compressed and expanded size limits, rejects unknown entries, validates the archive manifest and per-file sizes and sha256 hashes, and requires a known baseline with a registered upgrade path to this binary. An older supported store is upgraded in staging, then checked for schema, SQLite integrity, foreign keys, and retained object references. Imported retention policies are suspended until explicitly reviewed. Every affected live file is copied to a transaction-scoped .restore-recovery-, present files are fsynced under .restore-staging-, and explicit deletions are staged in an exclusively published restore.pending.json. A second stage is rejected while that marker exists. The live store is not swapped until the next launch preflights the complete staged read set and applies the marker before db.Open. The desktop client restarts its managed backend immediately after staging, before normal work can resume. Restore replaces; it never merges.
POST /v1/backup/restore/recovery-snapshot
Stage the latest compatible upgrade recovery snapshotSelects a self-contained snapshot directory in upgrade-recovery with a known schema baseline and upgrade route, validates its complete payload, and publishes the same restore transaction used by uploaded backups. Local recovery uses copy-on-write files where supported and does not inherit public ZIP upload limits. Recovery includes the store and retained files. The live store is not swapped until the next launch. Available on a healthy server and in recovery mode.
GET /v1/contributions
One complete projection of the captured contribution frame

Captures the current device catalog and MCP resource generations once, then projects the compiled contribution set with its frame_revision. The response is a one-way projection: it has no independent persistence, resolver, merge rules, cache key, or fallback source.

The frame is device-scoped. A project may suggest extensions; it never contributes them. Appearance is device configuration, so the theme list resolves at this scope.

GET /v1/cost/summary
Get cost summary for session or project
POST /v1/debug/den-perf
Ingest Den main-thread perf/stall events into the debug captureFire-and-forget sink for Den freeze attribution (longtask / loop-stall). When LYCAON_DEN_PERF_DEBUG or LYCAON_DEBUG_ALL is on, events append to den-perf.jsonl. When capture is off the handler still returns 204.
POST /v1/delegations
Create a delegation
GET /v1/delegations/{id}
Get delegation by ID
POST /v1/delegations/{id}/abort
Abort a delegation
POST /v1/delegations/{id}/dispatch
Dispatch a delegation leg to a worker
GET /v1/delegations/{id}/legs
List legs for a delegation
GET /v1/detection-packs
List detection packsReads detection packs from disk for the host or filtered by project lens.
POST /v1/detection-packs
Import a detection pack from a folderValidates and optionally copies an allowlisted pack folder into the device detection-packs directory. dry_run previews without writing.
DELETE /v1/detection-packs/{pack_id}
Remove a device detection pack
PATCH /v1/detection-packs/{pack_id}
Enable or disable a detection pack
GET /v1/diagnostics/export
Redacted local diagnostics bundleAssembles a zip containing health, the preflight report, resolved config, and recent log tails — every byte passed through the shared secret scrubber, with secret stores excluded entirely. The response is bytes for the user to save. Nothing is uploaded.
GET /v1/events
SSE EventHub stream for a project or the deviceEach SSE data: line is JSON matching docs/schemas/events/envelope.json. Topic payloads are defined in docs/schemas/events/{topic}.json. Human checkpoints use topic checkpoint (checkpoint.json, OpenAPI CheckpointEvent). Session lifecycle (including async prompt failures) uses topic session (session.json, OpenAPI SessionEvent). Six topics are device-scoped and are delivered on every open stream regardless of the project_id this one subscribed with: attention, cli_open, preflight, settings, providers, and model_policy. Each reports a fact whose audience is the device rather than one project — attention carries the whole cross-project AttentionView precisely to report the projects the client is not watching, and a readiness probe or a settings change still has to reach a window looking somewhere else. The project registry lifecycle topic also reaches every stream, with the affected project retained in its scope, so project switchers stay current. Live delivery and replay use the same audience. Every other topic stays filtered to this stream’s project. events.ValidatePublishScope refuses a project-less key on any other topic, so the set cannot widen by accident.
GET /v1/extensions
The catalog generation in force — packs, units, diagnostics, and desired state

One resolve, projected whole: pack summaries with blocked_reason, the units they resolved to, the diagnostics against them, and the desired state that selected them. A pack summary and a diagnostic about the same pack come from the same generation, so they cannot disagree.

MCP dependency is per contribution, not per pack: see the contribution frame’s requirement readiness. Does not install or enable MCP providers.

PATCH /v1/extensions/configuration
Atomically replace configuration values for one or more packsEach pack value is its complete replacement block; an empty map removes that pack’s configuration. Values are non-secret scalars or string lists and every pack commits under one extension-state revision.
POST /v1/extensions/meta-packs
Resolve a versioned suite and install all member packages in one graphClones from the source, so it can run for minutes; abandoning the request cancels the clone and installs nothing, and a remote that never responds fails the request rather than hanging.
DELETE /v1/extensions/meta-packs/{meta_pack_id}
Remove a community meta-pack cache entry
PATCH /v1/extensions/meta-packs/{meta_pack_id}
Enable or disable every present member pack in a meta-pack suite
POST /v1/extensions/meta-packs/{meta_pack_id}/apply
Enable every member pack in a meta-pack suite
POST /v1/extensions/packs
Resolve and install a versioned pack graphSelects the newest release satisfying version, or the exact ref pin; resolves transitive dependencies; and atomically publishes desired state plus an exact integrity lock. Does not apply profiles or install MCP. Network resolution is cancelable and bounded.
DELETE /v1/extensions/packs/{pack_id}
Remove a cached pack from desired state (stock packs rejected)
PATCH /v1/extensions/packs/{pack_id}
Enable or disable a pack in desired state
POST /v1/extensions/packs/{pack_id}/profiles/{profile_name}/apply
Apply a pack profile into desired state
POST /v1/extensions/packs/{pack_id}/reload
Re-read a linked folder pack from disk and refresh the catalog
GET /v1/extensions/packs/{pack_id}/update
Resolve and compare the complete candidate package graphStock packs update with the app. Development packs reload from disk. Release constraints re-resolve against SemVer tags; exact pins re-resolve only their recorded ref. The response reports every added, removed, upgraded, downgraded, or revised transitive package.
POST /v1/extensions/packs/{pack_id}/update
Resolve and atomically publish the candidate package graphFetches from the remote, so it can run for minutes; abandoning the request cancels the fetch, and a remote that never responds fails the request rather than hanging.
GET /v1/extensions/suggestions
The install proposals this project lists, never appliedReads the project’s extensions.yaml as a suggestion manifest. Packs listed there are proposals to install on the device. Suggest rows do not contribute until a human accepts them; unit disables are separate project settings.
POST /v1/extensions/suggestions/accept
Install suggested packs on the device
GET /v1/extensions/units/{unit_id}
Get one effective unit (providers + bodies)
PATCH /v1/extensions/units/{unit_id}
Update unit enablement or collision owner in desired state
GET /v1/history-storage
Inspect retained history storage
PATCH /v1/history-storage
Apply a reviewed history retention policy
POST /v1/history-storage/preview
Preview eligible historical bodies
POST /v1/history-storage/protections
Protect a project or session from pruning
DELETE /v1/history-storage/protections/{protection_id}
Remove a history protection
POST /v1/history-storage/prune
Prune one reviewed batch of historical bodies
GET /v1/host
Identify the host and the callerReturns the host identity, the contract version it serves, the authenticated person, and the capabilities offered on this connection.
GET /v1/host-resources
List catalog host resources and current machine-state availability
POST /v1/host-resources/refresh
Refresh all host-resource probes and return the new snapshot
PATCH /v1/host-resources/{resource_id}
Set or inherit the exact host-resource access rule for a scope
GET /v1/local-data
List rebuildable local-data bucket presence and sizes
POST /v1/local-data/clear
Clear one or more rebuildable local-data buckets
GET /v1/mcp/providers
List configured MCP providers
POST /v1/mcp/providers
Create an MCP provider overlay rowWrites a user-layer overlay by default (project overlay when project_id is set). Default enabled is false. Enabling registers the provider’s tools; workflow agent tool policy determines which agents receive them.
POST /v1/mcp/providers/check
Check MCP provider connections
DELETE /v1/mcp/providers/{provider_id}
Remove an MCP provider overlay rowRemoves the user or project overlay for this id. Distro base rows remain with device defaults after overlay deletion.
PATCH /v1/mcp/providers/{provider_id}
Patch an MCP provider overlay
DELETE /v1/mcp/providers/{provider_id}/oauth
Revoke MCP OAuth tokens (Sign out)
POST /v1/mcp/providers/{provider_id}/oauth/cancel
Cancel a pending MCP sign-in
POST /v1/mcp/providers/{provider_id}/oauth/complete
Finish MCP Authorization code exchange
POST /v1/mcp/providers/{provider_id}/oauth/start
Begin MCP Authorization (OAuth 2.1 + PKCE)
POST /v1/mcp/providers/{provider_id}/refresh
Reconnect and re-register tools for one MCP provider
GET /v1/mcp/providers/{provider_id}/tools
List discovered tools for an MCP provider
GET /v1/mcp/recipes
List known MCP provider recipesBundled catalog of common MCP providers. Ids are not a wire enum; adding a row to the host YAML is the whole change. Custom (blank URL or command) is the add-dialog escape hatch, not a catalog row.
GET /v1/preflight
Environment readiness probesMachine-state facts about the host the app depends on but does not control: macOS version, config-dir writability, free disk, the bundled git engine, and the bundled scanner and browser engines. Probes run in the sidecar so they resolve binaries the same way the tools do. Reporting only — preflight never installs or downloads anything.
GET /v1/projects
List projects (recent-first)
POST /v1/projects
Create a project (lazy materialization)
POST /v1/projects/clone
Clone a remote into a new folder and open it as a project
GET /v1/projects/detect
Project identity and extension suggestions for a folderReturns path, project_id when the folder is already a root, and project extension suggestions allowed by its trust settings.
GET /v1/projects/{id}
Get a project with embedded roots
PATCH /v1/projects/{id}
Rename a project workspace
GET /v1/projects/{id}/agent-context
Resolve actual repository guidance for one project path
GET /v1/projects/{id}/agent-presence
Agent sessions in the project's filesWhat each chat in the project, with its workers, is reading, is about to change, and has drafted. The projection is ephemeral: it lives in host memory, is bounded per chat, and is empty after a host restart. Live updates arrive on the project-scoped agent_presence SSE topic, one complete chat value per event.
GET /v1/projects/{id}/artifacts
List durable visual artifact metadata for a project
DELETE /v1/projects/{id}/artifacts/{artifact_id}
Delete one durable visual artifactTombstones the artifact and drops its stored bytes when nothing else needs them. The record itself survives: the id may already be spliced into a model-visible tool result, which cannot be rewritten, so it keeps resolving — as a deleted artifact. The response reports what referred to it at the moment of deletion.
POST /v1/projects/{id}/attachments
Stream one attachment body into project host dataThe body is the raw file, not a JSON envelope, so nothing is buffered whole in memory on either side. The host detects the format from the leading bytes, decodes a single-member compression container when it finds one, and materializes the result content-addressed. A video is decoded once in the managed browser, and one whose codec does not play is refused with attachment_undecodable. The returned blob_id is what a prompt references; the client never learns a filesystem path.
GET /v1/projects/{id}/blueprints
List blueprint files under the overlay blueprints/ directory for a project
POST /v1/projects/{id}/blueprints
Create a blueprint markdown file under the overlay blueprints/ directory
DELETE /v1/projects/{id}/blueprints/{blueprint_id}
Delete a blueprint file under the overlay blueprints/ directory
GET /v1/projects/{id}/blueprints/{blueprint_id}
Get blueprint by ID
PATCH /v1/projects/{id}/blueprints/{blueprint_id}
Update blueprint content and/or display title
POST /v1/projects/{id}/blueprints/{blueprint_id}/approve
Atomically approve reviewed blueprint bytes for a workflow revision
POST /v1/projects/{id}/blueprints/{blueprint_id}/launch
Launch a compatible workflow seeded from a file copy
GET /v1/projects/{id}/board
Get pack board snapshot for a projectReturns a slim coordination view by default. Pass detail_level=forensic only for full worker job records (including workspace baselines) — host-debug payload.
POST /v1/projects/{id}/commands/{command_id}/choices/{step_id}
Load one explicitly activated dynamic interaction choice source
POST /v1/projects/{id}/commands/{command_id}/invoke
Invoke a project-bound contribution commandProject-bound kinds: composer_prefill, navigate, external_link. The host captures the current frame, compares the caller’s frame_revision (an identical compiled subgraph proceeds), revalidates observables and authority, and returns a closed typed ui_effect. No session is minted.
POST /v1/projects/{id}/contribution-search/{source_id}
Search one explicitly selected extension source
GET /v1/projects/{id}/cost-report
Get project cost totals and per-session breakdown
POST /v1/projects/{id}/editor-documents
Open or resume a workspace-pinned editor documentOpens writable text as a collaborative document, preserving any unsaved draft. Read-only files return source_read_only and remain available through the source reader.
PUT /v1/projects/{id}/editor-documents/retention
Replace this person's open tab references for a window
POST /v1/projects/{id}/editor-documents/status
Read bounded document metadata without loading text or joining replicas
PUT /v1/projects/{id}/editor-documents/{document_id}
Persist a draft using a compare-and-swap revisionReplaces the host draft without writing the workspace file. The draft must be encodable within the 4 MiB source-file cap; invalid or oversize content is rejected without changing the stored revision.
GET /v1/projects/{id}/editor-documents/{document_id}/changes
List semantic collaborative edits and their undo state
POST /v1/projects/{id}/editor-documents/{document_id}/changes/{change_id}/revert
Selectively undo one semantic edit while preserving later contributions
POST /v1/projects/{id}/editor-documents/{document_id}/discard
Atomically discard the draft back to its durable base
POST /v1/projects/{id}/editor-documents/{document_id}/leave
Leave document presence
POST /v1/projects/{id}/editor-documents/{document_id}/observe
Compare an editor document's saved base with the workspace fileReconciles through the same host transition as filesystem observation. Clean documents adopt current editable bytes; dirty documents retain their draft and saved base and update divergence. Unsupported current bytes leave the document diverged so the client can refresh its presentation.
POST /v1/projects/{id}/editor-documents/{document_id}/presence
Publish ephemeral relative cursor positions
POST /v1/projects/{id}/editor-documents/{document_id}/reload
Replace the draft and base from the pinned workspace file
POST /v1/projects/{id}/editor-documents/{document_id}/resolve
Apply and publish a reviewed merge against exact document and disk versions
POST /v1/projects/{id}/editor-documents/{document_id}/save
Atomically save the workspace-pinned documentThe operation id makes a retry resume the same save rather than create another file mutation.
POST /v1/projects/{id}/editor-documents/{document_id}/secret-spans/mark
Mint a managed secret from bytes already present in this fileStores the value in the encrypted credential vault, admits its exact bytes to outbound screening, and returns the value-free capability. The file is not modified: a {{paintedwolf-secret:…}} reference is rejected by filesystem tools rather than materialized, so the source file remains the credential source of truth and marking is a zero-edit operation.
POST /v1/projects/{id}/editor-documents/{document_id}/secret-spans/preview
Report what marking a range in this document would captureResolves a selected rune range against the document draft and returns the resolved offsets, a shape receipt, and eligibility. Nothing is stored and no value is returned; the host slices its own copy of the draft so the client never sends the bytes it is asking about.
POST /v1/projects/{id}/editor-documents/{document_id}/snapshots
Pin an immutable accepted snapshot for a read or save
POST /v1/projects/{id}/editor-documents/{document_id}/sync
Synchronize a collaborative replica and join presence
POST /v1/projects/{id}/editor-documents/{document_id}/updates
Durably accept an idempotent collaborative update
POST /v1/projects/{id}/findings/export
Export what a ledger query matches as SARIF or OpenVEXThe query runs in the store, so the document contains what the query matched rather than the page the client happened to hold. SARIF carries each project decision as a suppression on the result it covers; OpenVEX carries the advisory judgements as statements. Absences that established nothing are omitted from OpenVEX rather than published as good news.
GET /v1/projects/{id}/findings/ignores
List the project's cross-engine finding ignoresEvery entry in force, bundled and project, with how many ledger rows each one covers now. Entries that could not be honoured are reported separately: a mistyped decision disables itself, never the file.
POST /v1/projects/{id}/findings/ignores
Record one ignore decision in the project's ignore fileAppends the entry to the project’s committed overlay file, so the decision is the same artifact a person could have written by hand and is reviewable like any other change to the repository. The predicates conjoin; an entry naming none is refused.
DELETE /v1/projects/{id}/findings/ignores/{entry_id}
Withdraw one ignore decisionTakes effect on the next read: findings the entry covered return to the open list without a rescan.
POST /v1/projects/{id}/findings/query
Page the project's finding ledger across every selected scannerWhat the project has open and what has left, folded across the authoritative set of each selected scanner. Filtering, ordering, and paging happen in the store, so a page is a page of the project’s set rather than a slice the client reorders. Drill down to the run that recorded an entry’s last event with POST /v1/projects/{id}/scans/{scan_id}/query.
GET /v1/projects/{id}/git/repos
Ordered repository set for a project
POST /v1/projects/{id}/git/repos
Initialize a new git repository in a named project root
GET /v1/projects/{id}/git/repos/{repo_id}/branches
List local branches for the Git tab picker
GET /v1/projects/{id}/git/repos/{repo_id}/changes
Page changed paths from a cached status revision
POST /v1/projects/{id}/git/repos/{repo_id}/checkout
Switch to a branch (optionally creating it first)
POST /v1/projects/{id}/git/repos/{repo_id}/commit
Stage and commit working-tree changes
POST /v1/projects/{id}/git/repos/{repo_id}/commit-message
Draft a commit message from the current diff via the lite model
POST /v1/projects/{id}/git/repos/{repo_id}/discard
Revert the working tree to a clean state
POST /v1/projects/{id}/git/repos/{repo_id}/pull
Fast-forward the current branch from its upstream
POST /v1/projects/{id}/git/repos/{repo_id}/push
Publish the current branch to its upstream
POST /v1/projects/{id}/git/repos/{repo_id}/stash
Stash working-tree changes (including untracked files)
GET /v1/projects/{id}/git/repos/{repo_id}/status
Working-tree status for the Den Git tab
DELETE /v1/projects/{id}/promotion
Cancel an uncommitted draft save and restore the chosen empty folder
POST /v1/projects/{id}/promotion
Save a draft into a folder through a restart-safe transition
GET /v1/projects/{id}/removal-assessment
Assess optional extension cleanup for project deletion
POST /v1/projects/{id}/removals
Delete a project and settle reviewed optional extension cleanup
GET /v1/projects/{id}/removals/{operation_id}
Read the recorded project removal outcome
POST /v1/projects/{id}/roots
Attach a folder root to a project
DELETE /v1/projects/{id}/roots/{root_id}
Detach a folder root from a project
PATCH /v1/projects/{id}/roots/{root_id}
Update a folder root (set primary or label)
GET /v1/projects/{id}/scanners
List scanners with project-tier enablement
PATCH /v1/projects/{id}/scanners/{scanner_id}
Update project enablement overlay for one scannerWrites scanners.yaml in the project overlay directory with id+enabled only.
GET /v1/projects/{id}/scans
List code scans for a project
POST /v1/projects/{id}/scans
Run a full pass of every admitted fileRecords a full pass in which each named scanner scans every file of one source generation, in resumable chunks. Members start together: a scanner still finishing earlier work holds the pass until it is free. An unfinished pass that covers the named scanners is joined, and one that has not started takes the new scanners, rather than another pass starting.
GET /v1/projects/{id}/scans/{scan_id}
Get code scan result
POST /v1/projects/{id}/scans/{scan_id}/query
Query stored scan guidance
GET /v1/projects/{id}/scans/{scan_id}/sarif
Export stored scan findings as SARIF 2.1
GET /v1/projects/{id}/secret-ignore-candidates/{candidate_id}
Review a live unprotected secret detectionOnly available during the originating approval and never for protected values. Plaintext is returned only for the explicit human review and is not persisted in the checkpoint.
GET /v1/projects/{id}/secret-ignores
Read the project's ignored public values
POST /v1/projects/{id}/secret-ignores
Declare one exact public value in the project's ignore fileWrites plaintext following an explicit human review. Does not release any held request. The entry id makes identical retries idempotent.
DELETE /v1/projects/{id}/secret-ignores/{entry_id}
Withdraw one declared public value from the project's ignore fileRemoves the entry the listing published, preserving the file’s other section and its comments. Withdrawing an exception only narrows what the project ignores, so it does not require the scanning trust surface. Covered findings return to normal screening without a rescan.
GET /v1/projects/{id}/secrets
List managed secret metadata for a projectReturns names, creation origins, scopes, references, and lifecycle state. Secret values are never returned.
POST /v1/projects/{id}/secrets
Store a value a person entered in project settingsMints a project-scoped capability from a credential the person typed in. The value travels in this request body and nowhere else: it is written to the encrypted credential vault and is not returned by this response or any later read.
DELETE /v1/projects/{id}/secrets/{secret_id}
Permanently revoke one managed secretDisables reference resolution and authenticated reveal permanently. Protected bytes remain only for provider screening until project deletion.
PATCH /v1/projects/{id}/secrets/{secret_id}
Administer one managed secretUpdates labels, agent-use deadline, and scope. Nothing here reaches the stored value, an external credential, or a project file, and a revoked capability is terminal.
POST /v1/projects/{id}/secrets/{secret_id}/reveal-challenges
Begin one authenticated human revealCreates a short-lived, single-use challenge for the installed desktop shell. This operation returns no credential bytes. A bearer-authenticated caller without the native shell’s challenge-bound proof cannot complete the reveal.
POST /v1/projects/{id}/secrets/{secret_id}/reveal-challenges/{challenge_id}/complete
Complete one authenticated human revealConsumes one fresh challenge and a native-shell signature produced only after operating-system user verification. The challenge is consumed by the first completion attempt. Success is durably audited before the current value is returned. The response body is categorically withheld from every debug capture.
GET /v1/projects/{id}/secrets/{secret_id}/uses
Recent substitution attempts for one managed secretA bounded rolling window of what the host did with this reference, newest first — successful substitutions and refusals alike. Records who asked, never the value and never the argument it was going into.
PUT /v1/projects/{id}/secrets/{secret_id}/value
Replace or restore one managed secret's stored valuePuts new bytes behind an unchanged reference. Only Painted Wolf’s protected copy changes; this does not modify a project file or rotate, revoke, or activate an external credential. The replaced value is retired rather than deleted, so provider screening keeps redacting a credential the agent has already seen; a retired value never resolves. The same call restores a capability whose stored value went missing.
GET /v1/projects/{id}/security
Get the project's scan coverage, baseline, and running full pass
GET /v1/projects/{id}/sessions
Page a project's chat sessionsMetadata-only chat browsing for one project. Worker child sessions are never listed. The default view is the active set; archived=true selects the archived set instead. pinned narrows either set to pinned or unpinned chats.
DELETE /v1/projects/{id}/source
Move a project file or folder to TrashMoves one file or folder into the macOS Trash under the same path jail as the source read. The entry is never permanently deleted — if the Trash move fails the path is left untouched and the call returns 500 source_trash_failed. Non-empty folders require recursive=true; without it the call returns 409 source_not_empty. Engine and VCS metadata paths (.git, the project overlay directory) and the root itself are refused.
GET /v1/projects/{id}/source
Read a project file for the in-app source viewer

Returns UTF-8 file content under the same path jail as agent tools (evidence.NormalizeCitationPath + sandbox symlink escape rejection). Editable ceiling is 4 MiB. Over-limit and binary (including image) reads return metadata with empty content — never a truncated prefix. Image bytes load from GET …/source/raw. UTF-16 text with a byte-order mark is decoded for editing and restored to the matching on-disk encoding on save. BOM-less UTF-16 is reported as binary metadata by default; a human may explicitly reopen it with decode_as=utf-16le or decode_as=utf-16be. Unsupported codepage text is refused with 415 unsupported_encoding (never source_binary). Successful text reads carry encoding and writable.

Pass worker_id to read a write worker’s file as the worker left it. A write worker mutates an isolated overlay copy, not the project roots, so until promote lands the change the path exists only on that overlay. With worker_id an available overlay is the exclusive source, including when a file is absent there; it never falls through to a primary copy. Without an available overlay, only the project roots are searched. A worker_id that is unknown or belongs to another project contributes no overlay — the read falls back to the project roots.

Pass root_id to pin the read to one attached root. Omitted, every root is searched primary-first — ambiguous when two roots hold the same relative path, so root-addressed surfaces (the Files tree) always pin.

POST /v1/projects/{id}/source
Create a new, empty project file or folder from the Files treeCreates a file or folder that does not exist yet, under the same path jail as the source read; kind picks which. Missing parent folders are created along the way, so one call handles a nested path. The target must be a new path — an existing file or folder is refused with 409 source_already_exists rather than overwritten or adopted; use PUT to change a file that already exists. A new file starts empty and is then read like any other file, so its first content arrives through PUT. Creation always lands on the project roots — worker overlays are read-only.
PUT /v1/projects/{id}/source
Save an in-app editor change to a project fileOverwrites an existing project file with full replacement content, under the same path jail as the source read. Only files the viewer could fully load are writable: the target must already exist, be a regular non-binary file, and be at most 4 MiB before and after the save. encoding must exactly echo the representation named by the read, and base_sha256 must echo the sha256 the read (or previous save) reported for this file; when the on-disk file no longer hashes to that value the save is refused with 409 source_write_conflict so an edit can never silently clobber a concurrent change. Saves always land on the project roots — worker overlays are read-only.
GET /v1/projects/{id}/source/attribution
Agent line provenance intervals for one fileIntervals are workspace-scoped. Pass session_id so a chat bound to a worktree reads provenance from the tree it is showing; omitted, the project’s own roots answer.
GET /v1/projects/{id}/source/browse
List one directory level for the project Files treeLists the immediate children of a directory under one attached root so the Files tree can expand lazily. Dot-entries and the project overlay directory, VCS metadata, and ignored directories are included. Symlinked children are classified by their target and dangling links are dropped. Entries come back folders first, then case-insensitive by name. The browse follows the same root jail as the source read: a dir that resolves outside the selected root — including through a symlink — is denied.
POST /v1/projects/{id}/source/commits/{commit_sha}/restore
Restore the file's bytes as one commit holds themApplies a listed commit’s blob as a new user-authored primary workspace version. Bytes come from the repository object store and must hash to the request’s blob_oid; a drifted or unreachable object returns 422 without touching the working tree. The required base tip provides compare-and-swap protection, matching version restore. The restored bytes equal the commit’s, so the merged listing re-labels the new row with the commit identity through the same blob join.
GET /v1/projects/{id}/source/comparison
Load exact endpoints for a Walk effect, file version, or lens range

Effect mode compares the explicit before_version_id and after_version_id stored on one Walk effect. Version mode compares the selected immutable version with its parent. Neither mode reads the live filesystem or a successor row.

Commit path mode (path + root_id + baseline=commit) compares the attached root’s HEAD with the live working path without creating source history. expected_head, when present, rejects a changed HEAD with 409; an empty expected_head names an unborn repository.

Scope mode (file_id + baseline) compares the oldest effect pre-image in that lens with the current tracked head. It exists for the tree and summary lenses; Walk itself always uses effect mode. Under the presentation baseline the range starts after the acknowledged version. Fully reviewed files answer with in_range false.

Reviewed mode (file_id + reviewed_through_ordinal) compares the immutable endpoints covered by that look. A replaced or withdrawn look answers 409. Reading reviewed history never acknowledges subsequent edits.

Git object mode (blob_oid + root_id) reads the endpoints straight from the repository object store: after is the named blob, before the optional before_blob_oid. It previews a listed commit — both sides are verified against the ids they were fetched by.

Exactly one mode per request. A baseline holding no change for the file answers 200 with in_range false, because an empty range is a valid scope answer.

POST /v1/projects/{id}/source/comparison-digests
Measure comparisons without opening viewsAnswers each comparison’s line counts and changes-presentation size, in request order, without opening views. Measuring neither decorates nor retains a document. A failed source answers its own failure; the others still answer.
POST /v1/projects/{id}/source/copy
Copy a project file or folder within one rootCopies one file or folder (recursive) to a new root-relative path under the same attached root. Modes are preserved. An occupied destination returns 409 source_already_exists. Cross-root copies return 400 source_cross_root. Engine and VCS metadata paths and the root itself are refused.
POST /v1/projects/{id}/source/definition
Resolve declarations for an identifierNarrows whole-word search hits in the active root to files that declare the exact identifier (AST confirmation). Mentions that are not declarations never appear as candidates. One candidate jumps; several are ranked for a picker; zero is a successful empty list so the client can open prefilled search. Caps set truncated. Languages without a grammar return an empty list with 200 — never an error.
POST /v1/projects/{id}/source/editable
Make a read-only project file editableAdds only the owner-write permission bit to one existing regular file. The path is resolved inside the selected project root without symlink traversal, and base_sha256 must still match the bytes returned by the source read. Repeating the same request is safe. Worker overlays and historical versions never call this operation; their toolbar actions return to the current project file instead.
GET /v1/projects/{id}/source/editorconfig
Resolve the EditorConfig pairs in effect for a project pathResolves .editorconfig files from the path’s directory toward its attached root, stopping at root = true or at the root itself, with nearer files and later sections winning. The host applies the same resolution when it checks agent writes. The file itself need not exist. Unrecognized keys and values are ignored; unset clears a pair from a farther file. An absent property is not declared for this path.
GET /v1/projects/{id}/source/file-briefings
Get the AI briefing for one exact source presentationcurrent accepts worker_id; document requires document_id and document_revision; version requires version_id. Fields for other presentation kinds are rejected.
POST /v1/projects/{id}/source/file-briefings
Request an AI briefing for one exact source presentation
GET /v1/projects/{id}/source/git-changes/{git_change_id}/review
Review the files recorded by an observed Git movementReads Git objects using an observed movement owned by this project. Paths and counts are limited to the attached root. No source effects are synthesized and neither HEAD nor working-tree bytes are compared. Commit, amend, merge, cherry-pick, revert, and clone default to the destination commit versus its first parent (empty tree for an initial commit). Other movements default to their recorded endpoints, or to the destination commit when no previous tip was recorded. Missing objects are unavailable, never an empty successful review.
GET /v1/projects/{id}/source/history
Get reversible project-file lifecycle historyReturns the exact host-owned undo and redo heads. Editor text history is separate.
POST /v1/projects/{id}/source/history/redo
Redo the current project-file lifecycle operationReapplies the exact displayed history head with filesystem fingerprint and destination-vacancy checks.
POST /v1/projects/{id}/source/history/undo
Undo the current project-file lifecycle operationApplies the exact displayed history head with filesystem fingerprint and destination-vacancy checks.
GET /v1/projects/{id}/source/index
Read the compact revisioned project source index summary
GET /v1/projects/{id}/source/operations
List active and recent file operationsReturns operations owned by the initiating person, with active operations first and recent terminal results next.
GET /v1/projects/{id}/source/operations/{operation_id}
Read file operation progress and completionAvailable only to the initiating person who can still invoke the original operation. Receipts survive restart. An interrupted operation is retried only by an explicit retry request after root authority is revalidated.
POST /v1/projects/{id}/source/operations/{operation_id}/cancel
Cancel a file operation
POST /v1/projects/{id}/source/operations/{operation_id}/retry
Retry a file operation
GET /v1/projects/{id}/source/pins
List named source pins
POST /v1/projects/{id}/source/pins
Pin the current source state
DELETE /v1/projects/{id}/source/pins/{pin_id}
Delete a source pin
PATCH /v1/projects/{id}/source/pins/{pin_id}
Rename a source pin
GET /v1/projects/{id}/source/raw
Read a jailed image payload for the in-app image viewerStreams image bytes under the same path jail as GET /source. Content type is sniffed from the first bytes (not the extension). Only the locked image MIME set is served; non-images return 415. Payloads over 8 MiB return 413. SVG is served as image/svg+xml for <img> rendering only — never as injectable markup.
POST /v1/projects/{id}/source/rename
Rename or move a project file or folder within one rootMoves one file or folder to a new root-relative path under the same attached root. Missing parent folders on the destination are created. An occupied destination returns 409 source_already_exists. A path that exists only under a different root returns 400 source_cross_root. Engine and VCS metadata paths and the root itself are refused.
GET /v1/projects/{id}/source/revisions
Resolve a typed commit, branch, or range to Git comparisonsReads one revision token in every attached root backed by a Git repository, or only root_id, and answers the two commits it names in each root where it resolves. A commit compares with its first parent (the empty tree for a root commit). A local branch that is checked out compares its tip with its parent; any other local branch compares its merge base with HEAD with HEAD. A..B compares A with B; A…B compares their merge base with B; an omitted side is HEAD. A revision that resolves nowhere answers 200 with no comparisons. A token that starts with a dash or holds whitespace or control characters is rejected before Git runs.
GET /v1/projects/{id}/source/revisions/review
Review the files between two commits of a root's repositoryPages the files that differ between two full commit ids in the named root’s repository, limited to that root. An empty before compares after with the empty tree. Nothing is recorded, and neither HEAD nor working-tree bytes are compared. Missing objects are unavailable, never an empty successful review.
GET /v1/projects/{id}/source/search
Search the cached project source index
GET /v1/projects/{id}/source/seen
List the files the reader has looked atFiles whose latest look is still current, newest look first, each with the effects that look covered. A file with anything new since its look is listed by the presentation walk instead, never here.
POST /v1/projects/{id}/source/seen
Record that the reader looked at a fileAdvances the file’s watermark through the displayed effect and records the range the look covered, so the file moves from new to seen. Any primary-tree effect is a valid token; an earlier or repeated look changes nothing. Agent effects at or below the token leave the new-from-AI count.
DELETE /v1/projects/{id}/source/seen/{file_id}
Mark a file unseenWithdraws the file’s latest look: the watermark returns to where that look began, so everything it covered counts as new again, and its agent effects rejoin the new-from-AI count.
GET /v1/projects/{id}/source/storage
Get retained content usage relevant to a project
GET /v1/projects/{id}/source/symbols
List declarations in one project fileReturns document-order outline symbols (functions, methods, types, classes, constants, headings) for one root-relative file. Languages without a grammar return an empty list with 200. Capped at 2 000 symbols with truncated set when the cap is hit.
GET /v1/projects/{id}/source/versions
List one file's merged history — retained versions and git lineage

Every retained state of the file, across primary and worker workspaces, newest first. It is not filtered by a presentation baseline or Walk range, and it is not limited to states a recorded effect produced: a tracked baseline and a preserved pre-image are restorable states and page here like any other.

When the file’s root is a git checkout the response also carries one page of the file’s commit lineage, joined server-side: a commit whose blob equals a retained version collapses into that version’s row, a commit that arrived through an observed ref movement names it, and the rest belong to the git-history era below the tracked-since boundary. The two lanes page independently. next_cursor continues every requested lane that holds more; a continuation answers an exhausted lane empty, with git_history_state not_requested for the Git lane. A cursor continues only the file, lane, and chat workspace that issued it. Select one lane for phased rendering or paging.

POST /v1/projects/{id}/source/versions/{version_id}/restore
Restore one retained version as the working fileApplies the selected immutable state as a new user-authored primary workspace version. Exact retained bytes are used, including binary content. The target remains at its current path across historical renames. The required base tip provides compare-and-swap protection; a changed or unexpectedly present/absent target returns 409. Content that was not captured or is unavailable returns 422 without touching the working tree.
POST /v1/projects/{id}/source/views
Create a source viewAccept a tree or comparison view without waiting for complete preparation. operation_id is idempotent within the authenticated person, project, and client identity. Agent capture exclusions never restrict this human presentation. The host validates the selected source and workspace; client_id grants no authority. On every view, presentation, and interest route, a handle the host does not retain for the caller (never issued, expired, released, evicted, or issued before a restart) answers 404 source_view_not_found; reopen with the same intent. A view addressed by a deleted chat ends with it, and creating one answers session_not_found. An address in a folder that is not attached answers root_not_found.
DELETE /v1/projects/{id}/source/views/{view_id}
Release a source view and its work interestsA stage detach does not release the view. Releasing a view cannot invalidate shared resources pinned by other views or active reads. Release needs no live chat. A view the host does not retain answers source_view_not_found; a client treats that as already released.
GET /v1/projects/{id}/source/views/{view_id}
Read source view state and revisions
POST /v1/projects/{id}/source/views/{view_id}/apply
Change source presentation intentDeduplicate operation_id before checking expected_intent_revision. A different body with the same operation_id is rejected. Tree reveal and disclosure are explicit mutations; frame and locate reads never expand folders.
DELETE /v1/projects/{id}/source/views/{view_id}/interests/{interest_id}
Release source viewport demand
PUT /v1/projects/{id}/source/views/{view_id}/interests/{interest_id}
Replace source viewport demandNewer sequences supersede previous demand for this consumer. Preparation is bounded and never changes presentation coordinates. Reordered older requests are ignored. Interests expire after thirty seconds without replacement. A presentation_id this view no longer presents answers source_view_revision_changed, as a navigation basis does.
POST /v1/projects/{id}/source/views/{view_id}/presentations
Acquire an immutable source presentationRetain complete coordinates for the requested intent revision. Preparation returns source_view_preparing without acquiring a resource. Retrying operation_id returns the same retained resource. Progress events cannot replace this presentation.
DELETE /v1/projects/{id}/source/views/{view_id}/presentations/{presentation_id}
Release a source presentationRelease is idempotent. A presentation belongs to one view; under any other view it is not retained and is already released.
GET /v1/projects/{id}/source/views/{view_id}/presentations/{presentation_id}/locate
Locate an address without changing disclosure
GET /v1/projects/{id}/source/views/{view_id}/presentations/{presentation_id}/rows
Read a bounded coherent source frameAt most 200 rows and 256 KiB including ancestor and layout context, read from the immutable presentation. Reads never discover directories, advance revisions, or change the extent. Endpoint bodies and full child arrays are not returned.
GET /v1/projects/{id}/source/views/{view_id}/presentations/{presentation_id}/search
Find within one source presentationContinuations are opaque and bound to the query and projection revision. Comparison search reads pinned source text; tree search reads labels in the current projection. This does not replace repository-wide source search.
GET /v1/projects/{id}/source/walk
List Walk effects by lens baselineFile-first cross-session groups. baseline is a query parameter only (presentation default; turn:session; turn:session,turn; session:id; commit; pin:id). turn:{session_id} names that chat’s current user turn; the host resolves the ordinal in the same read, and the response’s baseline spells it, so later pages and per-file comparisons address the turn this page listed. A chat baseline (turn or session) reads that chat’s workspace: session_id may be omitted and, when sent, must name the same chat. The presentation baseline lists what is new since the reader last looked at each file: every visible primary-tree effect above the file’s watermark. Files whose latest look is still current are listed by GET /source/seen instead. Commit mode pages Git status paths, including paths without recorded history; other modes page observed effects, newest first. Both page with cursor/next_cursor. A cursor continues only the baseline, workspace, and filters of the page that issued it; a turn baseline continues the turn the response spelled. Commit rows carry current comparison metadata and at most 100 recent recorded effects.
GET /v1/projects/{id}/source/walk-summary
Read complete Walk chapter counts for visible transcript turns
GET /v1/projects/{id}/source/workspace
Resolve the physical source workspace for a project and chatIdentity and physical roots of the tree the Files surfaces enumerate, derived from the project’s session-resolved root set. The root map is authoritative for both file reads and human path actions such as Copy path, Reveal, and Open in editor. The identity addresses a tree for listings, events, and watcher coverage; it is not a history identity, and nothing durable is keyed by it. Editor documents follow the logical file and need no workspace.
GET /v1/projects/{id}/trust
What this project supplies to the agent and which of it applies
PATCH /v1/projects/{id}/trust
Switch project surfaces on or off
POST /v1/projects/{id}/trust/review
Mark the shared Trust comparison read without replacing it
GET /v1/provider-kinds
List ship-catalog provider kinds for the Add AI provider picker
GET /v1/providers
List live model-provider instancesReturns AI providers from providers.local.yaml (plus credential status from the encrypted credential vault). Ship providers.yaml kinds are Add-picker templates via GET /v1/provider-kinds — not listed here until the user adds an instance. Host platform filtering may omit unsupported instances.
POST /v1/providers
Add a provider instance under a chosen id
DELETE /v1/providers/{provider_id}
Delete a provider instance
PATCH /v1/providers/{provider_id}
Update a provider instance
DELETE /v1/providers/{provider_id}/credential
Remove stored provider API key
PUT /v1/providers/{provider_id}/credential
Store provider API key (write-only)
POST /v1/providers/{provider_id}/refresh
Drop cached model discovery and return the freshly discovered provider
POST /v1/providers/{provider_id}/test
Test provider credentials via live model discovery and refresh the model listProbes the provider the same way Refresh does — live model listing with stored credentials — and returns the probe result.
GET /v1/scanners
List merged scanner catalog with install status
POST /v1/scanners
Create a user-layer external scanner or add from catalogWrites {configdir}/scanners.yaml.
GET /v1/scanners/catalog
List known third-party scanners we can driveBundled catalog of scanner CLIs the host knows how to invoke. No binaries are shipped or installed; each row carries the install command and whether the binary is currently on PATH.
POST /v1/scanners/check
Check installed scanner binariesRuns each external scanner’s probe command (usually –version) and reports whether the installed binary actually works on this device.
PUT /v1/scanners/slots/{category}
Choose the single scanner for one category slotExactly one scanner is enabled per slot (sast, sca, secret). Selecting is a replacement: the previous occupant is disabled in the same write.
DELETE /v1/scanners/{scanner_id}
Delete a user-layer scanner overlay rowHost stock remains. 404 when no user row exists.
PATCH /v1/scanners/{scanner_id}
Update a user scanner definitionHost stock may be replaced only by a complete external definition. Slot selection is a separate atomic operation.
POST /v1/search
Global federated search across all projects
POST /v1/search/export
Export global search results as JSONL, CSV, or SARIF
POST /v1/search/replace/apply
Apply a previewed replace-across-files operationApplies kept hunk indices per file only when the on-disk sha256 still matches the preview. Mismatches are skipped with a reason; remaining files continue. Never force-applies.
POST /v1/search/replace/preview
Preview a replace-across-files operationRuns the code search match pipeline and returns per-file hunks with sha256 guards. Capped at 500 files / 5 000 hunks with truncated set when the cap is hit. Binary and oversize files are omitted.
POST /v1/sessions
Create a coordinator session
DELETE /v1/sessions/{id}
Permanently delete a chat session and its historyRemoves the session, its transcript, and its session-keyed projections. Refused with session_not_idle while a turn is in flight; worker child sessions are never deletable through this surface.
GET /v1/sessions/{id}
Get session by ID
PATCH /v1/sessions/{id}
Mutate session lifecycle state (title, archived, pinned, pin position)
POST /v1/sessions/{id}/abort
Stop all activity attached to a session treeStops the session and every attached child activity. The request rejects new work while it cancels and joins queued prompts, child sessions, workers, workflows, checkpoints, tool invocations, and background session tasks. It returns after the session is idle and leaves persisted messages and results intact.
GET /v1/sessions/{id}/artifacts
List durable visual artifact metadata for a session tree
POST /v1/sessions/{id}/artifacts
Persist a live-tool recording as a visual artifact

Accepts a client-created low-frame-rate baseline H.264 MP4 recording of authenticated live-preview frames produced by a held page tool. The sidecar enforces the recording MIME type and artifact byte cap before writing through the shared visual artifact store.

Identity is durable, not process-local. operation_id is one client action, so a retried upload lands on the record it already wrote rather than failing; holder + page + assistant_message_id + tool_call_id is one invocation-specific product slot, so re-recording that invocation replaces its recording rather than multiplying it. Both identities survive a sidecar restart. The upload path session is the page holder (coordinator root or worker child), and Den remounts the recording at the originating ordered tool row.

GET /v1/sessions/{id}/artifacts/{artifact_id}
Fetch visual artifact bytes for a session tree
GET /v1/sessions/{id}/background
List recoverable background processesSnapshot of visible background processes for a session, bounded to active and retained recoverable background processes for this session: command jobs and read-only calls held past their foreground wait, which report a state and no output. Den uses this read-only endpoint to rebuild its process cards after a frontend reload; incremental output remains on the process SSE topic.
GET /v1/sessions/{id}/background/{process_id}/output
Get the retained output tail of a background processReturns the screened tail of one visible background process’s retained output with its lifecycle state. The snapshot is bounded by the host and takes no position: truncated reports cut or evicted output, and live output continues on the process event topic.
POST /v1/sessions/{id}/background/{process_id}/stop
Stop a running background processCommand endpoint that terminates a background command process, or cancels a held call, started in the session. RPC-style, not a REST resource — background process lifecycle is a host control action, and generic tool invocation stays on the agent loop rather than the HTTP surface.
GET /v1/sessions/{id}/bootstrap
Read the authoritative first-paint session snapshot
GET /v1/sessions/{id}/checkpoints
List human checkpoints for a session
POST /v1/sessions/{id}/checkpoints/{checkpoint_id}
Resolve a pending human checkpoint
POST /v1/sessions/{id}/commands/{command_id}/invoke
Invoke a session-bound contribution commandSession-bound kinds: editor_action, workflow_start, and mcp_tool. The host captures the current frame, compares the caller’s frame_revision with subgraph equality, resolves the command from the captured frame, revalidates observables and authority, and executes against that same frame.
POST /v1/sessions/{id}/compact
Force context compaction for a session
POST /v1/sessions/{id}/composer-secrets
Protect selected composer text for this task
GET /v1/sessions/{id}/context
Debug session context token budget and compaction stats
GET /v1/sessions/{id}/coordinator-context
Host-built coordinator workflow run context (same block as Prompt prepend)
GET /v1/sessions/{id}/drafts/{slot_id}/versions
Superseded coordinator draft bodies for one slotBounded read of prior draft attempt snapshots for the specified coordinator draft slot.
GET /v1/sessions/{id}/elevated-access
Get live elevated access for a chat and its workers
POST /v1/sessions/{id}/elevated-access/revoke
Revoke all currently applicable elevated-access approvalsShared project and device records are revoked for all consumers. Running processes retain already-issued access.
GET /v1/sessions/{id}/export
Export one session transcript as Markdown or JSONDownloads a portable copy of one conversation. format=md (default) is a human-readable conversation with transcript chrome filtered out. format=json is the full-fidelity SessionTranscriptPage. Filename is derived from the session title and the first eight characters of the session id.
GET /v1/sessions/{id}/findings
Findings digest for a session
DELETE /v1/sessions/{id}/git-worktree
Remove a chat's worktree checkoutRemoves the separate checkout and deletes the binding row. The branch and its commits are kept. A dirty worktree is refused. A missing worktree directory is always removable — that is the repair for a stale binding.
GET /v1/sessions/{id}/git-worktree
View a chat's worktree bindingReturns the worktree binding for a chat. An unbound chat returns bound false with zero counts. A missing worktree directory returns state stale without probing the path.
PUT /v1/sessions/{id}/git-worktree
Create a worktree for a chatCreates a separate checkout of one project repository on a new branch, outside the project folder. Refuses when the chat is busy, already bound, the branch exists, the repository HEAD is detached, or the target directory exists. The repository’s shared git config is proven unchanged.
POST /v1/sessions/{id}/git-worktree/land
Merge a chat's branch into its base branchMerges the session branch into the recorded base branch in the project folder with an explicit merge commit. Runs only when the chat is idle, both trees are clean, and the project folder is on the base branch. A conflicting merge is aborted; nothing is left half-applied. Landing does not unbind.
GET /v1/sessions/{id}/invocations
List durable invocation receipts for a session
GET /v1/sessions/{id}/messages
List a paginated Ord window of session messagesReturns one contiguous message window ordered by immutable Ord, plus the transcript watermark. Omit before/after to fetch the newest page (live tail), or from=oldest for the first page. Pass a page’s before_cursor as before for the older window, or its after_cursor as after for the newer one; before_message_id and after_message_id open the window beside a message the client already holds. A request names at most one of these positions. worker_id restricts a reused child session to one immutable worker run before pagination is applied.
GET /v1/sessions/{id}/messages/{message_id}/content
Read a bounded range of retained chat content
GET /v1/sessions/{id}/messages/{message_id}/content/search
Search retained chat content without downloading its body
POST /v1/sessions/{id}/navigation
Resolve a settled message's navigation destinations
GET /v1/sessions/{id}/previews
List live preview page attachments for a sessionRead-only preview attachment snapshot for frontend reload recovery, bounded to active browser pages held by this session. Returned rows identify held pages only; a visible Den pane starts its own watch to receive fresh image frames over SSE.
POST /v1/sessions/{id}/previews/watch
Toggle live page preview watch for a sessionCommand endpoint that starts or stops the host CDP screencast for a held (or in-flight drive) page when Den’s preview pane is visible. Read-only mirror only — no input is accepted. Frames stream on SSE topic preview.
GET /v1/sessions/{id}/progress
Coordinator progress digest for a session
POST /v1/sessions/{id}/prompts
Send user prompt to session
GET /v1/sessions/{id}/queue
Editable next-turn queue for a session
PATCH /v1/sessions/{id}/queue
Mutate the editable next-turn queue
POST /v1/sessions/{id}/rewind
Undo a turn — restore files and drop the ask and everything after itRestores the project and session to the state immediately before the anchor user message’s turn started: all selected source effects are reversed through exact retained versions, and the anchor plus every later transcript row is removed. Host-executed and not a tool — the model must not be able to undo itself. The session must be idle; a running turn or an in-flight worker rejects with session_not_idle. Unrelated to the HITL /checkpoints approval bus. See docs/session.md § Session recovery.
POST /v1/sessions/{id}/rewind/preview
Preview the source changes and conflicts for a rewind
POST /v1/sessions/{id}/seen
Record that the person has read this sessionStamps seen_at with the host clock. Clients call this while the conversation is readable on screen in a focused window, not merely loaded. The stamp clears the attention view’s finished class and opens the session’s project for the recents order (last_opened_at). The host supplies the time, so no client can mark a turn read before it lands. Idempotent: the newest call wins, and a turn completing after the stamp makes the session unread again.
GET /v1/sessions/{id}/stream
Stream one message for a sessionEach SSE data frame is a JSON object. token carries message text; reset selects replacement instead of append, including when token is empty; tool_calls replaces the in-flight tool-call list when present; done terminates the stream. Live frames are full snapshots with reset true. Settled replay resets on its first chunk and appends later chunks; a persisted fallback is one reset snapshot.
GET /v1/sessions/{id}/workflow-runs
Workflow run history for a session (newest first)
POST /v1/sessions/{id}/workflow-runs
Start or replace a workflow run on a session
GET /v1/sessions/{id}/workflow-runs/active
Get the active workflow run for a session
POST /v1/sessions/{id}/workflows/compose
Validate and register a session-scoped workflow manifest
POST /v1/sessions/{id}/workflows/compose-from-template
Compose a session workflow from a bundled template
POST /v1/sessions/{id}/workflows/{workflow_id}/persist
Promote a session workflow to the project overlay workflows/ directory
GET /v1/settings/approvals
Get effective approval rules for scope
PATCH /v1/settings/approvals
Update approval rules for global or project scope
GET /v1/settings/file-summaries
Get the device-wide File summaries setting
PATCH /v1/settings/file-summaries
Enable or fully disable File summaries
GET /v1/settings/limits
Get effective session and budget limits for scope
PATCH /v1/settings/limits
Update session and budget limits for global or project scope
GET /v1/settings/model-policy
Get stored model policy overlay (empty slots inherit parent / Settings)
PATCH /v1/settings/model-policy
Apply model-policy slot assignmentsOmitted slots stay stored. A present key, including an empty ref, is the assignment.
GET /v1/settings/power
Get the device power preference and live assertion status
PATCH /v1/settings/power
Set whether active sessions prevent macOS idle sleep
GET /v1/settings/pricing
Get global cost pricing settings
PATCH /v1/settings/pricing
Update global cost pricing settings
POST /v1/settings/pricing/sources/{source_id}/refresh
Force refresh one pricing feed source
GET /v1/settings/project-trust
Get device-wide off switches for what projects may supply
PATCH /v1/settings/project-trust
Update device-wide off switches for what projects may supply
GET /v1/settings/review
Get effective edit review settings for scope
PATCH /v1/settings/review
Update edit review settings for global or project scope
GET /v1/settings/security-scanners
Get global Security scanners settings
PATCH /v1/settings/security-scanners
Update global Security scanners settings
GET /v1/settings/verify
Get effective verify (test command) settings for scope
PATCH /v1/settings/verify
Update project verify settings
POST /v1/settings/verify/dismiss
Dismiss (or un-dismiss) the project verify-command suggestion
GET /v1/settings/web-research
Web research mode and enablement preferences
PATCH /v1/settings/web-research
Update web research mode and enablement preferences
POST /v1/store/reset
Stage a clean current store for apply on next launchPreserves the refused store, its SQLite sidecars, rolling snapshot, identity-coupled project/session files, rebuildable store projections, and the first-run onboarding latch in a transaction-scoped .restore-recovery-. It then stages a freshly initialized current schema baseline and resets the onboarding latch for the next launch. Provider definitions, credentials, approvals, and other device configuration are not replaced. Available on a healthy server and in recovery mode. The desktop client requires explicit confirmation and restarts its managed backend immediately after staging.
GET /v1/web-research/index
Local web index snapshot and recent background-warming activity
GET /v1/web-research/providers
Web research direct status and catalog provider rows
PATCH /v1/web-research/providers/{provider_id}
Store non-secret provider config (endpoint, cx, …)
DELETE /v1/web-research/providers/{provider_id}/credential
Remove stored web research API key
PUT /v1/web-research/providers/{provider_id}/credential
Store web research API key (write-only)
POST /v1/web-research/providers/{provider_id}/test
Run a canned search against one web research provider
GET /v1/workers
List worker tasks for a project
POST /v1/workers/{id}/cancel
Cancel a worker task
GET /v1/workers/{id}/changes
List overlay-modified paths for a worker job
GET /v1/workflow-runs/{id}
Get workflow run by ID
POST /v1/workflow-runs/{id}/advance
Advance workflow run to the next phase
POST /v1/workflow-runs/{id}/cancel
Cancel a workflow run
POST /v1/workflow-runs/{id}/decisions/{phase_id}
Resolve a structured user decision for a workflow phase
POST /v1/workflow-runs/{id}/exit
Exit a reviewed active workflow run
POST /v1/workflow-runs/{id}/feedback/{phase_id}
Resolve open-ended user feedback for a workflow phase
POST /v1/workflow-runs/{id}/feedback/{phase_id}/secret
Resolve a protected secret response for a workflow ask
POST /v1/workflow-runs/{id}/pause
Pause a workflow run
GET /v1/workflow-runs/{id}/report
Download the grounded workflow report PDFAssembles a ReportInput on demand from durable records (grounded closeout message, every review phase’s verdict evidence, scan findings and their rules, the evidence ledger, and visual artifacts) and renders a deterministic PDF: the same records produce byte-identical bytes. Returns 404 report_not_found unless the workflow enables reports, the run is terminal, the host has recorded report delivery, and a grounded run-scoped closeout message exists for that run.
POST /v1/workflow-runs/{id}/resume
Resume a paused workflow run
POST /v1/workflow-runs/{id}/transitions/{transition_id}
Fire a named choice transition on the current phase (human actor)
GET /v1/workflow-templates
List bundled workflow compose templates
GET /v1/workflows
Catalog of bundled and project-overlay workflow manifests