API Reference

HTTP surface for the Rust API. Default local origin is `http://localhost:8080`. Hosted deploys use the public HTTPS API origin.

Paths are /v1/*. Treat undocumented fields as unstable. Examples assume loopback. On a hosted API, add Authorization: Bearer $DOSO_TOKEN.

Request examples default to curl. TypeScript and Rust tabs call the same route. Responses name the models they return.

Overview#

Base URL (local)http://localhost:8080
Base URL (hosted)your public HTTPS API origin
Authlocal principal on loopback, Better Auth JWT, or dsa_… service account
Body limit64 MiB
CORSCORS_ALLOWED_ORIGINS (local web and Tauri by default)

Ask has two callers that share retrieval:

You wantCall
Doso to draft an answerPOST /v1/chat/completions or POST /v1/search
The retrieve pack onlyPOST /v1/ask/evidence

effort on chat completions:

ValueBehavior
fast (default)Seed → 1-hop facts → library passages → one draft
expertFull ask graph: rewrite, live research, skeptics. Product UI uses this via POST /v1/ui-chat

effort: "heavy" is accepted as expert. If effort is empty, the model string can select it: doso, doso-ask, ask, doso-full, doso-expert, and expert run the expert graph.

See Ask, chat & live sources.

Models#

These objects are the durable pieces of Doso: graph memory, Ask citations, workflow structure, and the connections a DAG may call. Endpoints return them by name so the field lists live once. Each model below says why the shape exists; the concept pages explain the machinery behind it.

Entity#

A person, organization, artifact, or other node in the knowledge graph. After ingest, resolve collapses surface forms onto one canonical_name plus aliases. Ask, neighbors, and the wiki all read this shape because the graph — not a document index — is the memory. See the architecture and entity wiki.

FieldTypeNotes
iduuidStable id
canonical_namestringDisplay name
typestringPERSON, ORGANIZATION, ARTIFACT, CONCEPT, …
descriptionstringGrounded one-liner
aliasesstring[]Other surface forms
mention_countnumberHow often it appears
propsobjectExtra typed properties
updated_atdatetimeISO-8601
1{2  "id": "8c1e0000-0000-0000-0000-000000000001",3  "canonical_name": "Atlas",4  "type": "ARTIFACT",5  "description": "Internal prototype",6  "aliases": ["Atlas proto"],7  "mention_count": 4,8  "props": {},9  "updated_at": "2026-08-16T12:00:00Z"10}

Document#

A source in the Library: the bytes (or extracted text) you uploaded, synced, or fetched live. Ingest reads this record; every Triple that comes out points back through source_doc_id. It is provenance, not a second memory. See the knowledge pipeline and Ask / Library.

FieldTypeNotes
iduuid
titlestring
mimestringtext/plain, application/pdf, …
textstringExtracted body
uristring?Stable connector URI
checksumstring?
metaobjectIncludes source, ingest_status
created_atdatetime
1{2  "id": "a91c0000-0000-0000-0000-000000000001",3  "title": "Atlas notes",4  "mime": "text/plain",5  "text": "Maya owns Atlas. Deadline is Friday.",6  "uri": "note://atlas",7  "checksum": "sha256:…",8  "meta": {"source": "upload", "ingest_status": "succeeded"},9  "created_at": "2026-08-16T12:00:00Z"10}

DocumentHit#

A keyword-search projection of a Document. Same id / title / uri, plus a match-centered excerpt, so a connector or UI can find a passage without loading the full text. It is not stored separately and is not the Ask retrieve pack — Ask’s document arm uses embeddings first and falls back to this lexical hit when you already know a phrase. See Ask / Library.

FieldTypeNotes
iduuidDocument id
titlestring
uristring?
excerptstring~900 characters around the match

Conversation#

A persisted chat thread. Completions create or reuse one so History can resume the same plan, citations, and remember path. The thread is storage for turns, not a second knowledge graph. See Ask, chat & live sources.

FieldType
iduuid
titlestring
created_atdatetime
updated_atdatetime
1{2  "id": "3f2a0000-0000-0000-0000-000000000001",3  "title": "Who owns Atlas?",4  "created_at": "2026-08-16T12:00:00Z",5  "updated_at": "2026-08-16T12:01:00Z"6}

ChatMessage#

One turn on a Conversation. Assistant rows carry meta with citations, run id, and workflow so the UI can replay evidence without parsing the prose. User rows are what remember may ingest into the graph. The message is history, not a fact — only remember writes memory. See Ask, chat & live sources.

FieldTypeNotes
iduuid
conversation_iduuid
rolestringuser or assistant
contentstring
metaobjectCitations, run id, workflow
created_atdatetime

Citation#

A cited fact in an Ask answer, written as prose (Maya owns Atlas) rather than a triple dump. Ask attaches these beside the answer so fluency cannot hide missing evidence. See Ask, chat & live sources.

FieldTypeNotes
srcstringSubject name
predicatestring
dststringObject name
source_docstringOptional document id or URI
1{"src": "Maya", "predicate": "owns", "dst": "Atlas", "source_doc": "a91c…"}

Triple#

A typed edge between two Entity nodes, with status (candidate, verified, rejected). This is the write-side fact: ingest assembles candidates, review promotes them, Ask reads verified by default. Predicates are open vocabulary. See predicates & ontology and the knowledge pipeline.

FieldTypeNotes
iduuid
src_iduuid
dst_iduuid
predicatestring
statementstringDisplay prose
source_doc_iduuid?
statusstringcandidate, verified, rejected
confidencenumber?
extracted_atdatetime
src_namestring
dst_namestring
src_typestring
dst_typestring

Subgraph#

A bounded neighborhood: the Entity nodes and Triple edges around a seed, plus a serialized prose form for the model. Ask and graph tools take this slice instead of walking the whole store. See architecture.

FieldType
nodesEntity[]
edgesTriple[]
serialized_triplesstring

EntityWiki#

A view-time page for one Entity: description, grouped one-hop relations, source Documents, and an optional cached summary. It is compiled from verified triples; you do not author wiki markdown. See entity wiki & statements.

FieldType
entityEntity
aboutstring
relationsobject[]
sourcesDocument[]
summarystring
compactedboolean
needs_compactionboolean
updated_atdatetime

OwnerStatus#

Whether the first-person owner node exists, has a named fact, and already has Memories. Chat onboarding and “what’s my name?” seed this entity. Identity is graph-only — there is no OWNER_NAME env. See chat memory.

FieldTypeNotes
existsboolean
namedboolean
has_thoughtsbooleanDurable self-facts exist
canonical_namestringEmpty until named
entity_iduuid?

Thought#

One fact in the Memories projection: a verified owner-adjacent Triple rewritten in first person. Forget supersedes the underlying edge; it does not delete a separate store. See Memory Groom.

FieldType
iduuid
src_iduuid
dst_iduuid
predicatestring
src_namestring
dst_namestring
factstring
subjectstring
extracted_atdatetime
source_doc_iduuid?

MemoryCard#

One row in the Memories list: a subject with its Thoughts, a structural title, and optional compacted summary. The list is groomed for reading; the graph remains the source of truth. See Memory Groom.

FieldType
idstring
subjectstring
titlestring
summarystring
factsThought[]
updated_atdatetime
compactedboolean
needs_compactionboolean

Workflow#

A reusable DAG: skill nodes, edges, and contracts that say how a job should run. The definition is the recipe; a WorkflowRun is one attempt. Capability templates (conn_cap_*) stay generic; promoted library rows are the ones you named and edited. See Workflows, the workflow graph, and harness principles.

FieldTypeNotes
idstringCatalog or wf_* library id
titlestring
descriptionstring
versionstring
sourcestringcatalog, chat, promoted, …
dagobject{ nodes, edges }

WorkflowRun#

One execution episode of a Workflow: status, cost, result, and live trace. A run is an inspectable attempt; the definition is the reusable structure. Promote lifts a succeeded run into a library def without changing the capability template it reused. See Workflows.

FieldTypeNotes
iduuid
kindstring
workflow_idstring
workflow_versionstring
titlestring
statusstringrunning, succeeded, failed, …
capsobject
costobject
resultobject
liveobject
errorstring?
created_atdatetime
finished_atdatetime?
1{2  "id": "r1000000-0000-0000-0000-000000000001",3  "kind": "workflow",4  "workflow_id": "wf_venues",5  "workflow_version": "3",6  "title": "Prepare three venue options",7  "status": "succeeded",8  "caps": {},9  "cost": {},10  "result": {},11  "live": {},12  "created_at": "2026-08-16T12:00:00Z",13  "finished_at": "2026-08-16T12:01:12Z"14}

Skill#

A contract a DAG node may bind: prompt, tool, code, or builtin. The workflow picks which skills the job needs; the harness only knows how to invoke the kind. Saving a skill does not run it. See Workflows and harness principles.

FieldTypeNotes
idstringStable slug
titlestring
kindstringprompt, tool, code, builtin
impl_refstringPrompt text, tool slug, or implementation ref

Connection#

A configured provider binding (hosted or local runtime) plus the capabilities discovered on it. Successful tool results persist as a provenance Document before they return; the connection itself is not memory. See Workflows and architecture.

FieldTypeNotes
iduuid
provider_idstringe.g. composio
display_namestring
runtime_kindstringhosted or local daemon
statusstringdraft, ready, …

Authentication#

  • GET /healthz is public.
  • POST /v1/runtimes/pair is public but consumes a single-use pairing code (ten minutes). Later daemon routes need the issued runtime token.
  • Every other /v1/* route goes through authenticate/authorize. Install-wide API keys and x-api-key are not supported.
  • DOSO_AUTH_MODE=jwks verifies JWTs via DOSO_JWKS_URL / issuer / audience. This is the product default for compose and hosted instances.
  • DOSO_AUTH_MODE=local trusts the loopback principal and refuses non-loopback binds. Dev/test and the Desktop embedded fallback only.
  • Service accounts use Authorization: Bearer dsa_…. They have no access until you grant policies.
  • Failed validation is usually 400 with a short plain-text body. Missing rows are 404. Unexpected failures are 500.

GET /healthz#

Public liveness for load balancers and deploy probes. Returns { status: "ok" } with no auth and no store read.

Local loopback (dev/test)#

On 127.0.0.1 / ::1 with DOSO_AUTH_MODE=local, no Authorization header is required. Product instances use JWKS instead. See GET /v1/owner.

Hosted JWKS#

Browser sessions obtain a Better Auth JWT from /api/auth/token on the web origin. Automation uses a service-account token.

1curl -s http://localhost:8080/healthz

Response

1{"status":"ok"}
1curl -s "$API/v1/ask/evidence" \2  -H "authorization: Bearer $DOSO_TOKEN" \3  -H 'content-type: application/json' \4  -d '{"q":"Who owns the Atlas prototype?"}'

Ask#

Grounded answers from the knowledge graph and Library. Chat completions persist a conversation. Search and evidence do not.

POST /v1/chat/completions#

Drafts a grounded answer over the graph and Library, persists the thread, and wraps the result as an OpenAI Chat Completions object so existing clients can call Doso without a custom protocol. Not the Responses API. The last non-empty user message is the question; earlier user / assistant turns are history.

FieldTypeRequiredNotes
messagesarrayyesLast user turn is the question
effortstringnofast (default) or expert
modelstringnoUsed only when effort is empty
streamboolnotrue returns text/event-stream
conversation_iduuidnoReuse a Conversation; omitted creates one

Stream with stream: true. Answer tokens are choices[0].delta.content. Thinking is choices[0].delta.reasoning_content. Ends with finish_reason=stop and data: [DONE]. No user message → 400.

1curl -s http://localhost:8080/v1/chat/completions \2  -H 'content-type: application/json' \3  -d '{4    "messages": [5      {"role":"user","content":"What is Atlas?"},6      {"role":"assistant","content":"A prototype."},7      {"role":"user","content":"Who owns it?"}8    ],9    "effort":"fast"10  }'
1{2  "id": "chatcmpl-…",3  "object": "chat.completion",4  "created": 1710000000,5  "model": "doso",6  "choices": [7    {8      "index": 0,9      "message": {"role": "assistant", "content": "Maya owns Atlas."},10      "finish_reason": "stop"11    }12  ],13  "conversation_id": "3f2a0000-0000-0000-0000-000000000001",14  "doso": {15    "answer": "Maya owns Atlas.",16    "citations": [17      {"src": "Maya", "predicate": "owns", "dst": "Atlas", "source_doc": "a91c…"}18    ],19    "confidence": "medium"20  }21}
1curl -sN http://localhost:8080/v1/chat/completions \2  -H 'content-type: application/json' \3  -d '{4    "messages":[{"role":"user","content":"Who owns Atlas?"}],5    "effort":"fast",6    "stream":true7  }'

Response

1data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{"role":"assistant","content":"Maya"},"finish_reason":null}],"conversation_id":"…"}2 3data: {"id":"chatcmpl-…","object":"chat.completion.chunk","choices":[{"index":0,"delta":{},"finish_reason":"stop"}],"conversation_id":"…"}4 5data: [DONE]

POST /v1/search#

One-shot retrieve plus a fast draft for callers that want an answer without creating a Conversation. Same retrieve path as effort=fast on completions. Also accepted at POST /v1/content/documents/search.

FieldTypeRequiredNotes
qstringyesAt least 3 characters
1curl -s http://localhost:8080/v1/search \2  -H 'content-type: application/json' \3  -d '{"q":"Atlas prototype deadline"}'

Response

Citation
1{2  "answer": "The Atlas prototype is due Friday.",3  "content": "The Atlas prototype is due Friday.",4  "citations": [5    {"src": "Atlas", "predicate": "has_deadline", "dst": "Friday"}6  ],7  "confidence": "medium",8  "orchestrator": {}9}

POST /v1/ask/evidence#

Returns the retrieve pack — entities, facts, and passages — so a client or another model can write the answer. No LLM call. q must be at least three characters.

1curl -s http://localhost:8080/v1/ask/evidence \2  -H 'content-type: application/json' \3  -d '{"q":"Who owns the Atlas prototype?"}'
1{2  "question": "Who owns the Atlas prototype?",3  "content": "## Entities\n- Atlas [ARTIFACT] id=8c1e…\n\n## Facts\nMaya owns Atlas\n",4  "entities": [5    {6      "id": "8c1e0000-0000-0000-0000-000000000001",7      "canonical_name": "Atlas",8      "type": "ARTIFACT",9      "description": "Internal prototype",10      "aliases": ["Atlas proto"],11      "mention_count": 4,12      "props": {},13      "updated_at": "2026-08-16T12:00:00Z"14    }15  ],16  "facts": [17    {"src": "Maya", "predicate": "owns", "dst": "Atlas"}18  ]19}

POST /v1/ui-chat#

Streams the expert ask graph as AI SDK UI-message events (reasoning-delta, then text-delta) for the web and Tauri chat surfaces. Persists ChatMessage rows on a Conversation.

FieldTypeNotes
messagesarrayUI messages with parts[]
conversation_iduuidOptional Conversation
workflow_idstringWhen set, every turn iterates this workflow
current_workflowobjectOptional in-progress definition
1curl -sN http://localhost:8080/v1/ui-chat \2  -H 'content-type: application/json' \3  -d '{4    "messages":[5      {"role":"user","parts":[{"type":"text","text":"Who owns Atlas?"}]}6    ]7  }'

POST /v1/chat/remember#

Ingests a user turn into the graph as first-person facts without running Ask. New rows later appear as Thought / MemoryCard.

1curl -s http://localhost:8080/v1/chat/remember \2  -H 'content-type: application/json' \3  -d '{"conversation_id":"3f2a0000-0000-0000-0000-000000000001","content":"I work at Northstar Studio."}'

GET /v1/owner#

Returns whether the first-person owner node exists, is named, and already has Memories. Chat onboarding and “what’s my name?” seed this OwnerStatus; there is no OWNER_NAME env.

1curl -s http://localhost:8080/v1/owner

Response

OwnerStatus
1{2  "exists": true,3  "named": true,4  "has_thoughts": true,5  "canonical_name": "Andrew",6  "entity_id": "0e000000-0000-0000-0000-000000000001"7}

Library#

Documents are provenance. Ingest extracts candidate Entity and Triple rows into the graph.

GET /v1/content/documents#

Pages Library documents so a client can browse provenance and ingest status without searching.

QueryNotes
limitDefault 100
ingest_statusOptional filter, e.g. failed
1curl -s 'http://localhost:8080/v1/content/documents?limit=20'

Response

Document
1{2  "documents": [3    {4      "id": "a91c0000-0000-0000-0000-000000000001",5      "title": "Atlas notes",6      "mime": "text/plain",7      "text": "Maya owns Atlas. Deadline is Friday.",8      "uri": "note://atlas",9      "meta": {"source": "upload", "ingest_status": "succeeded"},10      "created_at": "2026-08-16T12:00:00Z"11    }12  ]13}

POST /v1/content/documents#

Stores a new Library Document from a JSON body or multipart file and by default queues extract → resolve → assemble. ?ingest=0 stores the bytes only.

1curl -s http://localhost:8080/v1/content/documents \2  -H 'content-type: application/json' \3  -d '{4    "title":"Atlas notes",5    "text":"Maya owns Atlas. Deadline is Friday.",6    "uri":"note://atlas",7    "mime":"text/plain",8    "meta":{"source":"upload"}9  }'

Response

Document
1{2  "id": "a91c0000-0000-0000-0000-000000000001",3  "title": "Atlas notes",4  "mime": "text/plain",5  "text": "Maya owns Atlas. Deadline is Friday.",6  "uri": "note://atlas",7  "meta": {"source": "upload", "ingest_status": "queued"},8  "created_at": "2026-08-16T12:00:00Z"9}

GET /v1/content/documents/by-uri#

Looks up a Library document by its connector URI so a sync job can skip a re-upload when the same source is already stored.

1curl -sG http://localhost:8080/v1/content/documents/by-uri \2  --data-urlencode 'uri=note://atlas'

Response

Document
1{2  "exists": true,3  "has_triples": true,4  "document": {5    "id": "a91c0000-0000-0000-0000-000000000001",6    "title": "Atlas notes",7    "uri": "note://atlas"8  }9}

GET /v1/content/documents/{id}#

Returns one Library Document, including extracted text and ingest metadata.

1curl -s http://localhost:8080/v1/content/documents/a91c0000-0000-0000-0000-000000000001

Response

Document
1{2  "id": "a91c0000-0000-0000-0000-000000000001",3  "title": "Atlas notes",4  "mime": "text/plain",5  "text": "Maya owns Atlas. Deadline is Friday.",6  "uri": "note://atlas",7  "checksum": "sha256:…",8  "meta": {"source": "upload", "ingest_status": "succeeded"},9  "created_at": "2026-08-16T12:00:00Z"10}

DELETE /v1/content/documents/{id}#

Deletes the Library document and cancels an in-flight ingest. Graph triples that cited it remain until review removes them.

1curl -s -X DELETE http://localhost:8080/v1/content/documents/a91c0000-0000-0000-0000-000000000001

POST /v1/content/documents/{id}/ingest#

Starts or restarts extract → resolve → assemble on an already-stored document. Use this after ?ingest=0 or to retry a failed run.

1curl -s -X POST http://localhost:8080/v1/content/documents/a91c0000-0000-0000-0000-000000000001/ingest

Response

1{"id":"a91c0000-0000-0000-0000-000000000001","ingest_status":"running"}

Conversations#

GET /v1/conversations#

Lists persisted chat threads for History, newest first. Threads are storage for turns, not a second knowledge graph.

1curl -s 'http://localhost:8080/v1/conversations?limit=20'

Response

Conversation
1{2  "conversations": [3    {4      "id": "3f2a0000-0000-0000-0000-000000000001",5      "title": "Who owns Atlas?",6      "created_at": "2026-08-16T12:00:00Z",7      "updated_at": "2026-08-16T12:01:00Z"8    }9  ]10}

POST /v1/conversations#

Creates an empty Conversation so a client can pin a conversation_id before the first completion.

1curl -s http://localhost:8080/v1/conversations \2  -H 'content-type: application/json' \3  -d '{"title":"Atlas follow-up"}'

Response

Conversation
1{2  "id": "3f2a0000-0000-0000-0000-000000000001",3  "title": "Atlas follow-up",4  "created_at": "2026-08-16T12:00:00Z",5  "updated_at": "2026-08-16T12:00:00Z"6}

GET /v1/conversations/{id}#

Returns the thread plus its ChatMessage rows so History can resume plan, citations, and the remember path.

1curl -s http://localhost:8080/v1/conversations/3f2a0000-0000-0000-0000-000000000001
1{2  "conversation": {3    "id": "3f2a0000-0000-0000-0000-000000000001",4    "title": "Atlas follow-up"5  },6  "messages": [7    {8      "id": "m1",9      "conversation_id": "3f2a0000-0000-0000-0000-000000000001",10      "role": "user",11      "content": "Who owns Atlas?",12      "meta": {},13      "created_at": "2026-08-16T12:00:00Z"14    }15  ]16}

DELETE /v1/conversations/{id}#

Deletes the thread and its messages. Facts already remembered into the graph stay.

1curl -s -X DELETE http://localhost:8080/v1/conversations/3f2a0000-0000-0000-0000-000000000001

POST /v1/conversations/{id}/messages#

Appends a turn without running Ask — used to record user or assistant text the client already has.

1curl -s http://localhost:8080/v1/conversations/3f2a0000-0000-0000-0000-000000000001/messages \2  -H 'content-type: application/json' \3  -d '{"role":"user","content":"Also list the deadline."}'

Response

ChatMessage
1{2  "id": "m2000000-0000-0000-0000-000000000001",3  "conversation_id": "3f2a0000-0000-0000-0000-000000000001",4  "role": "user",5  "content": "Also list the deadline.",6  "meta": {},7  "created_at": "2026-08-16T12:02:00Z"8}

POST /v1/conversations/{id}/workflow-cards/complete#

Marks a workflow card in the thread as done so the UI can hide the approval or progress chip.

1curl -s http://localhost:8080/v1/conversations/3f2a0000-0000-0000-0000-000000000001/workflow-cards/complete \2  -H 'content-type: application/json' \3  -d '{"card_id":"card_…"}'

Graph#

The graph is the knowledge source of truth. The wiki is a compiled read model.

GET /v1/graph/entities#

Searches resolved graph nodes by name and optional type. Structural noise nodes are omitted.

QueryNotes
qSearch string
typeOptional type filter
1curl -sG http://localhost:8080/v1/graph/entities \2  --data-urlencode 'q=Atlas' \3  --data-urlencode 'type=ARTIFACT'

Response

Entity
1{2  "entities": [3    {4      "id": "8c1e0000-0000-0000-0000-000000000001",5      "canonical_name": "Atlas",6      "type": "ARTIFACT",7      "description": "Internal prototype",8      "aliases": ["Atlas proto"],9      "mention_count": 4,10      "props": {},11      "updated_at": "2026-08-16T12:00:00Z"12    }13  ]14}

POST /v1/graph/entities#

Creates or merges an Entity by name. type defaults to CONCEPT. Ingest uses this; callers can also seed a node before attaching triples.

1curl -s http://localhost:8080/v1/graph/entities \2  -H 'content-type: application/json' \3  -d '{"name":"Atlas","type":"ARTIFACT","description":"Internal prototype","aliases":["Atlas proto"]}'

Response

Entity
1{2  "id": "8c1e0000-0000-0000-0000-000000000001",3  "canonical_name": "Atlas",4  "type": "ARTIFACT",5  "description": "Internal prototype",6  "aliases": ["Atlas proto"],7  "mention_count": 4,8  "props": {},9  "updated_at": "2026-08-16T12:00:00Z"10}

GET /v1/graph/entities/{id}#

Returns one resolved Entity: canonical name, aliases, type, and props.

1curl -s http://localhost:8080/v1/graph/entities/8c1e0000-0000-0000-0000-000000000001

Response

Entity
1{2  "id": "8c1e0000-0000-0000-0000-000000000001",3  "canonical_name": "Atlas",4  "type": "ARTIFACT",5  "description": "Internal prototype",6  "aliases": ["Atlas proto"],7  "mention_count": 4,8  "props": {},9  "updated_at": "2026-08-16T12:00:00Z"10}

GET /v1/graph/entities/{id}/neighbors#

Walks hops from the seed and returns the bounded Subgraph Ask uses for 1-hop facts.

QueryNotes
hopsDefault 0
statusOptional triple status
1curl -s 'http://localhost:8080/v1/graph/entities/8c1e0000-0000-0000-0000-000000000001/neighbors?hops=1&status=verified'
1{2  "nodes": [3    {4      "id": "8c1e0000-0000-0000-0000-000000000001",5      "canonical_name": "Atlas",6      "type": "ARTIFACT",7      "description": "Internal prototype",8      "aliases": ["Atlas proto"],9      "mention_count": 4,10      "props": {},11      "updated_at": "2026-08-16T12:00:00Z"12    }13  ],14  "edges": [15    {16      "id": "t1000000-0000-0000-0000-000000000001",17      "src_id": "0e000000-0000-0000-0000-000000000002",18      "dst_id": "8c1e0000-0000-0000-0000-000000000001",19      "predicate": "owns",20      "src_name": "Maya",21      "dst_name": "Atlas",22      "status": "verified"23    }24  ],25  "serialized_triples": "Maya owns Atlas"26}

GET /v1/graph/entities/{id}/wiki#

Compiles the view-time EntityWiki for one node from verified triples. You do not author this markdown. See entity wiki.

1curl -s http://localhost:8080/v1/graph/entities/8c1e0000-0000-0000-0000-000000000001/wiki

Response

EntityWiki
1{2  "entity": {3    "id": "8c1e0000-0000-0000-0000-000000000001",4    "canonical_name": "Atlas",5    "type": "ARTIFACT"6  },7  "about": "Internal prototype",8  "relations": [9    {"predicate": "owns", "direction": "in", "entity": {"canonical_name": "Maya"}}10  ],11  "sources": [12    {"id": "a91c0000-0000-0000-0000-000000000001", "title": "Atlas notes"}13  ],14  "summary": "",15  "compacted": false16}

POST /v1/graph/query#

Same neighborhood walk as neighbors, but seeded by name and type instead of id. Returns a Subgraph.

1curl -s http://localhost:8080/v1/graph/query \2  -H 'content-type: application/json' \3  -d '{"seed":{"name":"Atlas","type":"ARTIFACT"},"hops":1,"status":"verified"}'

Response

Subgraph
1{2  "nodes": [3    {4      "id": "8c1e0000-0000-0000-0000-000000000001",5      "canonical_name": "Atlas",6      "type": "ARTIFACT"7    }8  ],9  "edges": [10    {11      "id": "t1000000-0000-0000-0000-000000000001",12      "predicate": "owns",13      "src_name": "Maya",14      "dst_name": "Atlas",15      "status": "verified"16    }17  ],18  "serialized_triples": "Maya owns Atlas"19}

GET /v1/graph/triples#

Lists typed edges, typically filtered by status, so a review UI can page candidates versus verified facts.

1curl -s 'http://localhost:8080/v1/graph/triples?status=candidate'

Response

Triple
1{2  "triples": [3    {4      "id": "t1000000-0000-0000-0000-000000000001",5      "src_id": "0e000000-0000-0000-0000-000000000002",6      "dst_id": "8c1e0000-0000-0000-0000-000000000001",7      "predicate": "owns",8      "src_name": "Maya",9      "dst_name": "Atlas",10      "status": "candidate"11    }12  ]13}

POST /v1/graph/triples/{id}/promote#

Marks a candidate Triple verified. Ask reads verified edges by default.

1curl -s -X POST http://localhost:8080/v1/graph/triples/t1000000-0000-0000-0000-000000000001/promote

POST /v1/graph/triples/{id}/reject#

Marks a candidate Triple rejected so it will not be cited.

1curl -s -X POST http://localhost:8080/v1/graph/triples/t1000000-0000-0000-0000-000000000001/reject

GET /v1/graph/documents/{id}/triples#

Lists every Triple extracted from one source document — the review queue for that file.

1curl -s 'http://localhost:8080/v1/graph/documents/a91c0000-0000-0000-0000-000000000001/triples?status=candidate'

Response

Triple
1{2  "triples": [3    {4      "id": "t1000000-0000-0000-0000-000000000001",5      "predicate": "owns",6      "src_name": "Maya",7      "dst_name": "Atlas",8      "status": "candidate",9      "source_doc_id": "a91c0000-0000-0000-0000-000000000001"10    }11  ]12}

POST /v1/graph/documents/{id}/promote#

Promotes every candidate triple extracted from that document in one call.

1curl -s -X POST http://localhost:8080/v1/graph/documents/a91c0000-0000-0000-0000-000000000001/promote

GET /v1/ontology#

Returns the extraction vocabulary (types, predicates) the pipeline uses. Open predicates stay allowed; this is guidance, not a closed schema. See predicates & ontology.

1curl -s http://localhost:8080/v1/ontology

Response

1{2  "types": ["PERSON", "ORGANIZATION", "ARTIFACT", "CONCEPT"],3  "predicates": ["works_at", "owns", "named"]4}

PUT /v1/ontology#

Replaces the stored extraction vocabulary. This is the allow/deny list the pipeline consults, not a lock on which predicates may exist.

1curl -s -X PUT http://localhost:8080/v1/ontology \2  -H 'content-type: application/json' \3  -d '{"types":["PERSON","ORGANIZATION"],"predicates":["works_at","owns"]}'

Workflows#

GET /v1/workflows#

Lists runnable catalog entries plus saved library definitions — what the hub shows under Workflows.

1curl -s http://localhost:8080/v1/workflows

Response

Workflow
1{2  "workflows": [3    {4      "id": "ask",5      "title": "Ask",6      "description": "Grounded question over the graph",7      "version": "1"8    }9  ]10}

POST /v1/workflows/run#

Starts one execution of a saved or catalog Workflow against an objective. Returns a run id immediately; poll GET /v1/runs/{id} for status. approve_consequential is the only way this request authorizes side effects.

FieldTypeNotes
workflow_idstringSaved or catalog id
objectivestring
hintsobjectOptional
approve_consequentialboolOnly when this request authorizes side effects
1curl -s http://localhost:8080/v1/workflows/run \2  -H 'content-type: application/json' \3  -d '{"workflow_id":"wf_venues","objective":"Prepare three venue options under $8,000","approve_consequential":false}'

Response

WorkflowRun
1{2  "run_id": "r1000000-0000-0000-0000-000000000001",3  "workflow_id": "wf_venues",4  "workflow_version": "3",5  "result": {}6}

GET /v1/workflows/defs#

Lists only persisted library Workflow definitions, not the builtin catalog.

1curl -s http://localhost:8080/v1/workflows/defs

Response

Workflow
1{2  "workflows": [3    {4      "id": "wf_venues",5      "title": "Venue options",6      "version": "1",7      "dag": {"nodes": [], "edges": []}8    }9  ]10}

POST /v1/workflows/defs#

Saves or updates a library Workflow (id, DAG, version). This is the editor’s persist path; it does not start a run.

1curl -s http://localhost:8080/v1/workflows/defs \2  -H 'content-type: application/json' \3  -d '{"id":"wf_venues","title":"Venue options","version":"1","dag":{"nodes":[],"edges":[]}}'

GET /v1/workflows/defs/{id}#

Returns one saved library Workflow, including its DAG, so the editor can reload the graph you last persisted.

1curl -s http://localhost:8080/v1/workflows/defs/wf_venues

Response

Workflow
1{2  "id": "wf_venues",3  "title": "Venue options",4  "version": "1",5  "dag": {"nodes": [], "edges": []}6}

DELETE /v1/workflows/defs/{id}#

Removes a saved library definition. Past WorkflowRun rows stay under /v1/runs.

1curl -s -X DELETE http://localhost:8080/v1/workflows/defs/wf_venues

POST /v1/workflows/compose#

Asks the composer to draft a DAG from an objective. The response is a definition; it is saved or run only when those flags are set.

1curl -s http://localhost:8080/v1/workflows/compose \2  -H 'content-type: application/json' \3  -d '{"objective":"Compare three venues and stop before booking"}'

GET /v1/skills#

Lists Skill contracts a DAG node may bind. ?all=1 includes hidden and system skills.

1curl -s 'http://localhost:8080/v1/skills?all=1'

Response

Skill
1{2  "skills": [3    {"id": "sk_brief", "title": "Write a brief", "kind": "prompt"}4  ]5}

POST /v1/skills#

Creates or updates a Skill contract. Workflows reference skills by id; this call does not run one.

1curl -s http://localhost:8080/v1/skills \2  -H 'content-type: application/json' \3  -d '{"id":"sk_brief","title":"Write a brief","kind":"prompt","impl_ref":"…"}'

GET /v1/skills/{id}#

Returns one Skill contract — title, kind, and impl_ref — so a client can edit the node without listing the whole catalog.

1curl -s http://localhost:8080/v1/skills/sk_brief

Response

Skill
1{"id": "sk_brief", "title": "Write a brief", "kind": "prompt", "impl_ref": "…"}

GET /v1/methods#

Lists interpreter methods (map, reduce, route, …) a DAG node may use. These are harness primitives, not user-authored skills.

1curl -s http://localhost:8080/v1/methods

Response

1{"methods": ["map", "reduce", "route"]}

Runs#

GET /v1/runs#

Lists execution episodes — live and recent — for the Runs filter.

1curl -s 'http://localhost:8080/v1/runs?limit=20'

Response

WorkflowRun
1{2  "runs": [3    {4      "id": "r1000000-0000-0000-0000-000000000001",5      "workflow_id": "wf_venues",6      "status": "succeeded",7      "title": "Prepare three venue options"8    }9  ]10}

GET /v1/runs/{id}#

Returns one WorkflowRun: status, cost, result, and live trace.

1curl -s http://localhost:8080/v1/runs/r1000000-0000-0000-0000-000000000001

Response

WorkflowRun
1{2  "id": "r1000000-0000-0000-0000-000000000001",3  "kind": "workflow",4  "workflow_id": "wf_venues",5  "workflow_version": "3",6  "title": "Prepare three venue options",7  "status": "succeeded",8  "caps": {},9  "cost": {},10  "result": {},11  "live": {},12  "created_at": "2026-08-16T12:00:00Z",13  "finished_at": "2026-08-16T12:01:12Z"14}

DELETE /v1/runs/{id}#

Deletes the episode record. The Workflow it came from is unchanged.

1curl -s -X DELETE http://localhost:8080/v1/runs/r1000000-0000-0000-0000-000000000001

POST /v1/runs/{id}/pause#

Pauses a running episode at the next safe yield. Status becomes paused.

1curl -s -X POST http://localhost:8080/v1/runs/r1000000-0000-0000-0000-000000000001/pause

Response

WorkflowRun
1{2  "id": "r1000000-0000-0000-0000-000000000001",3  "workflow_id": "wf_venues",4  "status": "paused",5  "title": "Prepare three venue options"6}

POST /v1/runs/{id}/resume#

Continues a paused episode. Status returns to running.

1curl -s -X POST http://localhost:8080/v1/runs/r1000000-0000-0000-0000-000000000001/resume

Response

WorkflowRun
1{2  "id": "r1000000-0000-0000-0000-000000000001",3  "workflow_id": "wf_venues",4  "status": "running",5  "title": "Prepare three venue options"6}

POST /v1/runs/{id}/cancel#

Stops the episode and marks it cancelled. In-flight tool calls are abandoned at the next yield.

1curl -s -X POST http://localhost:8080/v1/runs/r1000000-0000-0000-0000-000000000001/cancel

Response

WorkflowRun
1{2  "id": "r1000000-0000-0000-0000-000000000001",3  "workflow_id": "wf_venues",4  "status": "cancelled",5  "title": "Prepare three venue options"6}

POST /v1/runs/{id}/approve#

Releases a consequential step that is waiting on human approval so the run can continue.

1curl -s -X POST http://localhost:8080/v1/runs/r1000000-0000-0000-0000-000000000001/approve

Response

WorkflowRun
1{2  "id": "r1000000-0000-0000-0000-000000000001",3  "workflow_id": "wf_venues",4  "status": "running",5  "title": "Prepare three venue options"6}

POST /v1/runs/{id}/promote#

Lifts a succeeded episode into a named library Workflow. Optional { "title" }. Lift is agent_calls, clone_def, or goal_skeleton. The reusable capability template is left unchanged. See Workflows.

1curl -s http://localhost:8080/v1/runs/r1000000-0000-0000-0000-000000000001/promote \2  -H 'content-type: application/json' \3  -d '{"title":"Venue shortlist"}'

Response

1{2  "workflow": {"id": "wf_…", "title": "Venue shortlist", "source": "promoted"},3  "lift": "agent_calls"4}

POST /v1/eval/from-runs#

Builds a scored corpus from recent succeeded runs of a workflow so prompt skills can be tuned without changing the DAG. Proposals land as improvements; apply them with POST /v1/improvements/{id}/approve. See Workflows.

1curl -s http://localhost:8080/v1/eval/from-runs \2  -H 'content-type: application/json' \3  -d '{"workflow_id":"wf_venues","limit":3,"metric":{"kind":"autophagy_score","threshold":0.55},"persist":true,"run":false}'

Events#

GET /v1/events#

Server-sent events for live ingest and run progress. The first event is ready; document events carry a Document. Keepalive pings every 15 seconds.

1curl -sN http://localhost:8080/v1/events

Response

Document
1event: ready2data: {"ok":true}3 4event: document5data: {"id":"a91c…","title":"Atlas notes","ingest_status":"succeeded"}

GET /v1/analytics#

Returns install-wide counts (conversations, documents, runs) for the dashboard.

1curl -s http://localhost:8080/v1/analytics

Response

1{"conversations": 12, "documents": 48, "runs": 7}

Memories#

GET /v1/thoughts#

Returns the groomed Memories projection: first-person Thoughts plus MemoryCards. The graph remains the source of truth. See Memory Groom.

1curl -s http://localhost:8080/v1/thoughts
1{2  "thoughts": [3    {4      "id": "th100000-0000-0000-0000-000000000001",5      "subject": "You",6      "predicate": "works_at",7      "src_name": "You",8      "dst_name": "Northstar Studio",9      "fact": "You work at Northstar Studio"10    }11  ],12  "cards": [13    {14      "id": "You",15      "subject": "You",16      "title": "Work",17      "facts": [],18      "needs_compaction": false19    }20  ]21}

DELETE /v1/thoughts/{id}#

Forgets one memory by superseding the underlying Triple. This is not a second store delete.

1curl -s -X DELETE http://localhost:8080/v1/thoughts/th100000-0000-0000-0000-000000000001

Response

1{"id":"th100000-0000-0000-0000-000000000001","status":"superseded"}

GET /v1/system-events#

Pages the memory-groom event ledger (dirty subjects, compaction) so a client can show what the groomer last did.

1curl -s 'http://localhost:8080/v1/system-events?limit=50'

Response

1{2  "events": [3    {"id": "ev1…", "type": "memory.subject.dirty", "created_at": "2026-08-16T12:00:00Z"}4  ]5}

GET /v1/thoughts-runtime#

Lists pending thought proposals from groom before they become Memories. Filter with ?status=pending.

1curl -s 'http://localhost:8080/v1/thoughts-runtime?status=pending'

Response

1{2  "proposals": [3    {"id": "tp1…", "status": "pending", "subject": "You"}4  ]5}

POST /v1/thoughts-runtime/{id}/approve#

Accepts or rejects a thought proposal (decision: approve or reject). Approved rows become Thoughts.

1curl -s -X POST http://localhost:8080/v1/thoughts-runtime/tp1…/approve \2  -H 'content-type: application/json' \3  -d '{"decision":"approve"}'

GET /v1/approvals#

Lists install-wide approval policies (ask / allow / deny) for consequential actions.

1curl -s http://localhost:8080/v1/approvals

Response

1{"approvals": [{"id": "ap1…", "action": "execute", "mode": "ask"}]}

POST /v1/tension/evaluate#

Scores current memory tension — contradictions and stale subjects — without applying groom.

1curl -s -X POST http://localhost:8080/v1/tension/evaluate

POST /v1/tension/tick#

Runs one groom tick: evaluate tension, then compact or propose as configured. See Memory Groom.

1curl -s -X POST http://localhost:8080/v1/tension/tick

Connections#

Successful connection results are persisted as a provenance Document before they are returned. Nested objects become candidate graph context.

GET /v1/connection-providers#

Lists connection providers and which runtime kinds they support (hosted versus a local daemon).

1curl -s http://localhost:8080/v1/connection-providers

Response

1{2  "providers": [3    {"id": "composio", "runtime_kinds": ["hosted"]}4  ]5}

GET /v1/composio/toolkits#

Lists toolkits available through the configured Composio provider so the UI can pick apps without hardcoding them.

1curl -s http://localhost:8080/v1/composio/toolkits

GET /v1/connections#

Lists configured Connections, from draft through ready, including provider and display name. Authenticate and discover are separate calls.

1curl -s http://localhost:8080/v1/connections

Response

Connection
1{2  "connections": [3    {4      "id": "c1000000-0000-0000-0000-000000000001",5      "provider_id": "composio",6      "display_name": "Work Composio",7      "status": "ready"8    }9  ]10}

POST /v1/connections#

Creates a draft Connection bound to a provider and runtime kind. Authenticate separately; this call does not talk to the provider.

1curl -s http://localhost:8080/v1/connections \2  -H 'content-type: application/json' \3  -d '{"provider_id":"composio","display_name":"Work Composio","runtime_kind":"hosted"}'

GET /v1/connections/{id}#

Returns one Connection’s status, provider, and display name.

1curl -s http://localhost:8080/v1/connections/c1000000-0000-0000-0000-000000000001

Response

Connection
1{2  "id": "c1000000-0000-0000-0000-000000000001",3  "provider_id": "composio",4  "display_name": "Work Composio",5  "status": "ready"6}

PATCH /v1/connections/{id}#

Updates display name or other non-secret fields on a Connection.

1curl -s -X PATCH http://localhost:8080/v1/connections/c1000000-0000-0000-0000-000000000001 \2  -H 'content-type: application/json' \3  -d '{"display_name":"Personal Composio"}'

DELETE /v1/connections/{id}#

Removes the Connection and its capability rows. Library documents imported from it stay.

1curl -s -X DELETE http://localhost:8080/v1/connections/c1000000-0000-0000-0000-000000000001

POST /v1/connections/{id}/authenticate#

Starts provider auth (API key or OAuth). The connection stays draft until the provider callback completes.

1curl -s http://localhost:8080/v1/connections/c1000000-0000-0000-0000-000000000001/authenticate \2  -H 'content-type: application/json' \3  -d '{"api_key":"ck_…","toolkits":["gmail"]}'

POST /v1/connections/{id}/discover#

Refreshes the capability catalog for this connection from the provider. Does not enable or approve any capability.

1curl -s -X POST http://localhost:8080/v1/connections/c1000000-0000-0000-0000-000000000001/discover

GET /v1/connections/{id}/capabilities#

Lists discovered capabilities on this connection and their risk class (read / write / …).

1curl -s http://localhost:8080/v1/connections/c1000000-0000-0000-0000-000000000001/capabilities

Response

1{2  "capabilities": [3    {"id": "cap_…", "name": "gmail.send", "enabled": true, "risk": "write"}4  ]5}

PATCH /v1/connections/{id}/capabilities/{capability_id}#

Enables or disables one capability on this connection. Disabled capabilities cannot be invoked.

1curl -s -X PATCH http://localhost:8080/v1/connections/c1…/capabilities/cap_… \2  -H 'content-type: application/json' \3  -d '{"enabled":true}'

POST /v1/connections/{id}/capabilities/{capability_id}/approve#

Issues a five-minute, one-use approval for a specific capability input so a consequential call can proceed.

1curl -s http://localhost:8080/v1/connections/c1…/capabilities/cap_…/approve \2  -H 'content-type: application/json' \3  -d '{"input":{"to":"maya@example.com","subject":"Atlas"}}'

POST /v1/connections/{id}/test#

Pings the provider with this connection’s credentials to confirm the binding still works.

1curl -s -X POST http://localhost:8080/v1/connections/c1000000-0000-0000-0000-000000000001/test

GET /v1/runtimes#

Lists paired local and hosted runtimes. Token hashes are never returned.

1curl -s http://localhost:8080/v1/runtimes

Response

1{2  "runtimes": [3    {"id": "rt1…", "kind": "local", "status": "ok"}4  ]5}

POST /v1/runtimes/pairings#

Mints a short-lived pairing code a daemon will redeem at POST /v1/runtimes/pair.

1curl -s -X POST http://localhost:8080/v1/runtimes/pairings

POST /v1/runtimes/pair#

Public one-time exchange: pairing code → runtime_id plus bearer token. Later daemon routes need that token.

1curl -s http://localhost:8080/v1/runtimes/pair \2  -H 'content-type: application/json' \3  -d '{"code":"ABCD-EFGH"}'

POST /v1/runtimes/{id}/heartbeat#

Daemon liveness. Updates last-seen and status so the orchestrator knows the runtime can take work.

1curl -s http://localhost:8080/v1/runtimes/rt1…/heartbeat \2  -H "authorization: Bearer $RUNTIME_TOKEN" \3  -H 'content-type: application/json' \4  -d '{"status":"ok"}'

GET /v1/runtimes/{id}/invocations/next#

Long-poll for the next work item this runtime should execute. Empty when idle.

1curl -s http://localhost:8080/v1/runtimes/rt1…/invocations/next \2  -H "authorization: Bearer $RUNTIME_TOKEN"

POST /v1/runtimes/{id}/invocations/{invocation_id}/complete#

Reports an invocation result back to the orchestrator so the waiting run can continue.

1curl -s http://localhost:8080/v1/runtimes/rt1…/invocations/inv1…/complete \2  -H "authorization: Bearer $RUNTIME_TOKEN" \3  -H 'content-type: application/json' \4  -d '{"ok":true,"output":{}}'

DELETE /v1/runtimes/{id}#

Unpairs the runtime and invalidates its token. In-flight invocations are abandoned.

1curl -s -X DELETE http://localhost:8080/v1/runtimes/rt1…

Identity#

See Identity & authorization.

GET /v1/scopes#

Lists identity scopes (personal or tenant) the current principal can see. See identity.

1curl -s http://localhost:8080/v1/scopes

Response

1{"scopes":[{"id":"…","kind":"personal","name":"Local"}]}

GET /v1/service-accounts#

Lists service accounts. They have no access until you grant policies.

1curl -s http://localhost:8080/v1/service-accounts

POST /v1/service-accounts#

Creates a service account with a name and optional description. No credential is issued yet.

1curl -s http://localhost:8080/v1/service-accounts \2  -H 'content-type: application/json' \3  -d '{"name":"ci","description":"Eval runner"}'

Response

1{"service_account":{"id":"sa100000-0000-0000-0000-000000000001","name":"ci"}}

POST /v1/service-accounts/{id}/credentials#

Issues a dsa_… secret for the service account. The secret is shown once; store it immediately.

1curl -s http://localhost:8080/v1/service-accounts/sa100000-0000-0000-0000-000000000001/credentials \2  -H 'content-type: application/json' \3  -d '{"name":"ci-main"}'

Response

1{2  "credential": {"id": "cred1…", "name": "ci-main"},3  "secret": "dsa_…"4}

DELETE /v1/service-accounts/credentials/{credential_id}#

Revokes that dsa_… secret. Other credentials on the same account keep working.

1curl -s -X DELETE http://localhost:8080/v1/service-accounts/credentials/cred1…

POST /v1/policies/grant#

Grants or denies a subject an action on an object (route or resource). Service accounts stay inert until this is called. See identity.

1curl -s http://localhost:8080/v1/policies/grant \2  -H 'content-type: application/json' \3  -d '{"subject":"sa:sa1…","object":"/v1/ask/evidence","action":"execute","allow":true}'

Voice#

POST /v1/voice/transcribe#

Speech-to-text for an uploaded audio clip. Returns { text }.

1curl -s http://localhost:8080/v1/voice/transcribe -F 'file=@clip.wav'

POST /v1/voice/speak#

Text-to-speech. The response body is audio, not JSON.

1curl -s http://localhost:8080/v1/voice/speak \2  -H 'content-type: application/json' \3  -d '{"text":"Maya owns Atlas."}' \4  --output speech.wav

POST /v1/voice/realtime/session#

Mints ephemeral credentials for a live voice session. The client uses them against the realtime transport, not this API.

1curl -s -X POST http://localhost:8080/v1/voice/realtime/session

GET /v1/voice/wake#

Returns whether wake-word listening is enabled and the phrase.

1curl -s http://localhost:8080/v1/voice/wake

POST /v1/voice/wake#

Enables or disables wake-word listening and sets the phrase.

1curl -s http://localhost:8080/v1/voice/wake \2  -H 'content-type: application/json' \3  -d '{"enabled":true,"phrase":"hey doso"}'

GET /v1/voice/models#

Lists on-device voice models and whether each is downloaded.

1curl -s http://localhost:8080/v1/voice/models

POST /v1/voice/models/{id}/download#

Starts downloading an on-device voice model. Poll GET /v1/voice/models for status.

1curl -s -X POST http://localhost:8080/v1/voice/models/whisper-small/download

DELETE /v1/voice/models/{id}#

Deletes a downloaded on-device voice model from disk.

1curl -s -X DELETE http://localhost:8080/v1/voice/models/whisper-small

Settings#

GET /v1/models#

Lists configured inference models in the OpenAI { object, data } shape so a completions client can pick an id.

1curl -s http://localhost:8080/v1/models

Response

1{2  "object": "list",3  "data": [{"id": "grok-4", "object": "model", "owned_by": "xai"}]4}

GET /v1/settings#

Returns public runtime settings with secrets redacted (Ask skeptics, providers, …).

1curl -s http://localhost:8080/v1/settings

Response

1{"ask_skeptics": true, "providers": ["xai"]}

PUT /v1/settings#

Updates public runtime settings. Secrets are not accepted here.

1curl -s -X PUT http://localhost:8080/v1/settings \2  -H 'content-type: application/json' \3  -d '{"ask_skeptics":true}'

POST /v1/providers/openrouter/oauth/start#

Starts OpenRouter OAuth and returns { url, state } to send the user to.

1curl -s -X POST http://localhost:8080/v1/providers/openrouter/oauth/start

Vault#

Remote/server exposure is disabled unless DOSO_ENABLE_VAULT_EXPOSURE=1.

GET /v1/vault/status#

Returns lock state, vault mode, and the configured remote. See Git vault.

1curl -s http://localhost:8080/v1/vault/status

Response

1{"locked": false, "mode": "git", "remote": null}

POST /v1/vault/init#

Creates the Git vault with a passphrase. First-time setup only. See Git vault.

1curl -s http://localhost:8080/v1/vault/init \2  -H 'content-type: application/json' \3  -d '{"passphrase":"…"}'

POST /v1/vault/unlock#

Unlocks an existing vault for this process so Library reads and writes can proceed.

1curl -s http://localhost:8080/v1/vault/unlock \2  -H 'content-type: application/json' \3  -d '{"passphrase":"…"}'

POST /v1/vault/lock#

Locks the vault in this process. Subsequent Library reads fail until unlock.

1curl -s -X POST http://localhost:8080/v1/vault/lock

PUT /v1/settings/storage#

Switches the Ask index between SQLite and Postgres. Requires DOSO_ENABLE_STORAGE_MIGRATION=1. Independent of the Git vault. See data modes.

1curl -s -X PUT http://localhost:8080/v1/settings/storage \2  -H 'content-type: application/json' \3  -d '{"mode":"postgres","url":"postgres://…"}'