Skip to main content

API reference

This is the complete reference for every endpoint exposed by the AgentFlow server. All endpoints return JSON by default (using ORJSON for performance). The interactive Swagger UI at /docs reflects the live server and can be used for manual testing.

Conventions

Base URL

All API routes (except /ping) are prefixed with /v1/.

Response envelope

Every successful endpoint wraps its payload in a uniform envelope:

{
"success": true,
"data": { ... },
"message": "optional message",
"timestamp": "2026-05-23T10:00:00Z"
}

Errors (4xx / 5xx) return FastAPI's standard detail format:

{
"detail": "Not authorized to invoke graph"
}

Authentication

When auth is configured in agentflow.json, all endpoints except /ping require a Bearer token:

Authorization: Bearer <token>

WebSocket connections that cannot send an Authorization header can pass the token as a query parameter instead:

/v1/graph/ws?token=<token>

If auth is not configured ("auth": null), credentials are not required and the auth layer is skipped entirely.

Permission model

Each endpoint requires a specific (resource, action) pair. The table below lists them. When an AuthorizationBackend is configured, the authorize(user, resource, action) method is called. The built-in DefaultAuthorizationBackend allows all requests as long as user_id is present.


Health

GET /ping

No authentication. Returns a pong response. Use as a load-balancer or monitoring health check.

Response

{
"success": true,
"data": "pong"
}

cURL

curl http://127.0.0.1:8000/ping

Graph

All graph endpoints require the graph resource. The graph is loaded at startup from the agent field in agentflow.json.

POST /v1/graph/invoke

Execute the graph with a list of messages and return the final result synchronously.

Permission: graph:invoke

Request body

{
"messages": [
{"role": "user", "content": "What is the weather in London?"}
],
"config": {
"thread_id": "thread-abc123"
},
"initial_state": null,
"recursion_limit": 25,
"response_granularity": "low"
}
FieldTypeRequiredDefaultDescription
messagesMessage[]YesAt least one message. The last message is the input to the graph.
configobjectNonullForwarded to the graph as run config. Include thread_id to resume a thread.
initial_stateobjectNonullOverride the initial graph state before the run.
recursion_limitintNo25Max number of graph iterations (1–100). Increase for complex multi-step tasks.
response_granularitystringNo"low"How much of the graph state to include in the response. Values: "low", "partial", "full".

Response data

{
"messages": [
{"role": "user", "content": "What is the weather in London?"},
{"role": "assistant", "content": "The weather in London is 15°C and cloudy."}
],
"state": null,
"context": null,
"summary": null,
"meta": null
}
FieldTypeDescription
messagesMessage[]Full conversation history after the run.
stateobject | nullGraph state snapshot (populated when response_granularity is "full").
contextMessage[] | nullContext messages used by the graph.
summarystring | nullConversation summary if the graph produces one.
metaobject | nullAdditional metadata from the graph run.

POST /v1/graph/stream

Execute the graph with streaming output. Returns a Server-Sent Events (SSE) stream.

Permission: graph:stream

Request body: Same as POST /v1/graph/invoke.

Response: Content-Type: text/event-stream

Each event is a JSON-encoded StreamChunk:

{"event": "updates", "data": {"messages": [...], "node": "agent"}}

StreamChunk events:

EventWhen
updatesA graph node has produced new output
errorA recoverable error occurred mid-stream
doneStream has finished (final chunk)

SSE headers set by the server:

Cache-Control: no-cache, no-transform
Connection: keep-alive
X-Accel-Buffering: no
Content-Encoding: identity

The X-Accel-Buffering: no header disables nginx buffering for true real-time delivery.


WebSocket /v1/graph/ws

Bidirectional WebSocket for streaming graph execution. Supports fresh runs and resuming after remote tool calls.

Permission: graph:stream

Authentication: Pass the Bearer token as the Authorization header during the WebSocket upgrade, or as ?token=<value> in the URL.

Client → Server messages (JSON)

{
"invoke_type": "fresh",
"messages": [{"role": "user", "content": "Hello"}],
"config": {"thread_id": "abc"},
"initial_state": null,
"recursion_limit": 25,
"response_granularity": "low"
}
FieldTypeDefaultDescription
invoke_type"fresh" | "resume""fresh""fresh" starts a new run; "resume" continues after a remote tool call.
messagesMessage[][]Required and non-empty for fresh runs.
tool_resultMessage[] | nullnullRequired for resume runs — the tool-result messages to inject.
configobject | nullnullMust include thread_id for resume runs.
initial_stateobject | nullnullInitial state override.
recursion_limitint25Max iterations (1–100).
response_granularitystring"low"Response detail level.

