Expand description
v2 /api/* surface (opencode 1.18.31 contract).
The 1.18.31 TUI and web UI are hybrid clients: their SyncProvider boots
on v1 paths (served elsewhere in this crate) while DataProvider and the
session view call the v2 SDK (client.v2.*), which resolves to /api/*.
Before this module every /api/* call fell through the web-UI catch-all
to SPA HTML, and JSON clients that parse it degrade or crash (e.g. the
session.diff → SidebarFiles.flatMap TypeError on an HTML string,
2026-10-08).
Shapes are taken from the vendored OpenAPI contract
(assets/openapi/1.18.31.json, served at /doc) and probed live from the
freeze. Envelope rules captured live:
{location, data}for location-scoped collections (agent/model/…),{data: …}for single resources,{data, cursor:{previous,next}}for paginated message lists.
The v2 session prompt route does NOT duplicate the prompt loop — it
normalizes the v2 body and calls the same ocserve_core::prompt::run_prompt
executor the v1 routes use (single owner of the state machine).
Functions§
- active_
sessions - GET /api/session/active —
{data:{}}(no per-request foreground drains are tracked on the v2 wire; ocserve’s own prompt locks are v1 status). - compact
- POST /api/session/{sessionID}/compact — NoContent (triggers summarize).
- console_
orgs - GET /experimental/console/orgs —
{orgs:[]}. - console_
switch - POST /experimental/console/switch — 400 payload (accountID required).
- control_
move_ session - POST /experimental/control-plane/move-session — 400 (sessionID required).
- create_
session - POST /api/session — create a session (v2). Shares the v1 creation path.
- credential_
remove - DELETE /api/credential/{credentialID} — NoContent.
- credential_
update - PATCH /api/credential/{credentialID} — NoContent (no credential store; ocserve has no OAuth credential backend — named divergence D-CREDENTIAL).
- fs_find
- GET /api/fs/find?query=&type=&limit= —
{location, data:[FileSystemEntry]}. - fs_list
- GET /api/fs/list?path= — direct children of
path(default “.”). - fs_read
- GET /api/fs/read/*path — raw bytes of one file (Uint8Array on the wire).
- get_
integration - GET /api/integration/{integrationID} — single integration.
- get_
message - GET /api/session/{sessionID}/message/{messageID} — single message.
- get_
provider - GET /api/provider/{providerID} —
{location, data:ProviderV2Info}. - get_
session - GET /api/session/{sessionID} —
{data:SessionV2Info}. - git_
init - POST /project/git/init — Project record (freeze shape). ocserve does not
initialize a git repo; it echoes the current project with
vcs:"git"when the worktree is a git checkout (probed 2026-10-09). - global_
upgrade - POST /global/upgrade — 400 BadRequest (missing target). ocserve does not self-upgrade; cited divergence.
- health
- GET /api/health —
{healthy:true}(schema: enum true). - integration_
attempt_ complete - POST /api/integration/attempt/{attemptID}/complete — 404 (no attempts).
- integration_
attempt_ delete - DELETE /api/integration/attempt/{attemptID} — 404 (no attempts).
- integration_
attempt_ get - GET /api/integration/attempt/{attemptID} — 404 (no attempts).
- integration_
connect_ key - POST /api/integration/{integrationID}/connect/key — NoContent (no credential backend; named divergence D-CREDENTIAL).
- integration_
connect_ oauth - POST /api/integration/{integrationID}/connect/oauth — 401 (no OAuth backend configured; freezes probe returns UnauthorizedError).
- integrations_
body - Pure integration-list projection (the
/api/integrationbody). - interrupt
- POST /api/session/{sessionID}/interrupt — NoContent (abort the task).
- list_
integrations - GET /api/integration —
{location, data:[IntegrationInfo]}. - list_
messages - GET /api/session/{sessionID}/message — paginated projected messages.
- list_
models - GET /api/model —
{location, data:[ModelV2Info]}, available providers only, non-deprecated models, sorted by release descending (upstream order). - list_
providers - GET /api/provider —
{location, data:[ProviderV2Info]}for available providers. - list_
references - GET /api/reference —
{location, data:[]}(no references configured). - list_
sessions - GET /api/session —
{data:[SessionV2Info], cursor:{previous,next}}. - list_
skills - GET /api/skill —
{location, data:[SkillV2Info]}. ocserve exposes the one built-in skill it ships (thecustomize-opencodecommand, source “skill”). - list_
skills_ raw - GET /skill — bare array (v1 shape; distinct from /api/skill’s envelope).
- location
- GET /api/location — bare LocationInfo (not enveloped).
- models_
body - Pure model-list projection (the
/api/modelbody). - patch_
project - PATCH /project/{projectID} — echo the project (freeze returns the project record; updates to name/icon are not persisted by ocserve).
- permission_
create - POST /api/session/{sessionID}/permission — create a permission request.
ocserve’s gate is a rendezvous for the RUNNING prompt; a standalone
create is acknowledged with the evaluated effect (freeze shape
{data:{id,effect}}). Full blocking semantics belong to the prompt path. - permission_
reply - POST /api/session/{sessionID}/permission/{requestID}/reply — reply to ask.
Body decode precedes the 404 (same Payload envelope as v1); the old
unwrap_or("reject")silently rejected a real ask on a malformed body. - permission_
request - GET /api/permission/request —
{location, data:[...]}pending asks. - permission_
saved - GET /api/permission/saved —
{data:[PermissionSavedInfo]}. - permission_
saved_ delete - DELETE /api/permission/saved/{id} — NoContent (best-effort grant removal).
- project_
copy_ create - POST /experimental/project/{projectID}/copy — 400 (strategy required).
- project_
copy_ generate_ name - POST /experimental/project/{projectID}/copy/generate-name — random name.
- project_
copy_ refresh - POST /experimental/project/{projectID}/copy/refresh — 204.
- project_
copy_ remove - DELETE /experimental/project/{projectID}/copy — 400 (body required).
- prompt
- POST /api/session/{sessionID}/prompt — spec
SessionInput.Admitted. Normalizes the v2 body{id?, prompt:{text,files,agents}, delivery, resume}into the v1 payload form and runs the SAMEocserve_core::prompt::run_promptthe v1/session/{id}/messageand/prompt_asyncroutes use. This route is synchronous (the v1session.promptsemantics), so the loop stays in one place; only the body shape differs. - providers_
body - Pure projections of the v2 metadata group over the payloads — the ONE
source of truth for both the request path and
rebuild_wire(the wire bytes are these values serialized with serde_json). Byte-parity pinned bytests/v2_body_parity.rs(including a planted-drift negative control). - pty_
connect_ token - POST /api/pty/{ptyID}/connect-token.
- pty_
create - POST /api/pty — create (location-wrapped info).
- pty_get
- GET /api/pty/{ptyID} — get.
- pty_
list - GET /api/pty —
{location, data:[Pty]}. - pty_
remove - DELETE /api/pty/{ptyID} — remove (true).
- pty_
update - PUT /api/pty/{ptyID} — update title/size.
- question_
reject - POST /api/session/{sessionID}/question/{requestID}/reject — reject.
- question_
reply - POST /api/session/{sessionID}/question/{requestID}/reply — reply.
- question_
request - GET /api/question/request —
{location, data:[...]}pending questions. - revert_
clear - POST /api/session/{sessionID}/revert/clear — restore the snapshot, clear the
marker, emit
session.next.revert.cleared; NoContent. - revert_
commit - POST /api/session/{sessionID}/revert/commit — finalize the revert: delete
the reverted range and clear the marker; emit
session.next.revert.committed. - revert_
stage - POST /api/session/{sessionID}/revert/stage —
{data:RevertState}. Runs the real revert engine (restore files, compute the range diff, set the marker) and emitssession.next.revert.stagedso the web UI resyncs. - session_
background - POST /experimental/session/{sessionID}/background — false.
- session_
context - GET /api/session/{sessionID}/context —
{data:[SessionMessage.Message]}. Projection over the stored message stream (thinner than upstream’s full event-sourcing; documented divergence). - session_
diff - GET /session/{id}/diff — with
?messageID=, the message’s storedsummary.diffs(upstreamSessionSummary.diff); without it, the session convenience path returns[]. Crucially this is JSON, not SPA HTML: the TUI’sSidebarFilesflatMaps the result and an HTML string crashes it. - session_
event - GET /api/session/{sessionID}/event — durable SSE for one session. ocserve has no per-session event stream distinct from the global bus; alias the global SSE (frames carry sessionID, clients filter). Semantics are a subset; wire shape (text/event-stream) matches.
- session_
history - GET /api/session/{sessionID}/history —
{data:[DurableEvent], hasMore}. ocserve serves its own event log; the wire shape matches, semantics are a subset (documented). - session_
permission - GET /api/session/{sessionID}/permission —
{data:[]}(per-session asks). - session_
permission_ one - GET /api/session/{sessionID}/permission/{requestID} — one ask.
- session_
question - GET /api/session/{sessionID}/question —
{data:[]}(per-session questions). - session_
share - POST /session/{id}/share — freeze returns the Session record (share is a
local-only marker; ocserve has no remote share backend, so the record is
returned with
shareunset — a named divergenceD-SHARE-NOBACKEND). - session_
unshare - DELETE /session/{id}/share — freeze returns the (unshared) Session record.
- switch_
agent - POST /api/session/{sessionID}/agent — switch agent (NoContent).
- switch_
model - POST /api/session/{sessionID}/model — switch model (NoContent).
- sync_
history - POST /sync/history —
{data:[]}. - sync_
replay - POST /sync/replay —
true. - sync_
start - POST /sync/start —
true(freeze: workspace sync started). - sync_
steal - POST /sync/steal —
true. - vcs_
apply - POST /vcs/apply — apply a patch to the session directory’s git worktree
(upstream
project/vcs.ts:399):git apply→{applied:true}; not a git worktree →reason:"non-git"; patch rejected →reason:"not-clean". missingpatch→ 400 payload. The route DOES mutate the worktree. - wait
- POST /api/session/{sessionID}/wait — freeze returns ServiceUnavailable (“not available yet”); mirror honestly (no long-poll engine).
- workspace_
adapter - GET /experimental/workspace/adapter —
[{type,name,description}]. - workspace_
create - POST /experimental/workspace — create a workspace. Freeze requires
typeand rejects unknown adapters. ocserve registers the built-in adapters and mirrors the error envelope for others. - workspace_
delete - DELETE /experimental/workspace/{id} — 204.
- workspace_
list - GET /experimental/workspace —
[]. - workspace_
status - GET /experimental/workspace/status —
[]. - workspace_
sync_ list - POST /experimental/workspace/sync-list — 204.
- workspace_
warp - POST /experimental/workspace/warp — 400 payload (id required).
- worktree_
create - POST /experimental/worktree — WorktreeNotGitError on non-git.
- worktree_
list - GET /experimental/worktree —
[]. - worktree_
remove - DELETE /experimental/worktree — 400 WorktreeRemoveInput required.
- worktree_
reset - POST /experimental/worktree/reset — 400 payload (directory required).