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 |
| Auth | local principal on loopback, Better Auth JWT, or dsa_… service account |
| Body limit | 64 MiB |
| CORS | CORS_ALLOWED_ORIGINS (local web and Tauri by default) |
Ask has two callers that share retrieval:
| You want | Call |
|---|---|
| Doso to draft an answer | POST /v1/chat/completions or POST /v1/search |
| The retrieve pack only | POST /v1/ask/evidence |
effort on chat completions:
| Value | Behavior |
|---|---|
fast (default) | Seed → 1-hop facts → library passages → one draft |
expert | Full 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.
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.
| Field | Type | Notes |
|---|---|---|
id | uuid | Stable id |
canonical_name | string | Display name |
type | string | PERSON, ORGANIZATION, ARTIFACT, CONCEPT, … |
description | string | Grounded one-liner |
aliases | string[] | Other surface forms |
mention_count | number | How often it appears |
props | object | Extra typed properties |
updated_at | datetime | ISO-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.
| Field | Type | Notes |
|---|---|---|
id | uuid | |
title | string | |
mime | string | text/plain, application/pdf, … |
text | string | Extracted body |
uri | string? | Stable connector URI |
checksum | string? | |
meta | object | Includes source, ingest_status |
created_at | datetime |
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.
| Field | Type | Notes |
|---|---|---|
id | uuid | Document id |
title | string | |
uri | string? | |
excerpt | string | ~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.
| Field | Type |
|---|---|
id | uuid |
title | string |
created_at | datetime |
updated_at | datetime |
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.
| Field | Type | Notes |
|---|---|---|
id | uuid | |
conversation_id | uuid | |
role | string | user or assistant |
content | string | |
meta | object | Citations, run id, workflow |
created_at | datetime |
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.
| Field | Type | Notes |
|---|---|---|
src | string | Subject name |
predicate | string | |
dst | string | Object name |
source_doc | string | Optional 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.
| Field | Type | Notes |
|---|---|---|
id | uuid | |
src_id | uuid | |
dst_id | uuid | |
predicate | string | |
statement | string | Display prose |
source_doc_id | uuid? | |
status | string | candidate, verified, rejected |
confidence | number? | |
extracted_at | datetime | |
src_name | string | |
dst_name | string | |
src_type | string | |
dst_type | string |
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.
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.
| Field | Type |
|---|---|
entity | Entity |
about | string |
relations | object[] |
sources | Document[] |
summary | string |
compacted | boolean |
needs_compaction | boolean |
updated_at | datetime |
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.
| Field | Type | Notes |
|---|---|---|
exists | boolean | |
named | boolean | |
has_thoughts | boolean | Durable self-facts exist |
canonical_name | string | Empty until named |
entity_id | uuid? |
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.
| Field | Type |
|---|---|
id | uuid |
src_id | uuid |
dst_id | uuid |
predicate | string |
src_name | string |
dst_name | string |
fact | string |
subject | string |
extracted_at | datetime |
source_doc_id | uuid? |
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.
| Field | Type |
|---|---|
id | string |
subject | string |
title | string |
summary | string |
facts | Thought[] |
updated_at | datetime |
compacted | boolean |
needs_compaction | boolean |
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.
| Field | Type | Notes |
|---|---|---|
id | string | Catalog or wf_* library id |
title | string | |
description | string | |
version | string | |
source | string | catalog, chat, promoted, … |
dag | object | { 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.
| Field | Type | Notes |
|---|---|---|
id | uuid | |
kind | string | |
workflow_id | string | |
workflow_version | string | |
title | string | |
status | string | running, succeeded, failed, … |
caps | object | |
cost | object | |
result | object | |
live | object | |
error | string? | |
created_at | datetime | |
finished_at | datetime? |
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.
| Field | Type | Notes |
|---|---|---|
id | string | Stable slug |
title | string | |
kind | string | prompt, tool, code, builtin |
impl_ref | string | Prompt 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.
| Field | Type | Notes |
|---|---|---|
id | uuid | |
provider_id | string | e.g. composio |
display_name | string | |
runtime_kind | string | hosted or local daemon |
status | string | draft, ready, … |
Authentication#
GET /healthzis public.POST /v1/runtimes/pairis 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 andx-api-keyare not supported. DOSO_AUTH_MODE=jwksverifies JWTs viaDOSO_JWKS_URL/ issuer / audience. This is the product default for compose and hosted instances.DOSO_AUTH_MODE=localtrusts 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
400with a short plain-text body. Missing rows are404. Unexpected failures are500.
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/healthzResponse
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.
| Field | Type | Required | Notes |
|---|---|---|---|
messages | array | yes | Last user turn is the question |
effort | string | no | fast (default) or expert |
model | string | no | Used only when effort is empty |
stream | bool | no | true returns text/event-stream |
conversation_id | uuid | no | Reuse 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 }'Response
CitationChatMessageConversation1{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.
| Field | Type | Required | Notes |
|---|---|---|---|
q | string | yes | At least 3 characters |
1curl -s http://localhost:8080/v1/search \2 -H 'content-type: application/json' \3 -d '{"q":"Atlas prototype deadline"}'Response
Citation1{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.
| Field | Type | Notes |
|---|---|---|
messages | array | UI messages with parts[] |
conversation_id | uuid | Optional Conversation |
workflow_id | string | When set, every turn iterates this workflow |
current_workflow | object | Optional 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/ownerResponse
OwnerStatus1{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.
| Query | Notes |
|---|---|
limit | Default 100 |
ingest_status | Optional filter, e.g. failed |
1curl -s 'http://localhost:8080/v1/content/documents?limit=20'Response
Document1{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
Document1{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/search#
Keyword search over Library text. Returns DocumentHit excerpts, not full bodies — use this when you already know a phrase, not when you want Ask.
| Query | Notes |
|---|---|
q | Required, at least 3 characters |
uri_prefix | Optional URI prefix |
limit | Default 6 |
1curl -sG http://localhost:8080/v1/content/documents/search \2 --data-urlencode 'q=Atlas deadline'Response
DocumentHit1{2 "documents": [3 {4 "id": "a91c0000-0000-0000-0000-000000000001",5 "title": "Atlas notes",6 "uri": "note://atlas",7 "excerpt": "…Maya owns Atlas. Deadline is Friday.…"8 }9 ]10}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
Document1{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-000000000001Response
Document1{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-000000000001POST /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/ingestResponse
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
Conversation1{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
Conversation1{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-000000000001Response
ConversationChatMessage1{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-000000000001POST /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
ChatMessage1{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.
| Query | Notes |
|---|---|
q | Search string |
type | Optional type filter |
1curl -sG http://localhost:8080/v1/graph/entities \2 --data-urlencode 'q=Atlas' \3 --data-urlencode 'type=ARTIFACT'Response
Entity1{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
Entity1{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}1curl -s http://localhost:8080/v1/graph/entities/8c1e0000-0000-0000-0000-000000000001Response
Entity1{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.
| Query | Notes |
|---|---|
hops | Default 0 |
status | Optional 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/wikiResponse
EntityWiki1{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
Subgraph1{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
Triple1{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}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
Triple1{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/promoteGET /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/ontologyResponse
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/workflowsResponse
Workflow1{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.
| Field | Type | Notes |
|---|---|---|
workflow_id | string | Saved or catalog id |
objective | string | |
hints | object | Optional |
approve_consequential | bool | Only 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
WorkflowRun1{2 "run_id": "r1000000-0000-0000-0000-000000000001",3 "workflow_id": "wf_venues",4 "workflow_version": "3",5 "result": {}6}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_venuesResponse
Workflow1{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_venuesPOST /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/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/methodsResponse
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
WorkflowRun1{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-000000000001Response
WorkflowRun1{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}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/pauseResponse
WorkflowRun1{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/resumeResponse
WorkflowRun1{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/cancelResponse
WorkflowRun1{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/approveResponse
WorkflowRun1{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#
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/eventsResponse
Document1event: 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/analyticsResponse
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/thoughtsResponse
ThoughtMemoryCard1{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}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}GET /v1/approvals#
Lists install-wide approval policies (ask / allow / deny) for
consequential actions.
1curl -s http://localhost:8080/v1/approvalsResponse
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/evaluatePOST /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/tickConnections#
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-providersResponse
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/toolkitsGET /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/connectionsResponse
Connection1{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-000000000001Response
Connection1{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-000000000001POST /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/discoverGET /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/capabilitiesResponse
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/testGET /v1/runtimes#
Lists paired local and hosted runtimes. Token hashes are never returned.
1curl -s http://localhost:8080/v1/runtimesResponse
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/pairingsPOST /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#
GET /v1/service-accounts#
Lists service accounts. They have no access until you grant policies.
1curl -s http://localhost:8080/v1/service-accountsPOST /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.wavPOST /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/sessionGET /v1/voice/wake#
Returns whether wake-word listening is enabled and the phrase.
1curl -s http://localhost:8080/v1/voice/wakePOST /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/modelsPOST /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/downloadDELETE /v1/voice/models/{id}#
Deletes a downloaded on-device voice model from disk.
1curl -s -X DELETE http://localhost:8080/v1/voice/models/whisper-smallSettings#
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/modelsResponse
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/settingsResponse
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/startVault#
Remote/server exposure is disabled unless DOSO_ENABLE_VAULT_EXPOSURE=1.
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/lockPUT /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://…"}'