Server → Client messages

The server sends StreamChunk JSON messages identical to the SSE stream, followed by a final done chunk:

{"event": "updates", "data": {"status": "done"}}

Close codes:

CodeMeaning
1000Normal closure — client disconnected cleanly
1011Server error — unexpected exception

Resume flow (remote tool calls):

  1. Client receives a StreamChunk with event: "remote_tool_call" mid-stream.
  2. Client executes the tool locally.
  3. Client sends a resume message with tool_result containing the tool outputs and config.thread_id matching the original thread.
  4. Server resumes the graph from the checkpoint and continues streaming.

GET /v1/graph

Return the graph structure and metadata (nodes, edges, capabilities).

Permission: graph:read

Response data

{
"info": {
"node_count": 3,
"edge_count": 4,
"checkpointer": true,
"checkpointer_type": "PgCheckpointer",
"publisher": false,
"store": false,
"interrupt_before": null,
"interrupt_after": null,
"context_type": null,
"id_generator": "snowflake",
"id_type": "int",
"state_type": "AgentState",
"state_fields": ["messages", "context", "summary"]
},
"nodes": [
{"id": "agent", "name": "agent"},
{"id": "tools", "name": "tools"},
{"id": "__end__", "name": "__end__"}
],
"edges": [
{"id": "e1", "source": "agent", "target": "tools"},
{"id": "e2", "source": "tools", "target": "agent"}
]
}

GET /v1/graph:StateSchema

Return the state schema of the graph as a JSON object.

Permission: graph:read

Response data: A JSON object describing the graph's state fields and their types.


POST /v1/graph/stop

Stop a running graph execution for a specific thread. Useful when a graph is in a long-running loop.

Permission: graph:stop

Request body

{
"thread_id": "thread-abc123",
"config": null
}
FieldTypeRequiredDescription
thread_idstringYesNon-empty thread ID to stop.
configobject | nullNoOptional additional config forwarded to the stop operation.

POST /v1/graph/setup

Register remote tools with the graph. Used by the TypeScript client to inform the graph server which client-side tools are available before starting a run.

Permission: graph:setup

Request body

{
"tools": [
{
"node_name": "client_tools",
"name": "get_location",
"description": "Get the user's current geolocation",
"parameters": {
"type": "object",
"properties": {}
}
}
]
}

POST /v1/graph/fix

Remove messages with empty tool-call content from a thread's state. Useful for recovering from interrupted or failed tool executions.

Permission: graph:fix

Request body

{
"thread_id": "thread-abc123",
"config": null
}

Response data

{
"success": true,
"message": "Removed 2 messages with empty tool calls",
"removed_count": 2,
"state": { ... }
}

Threads

Thread endpoints require a checkpointer to be configured. Without a checkpointer these endpoints return errors.

GET /v1/threads/{thread_id}/state

Get the current state snapshot for a thread.

Permission: checkpointer:read

Path params: thread_id (string or integer, non-empty)

Response data

{
"state": {
"messages": [...],
"context": null
}
}

PUT /v1/threads/{thread_id}/state

Overwrite the state for a thread.

Permission: checkpointer:write

Request body

{
"state": {
"messages": [...],
"context": null
},
"config": null
}

DELETE /v1/threads/{thread_id}/state

Clear the state for a thread (removes the checkpoint).

Permission: checkpointer:delete


GET /v1/threads/{thread_id}/messages

List messages for a thread, with optional search and pagination.

Permission: checkpointer:read

Query params

ParamTypeDescription
searchstringOptional text search filter.
offsetintNumber of messages to skip (must be ≥ 0).
limitintMax messages to return (must be > 0).

Response data

{
"messages": [
{"role": "user", "content": "Hello", "id": "msg-1"},
{"role": "assistant", "content": "Hi there!", "id": "msg-2"}
]
}

GET /v1/threads/{thread_id}/messages/{message_id}

Get a single message by ID.

Permission: checkpointer:read


POST /v1/threads/{thread_id}/messages

Append messages to a thread's history.

Permission: checkpointer:write

Request body

{
"messages": [
{"role": "user", "content": "Follow-up message"}
],
"metadata": null,
"config": null
}

DELETE /v1/threads/{thread_id}/messages/{message_id}

Delete a specific message from a thread.

Permission: checkpointer:delete

Request body

{
"config": null
}

GET /v1/threads

List all threads, with optional search and pagination.

Permission: checkpointer:read

Query params

ParamTypeDescription
searchstringOptional text search filter applied to thread metadata.
offsetintNumber of threads to skip.
limitintMax threads to return.

Response data

{
"threads": [
{"thread_id": "abc", "name": "thoughtful-dialogue", "created_at": "..."},
{"thread_id": "def", "name": "my-thread", "created_at": "..."}
]
}

GET /v1/threads/{thread_id}

Get metadata for a single thread.

Permission: checkpointer:read

Response data

{
"thread_data": {
"thread_id": "abc",
"name": "thoughtful-dialogue",
"created_at": "..."
}
}

DELETE /v1/threads/{thread_id}

Delete a thread and all its associated state and messages.

Permission: checkpointer:delete

Request body

{
"config": null
}

Store (memory)

Store endpoints require a store backend to be configured. The store is a semantic memory layer — content is embedded and retrieved by similarity.

POST /v1/store/memories

Store a new memory item.

Permission: store:write

Request body

{
"content": "The user prefers dark mode",
"memory_type": "episodic",
"category": "preferences",
"metadata": {"source": "conversation"},
"config": null,
"options": null
}
FieldTypeDefaultDescription
contentstring | MessageMemory text or structured message.
memory_typestring"episodic"Memory classification. Values: "episodic", "semantic", "procedural".
categorystring"general"Arbitrary category label.
metadataobject | nullnullArbitrary key-value metadata.
configobject | nullnullConfig forwarded to the store backend.
optionsobject | nullnullExtra keyword args forwarded to the store backend.

Response data

{
"memory_id": "mem-abc123"
}

POST /v1/store/search

Search memories by semantic similarity.

Permission: store:read

Request body

{
"query": "user interface preferences",
"memory_type": null,
"category": null,
"limit": 10,
"score_threshold": null,
"filters": null,
"retrieval_strategy": "similarity",
"distance_metric": "cosine",
"max_tokens": 4000,
"config": null,
"options": null
}
FieldTypeDefaultDescription
querystringRequired. Semantic search query text.
memory_typestring | nullnullFilter by memory type.
categorystring | nullnullFilter by category.
limitint10Max results.
score_thresholdfloat | nullnullMinimum similarity score to include.
filtersobject | nullnullBackend-specific additional filters.
retrieval_strategystring"similarity"How to retrieve results: "similarity", "hybrid", etc.
distance_metricstring"cosine"Distance function: "cosine", "euclidean", "dot_product".
max_tokensint4000Max tokens for truncation during similarity calculation.

Response data

{
"results": [
{
"memory_id": "mem-abc123",
"content": "The user prefers dark mode",
"score": 0.92,
"memory_type": "episodic",
"category": "preferences",
"metadata": {}
}
]
}

POST /v1/store/memories/{memory_id}

Get a single memory by ID.

Permission: store:read

Request body (optional)

{
"config": null,
"options": null
}

POST /v1/store/memories/list

List all memories from the store.

Permission: store:read

Request body (optional)

{
"limit": 100,
"config": null,
"options": null
}

Response data

{
"memories": [...]
}

PUT /v1/store/memories/{memory_id}

Update the content or metadata of a stored memory.

Permission: store:write

Request body

{
"content": "Updated memory content",
"metadata": {"updated": true},
"config": null,
"options": null
}

DELETE /v1/store/memories/{memory_id}

Delete a memory by ID.

Permission: store:delete

Request body (optional)

{
"config": null,
"options": null
}

POST /v1/store/memories/forget

Bulk-delete memories matching the provided filters.

Permission: store:delete

Request body

{
"memory_type": "episodic",
"category": "preferences",
"filters": null,
"config": null,
"options": null
}

All fields are optional. Omitting a filter means it does not constrain the deletion.


Files

File endpoints handle multimodal content (images, audio, documents). Requires a MediaStore backend. The default storage type is local (disk).

POST /v1/files/upload

Upload a file. Accepts multipart/form-data.

Permission: files:upload

Form field: file (the file binary)

Response data

{
"file_id": "f-abc123",
"mime_type": "image/png",
"size_bytes": 204800,
"filename": "chart.png",
"extracted_text": null,
"url": "/v1/files/f-abc123",
"direct_url": null,
"direct_url_expires_at": null
}
FieldDescription
file_idOpaque storage key used in all subsequent requests.
mime_typeMIME type detected from the Content-Type header or file extension.
size_bytesFile size in bytes.
filenameOriginal filename from the upload.
extracted_textFor documents: text extracted for use in graph context. null for images/audio.
urlRelative URL to retrieve the raw file binary.
direct_urlPre-signed URL for cloud storage (if configured). null for local storage.
direct_url_expires_atUnix timestamp when direct_url expires. null for local storage.

Maximum file size is controlled by MEDIA_MAX_SIZE_MB (default 25 MB). Exceeding this limit returns 413.


GET /v1/files/{file_id}

Download raw file binary.

Permission: files:read

Response: Raw binary with correct Content-Type.


GET /v1/files/{file_id}/info

Get file metadata without downloading the binary.

Permission: files:read

Response data

{
"file_id": "f-abc123",
"mime_type": "image/png",
"size_bytes": 204800,
"filename": "chart.png",
"extracted_text": null,
"direct_url": null,
"direct_url_expires_at": null
}

GET /v1/files/{file_id}/url

Get a direct access URL for a file. For local storage this returns the /v1/files/{file_id} path. For cloud storage this returns a pre-signed URL with a TTL controlled by MEDIA_SIGNED_URL_TTL_SECONDS.

Permission: files:read

Response data

{
"file_id": "f-abc123",
"url": "https://bucket.s3.amazonaws.com/...",
"expires_at": 1748000000,
"mime_type": "image/png"
}

Config

GET /v1/config/multimodal

Return the current multimodal (file storage) configuration. Useful for client-side feature detection.

Permission: config:read

Response data

{
"media_storage_type": "local",
"media_storage_path": "./uploads",
"media_max_size_mb": 25.0,
"document_handling": "extract_text"
}
FieldValuesDescription
media_storage_type"memory", "local", "cloud", "pg"Where files are stored.
media_storage_pathstringLocal path or cloud prefix.
media_max_size_mbfloatUpload size limit in MB.
document_handling"extract_text", "pass_raw", "skip"How uploaded documents are processed.

Permission reference

The table below lists every (resource, action) pair enforced by the server. When an AuthorizationBackend is configured, each request calls authorize(user, resource, action).

EndpointResourceAction
POST /v1/graph/invokegraphinvoke
POST /v1/graph/streamgraphstream
WebSocket /v1/graph/wsgraphstream
GET /v1/graphgraphread
GET /v1/graph:StateSchemagraphread
POST /v1/graph/stopgraphstop
POST /v1/graph/setupgraphsetup
POST /v1/graph/fixgraphfix
GET /v1/threads/{id}/statecheckpointerread
PUT /v1/threads/{id}/statecheckpointerwrite
DELETE /v1/threads/{id}/statecheckpointerdelete
GET /v1/threads/{id}/messagescheckpointerread
GET /v1/threads/{id}/messages/{msg}checkpointerread
POST /v1/threads/{id}/messagescheckpointerwrite
DELETE /v1/threads/{id}/messages/{msg}checkpointerdelete
GET /v1/threadscheckpointerread
GET /v1/threads/{id}checkpointerread
DELETE /v1/threads/{id}checkpointerdelete
POST /v1/store/memoriesstorewrite
POST /v1/store/searchstoreread
POST /v1/store/memories/{id}storeread
POST /v1/store/memories/liststoreread
PUT /v1/store/memories/{id}storewrite
DELETE /v1/store/memories/{id}storedelete
POST /v1/store/memories/forgetstoredelete
POST /v1/files/uploadfilesupload
GET /v1/files/{id}filesread
GET /v1/files/{id}/infofilesread
GET /v1/files/{id}/urlfilesread
GET /v1/config/multimodalconfigread
GET /ping(none)(none)

HTTP status codes

CodeWhen
200Success
400Empty file upload or missing required field
401Token missing or invalid (when auth is configured)
403Authorization check failed
404Resource not found (file, thread, message)
413Uploaded file exceeds MEDIA_MAX_SIZE_MB
422Request validation error (malformed body or invalid param)
500Unexpected server error