Graph endpoints

In shortReference for the 10xGraph REST endpoints that invoke, stream, stop, and inspect a graph, with request bodies, responses, and auth requirements.

  • 7 min read
  • 10 sections
  • Updated
  • v0.9.2
  • Markdown

These endpoints drive the core graph execution. All requests require a valid thread_id in config when the server has a checkpointer configured.

Base path: /v1/graph

Route summary

Method Path Permission
POST /v1/graph/invoke graph:invoke
POST /v1/graph/stream graph:stream
POST /v1/graph/stop graph:stop
POST /v1/graph/fix graph:fix
GET /v1/graph graph:read
GET /v1/graph/tools graph:read
GET /v1/graph:StateSchema graph:read
GET /v1/observability/{thread_id} graph:read
WS /v1/graph/ws graph:stream
WS /v1/graph/live graph:stream

/v1/observability/{thread_id} is documented in Observability and WS /v1/graph/live in Live WebSocket.


POST /v1/graph/invoke

Run the graph and return the complete result when all nodes finish.

Request body:

JSON
{
  "messages": [
    {
      "role": "user",
      "content": "What is the capital of France?"
    }
  ],
  "config": {
    "thread_id": "my-thread-1"
  }
}
Field Type Required Description
messages array yes, unless resume is set One or more messages to append to state before invoking.
resume any no Answer for a thread paused by interrupt(). The paused node runs again and interrupt() returns this value; null means cancelled. Send it only to resume a paused thread.
initial_state object no Initial state for the run
config object no Run configuration
config.thread_id string no Conversation identifier. When absent or blank, the server generates one and persists the thread; the id comes back in data.meta.thread_id.
config.remote_tools - - Server-owned: dropped from client config, along with user, user_id, authz and _-prefixed keys.
recursion_limit integer no Maximum graph iterations. Default 25, range 1-100.
response_granularity string no full, partial, or low. Default low.

Messages may carry image and document content blocks referencing an uploaded file_id. Those references are resolved before the graph runs; see Multimodal and vision.

Response:

JSON
{
  "success": true,
  "data": {
    "messages": [
      {"role": "user", "content": "What is the capital of France?"},
      {"role": "assistant", "content": "The capital of France is Paris."}
    ]
  }
}

POST /v1/graph/stream

Run the graph and stream one JSON-encoded StreamChunk per line.

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

Response: Content-Type: text/event-stream

Despite the content type, the body is not SSE-framed. There are no data: prefixes and no blank-line separators. It is newline-delimited JSON, so split the body on newlines rather than using EventSource or an SSE client library.

plaintext
{"event": "message", "message": {"role": "assistant", "content": [{"type": "text", "text": "The capital"}]}, "thread_id": "t1", ...}
{"event": "message", "message": {"role": "assistant", "content": [{"type": "text", "text": " of France is Paris."}]}, "thread_id": "t1", ...}
{"event": "updates", "data": {"status": "done"}, "thread_id": "t1", ...}

StreamChunk fields

Field Type Description
event string Chunk discriminator. One of message, state, updates, error.
message object | null Populated when event is message. A full Message with a content block list.
state object | null Populated when event is state. The current AgentState.
data object | null Populated for updates (for example {"status": "done"}) and error (for example {"reason": "..."}).
thread_id string | null Thread this chunk belongs to.
run_id string | null Run this chunk belongs to.
metadata object | null Additional per-chunk metadata.
timestamp number UNIX timestamp of chunk creation.

Tool activity arrives as ordinary message chunks whose content carries tool_call and tool_result blocks. There are no separate tool_call or done event types.


POST /v1/graph/stop

Cancel an in-progress graph execution.

Request body:

JSON
{
  "thread_id": "my-thread-1",
  "config": {}
}

Response:

JSON
{
  "success": true,
  "data": {"status": "stopped", "thread_id": "my-thread-1"}
}

The running graph finishes the current node and then stops. It does not interrupt mid-node.


GET /v1/graph

Get metadata about the loaded graph: its nodes, edges, and an info block describing how the server is wired.

Response:

JSON
{
  "success": true,
  "data": {
    "info": {
      "node_count": 2,
      "edge_count": 3,
      "is_realtime": false,
      "checkpointer": true,
      "checkpointer_type": "PgCheckpointer",
      "publisher": false,
      "store": false,
      "interrupt_before": null,
      "interrupt_after": null,
      "context_type": null,
      "id_generator": "SnowFlakeIdGenerator",
      "id_type": "BIGINT",
      "state_type": "AgentState",
      "state_fields": ["context", "context_summary"]
    },
    "nodes": [
      {"id": "MAIN", "name": "MAIN"},
      {"id": "TOOL", "name": "TOOL"}
    ],
    "edges": [
      {"id": "MAIN-TOOL", "source": "MAIN", "target": "TOOL"}
    ]
  }
}

info.is_realtime

is_realtime is true when the compiled graph is rooted at a live (realtime) agent. Clients use it to decide which socket to open:

is_realtime Use
false POST /v1/graph/invoke, POST /v1/graph/stream, WS /v1/graph/ws
true WS /v1/graph/live

Connecting to the wrong one is rejected at the handshake with close code 1008, so read this field once at startup and route accordingly.


GET /v1/graph/tools

List the tools exposed by every ToolNode in the graph, grouped by node.

Response:

JSON
{
  "success": true,
  "data": {
    "node_count": 1,
    "tool_count": 2,
    "nodes": [
      {
        "node_name": "TOOL",
        "tool_count": 2,
        "tools": [
          {
            "name": "get_weather",
            "description": "Look up the current weather for a city.",
            "source": "local",
            "parameters": {"type": "object", "properties": {"city": {"type": "string"}}}
          },
          {
            "name": "search_repos",
            "description": "Search GitHub repositories.",
            "source": "mcp",
            "parameters": {"type": "object", "properties": {"query": {"type": "string"}}}
          }
        ]
      }
    ]
  }
}
Field Description
node_count Number of ToolNodes in the graph
tool_count Total tools across all tool nodes
nodes[].node_name Name of the tool node in the graph
nodes[].tools[].source local (a Python function on the node), mcp (from a connected MCP server), or remote (declared in 10xgraph.json and executed by a client)
nodes[].tools[].parameters JSON Schema for the tool’s parameters, in OpenAI function-calling shape

Tool collection is best-effort per node: an MCP server that fails to respond is logged and its node contributes no tools, rather than failing the whole request.


POST /v1/graph/fix

Repair a thread whose state contains messages with tool calls that have empty content, which is what an interrupted or failed tool execution leaves behind. Those messages are removed from the state.

Request body:

JSON
{
  "thread_id": "my-thread-1",
  "config": {}
}

thread_id is required and must not be blank.

Response:

JSON
{
  "success": true,
  "data": {
    "success": true,
    "message": "Removed 1 message with empty tool calls",
    "removed_count": 1,
    "state": {}
  }
}

WS /v1/graph/ws

Turn-based streaming over a WebSocket. Same payloads as POST /v1/graph/stream, but the connection stays open across runs, which is what makes client-side tool execution possible without a second HTTP request.

Always mounted. There is no setting that turns this endpoint off. Concurrent connections are capped per process by websocket.max_connections and max_connections_per_user; a refused handshake closes with code 1013.

Authentication. Bearer token via the Authorization header, the agentflow-bearer Sec-WebSocket-Protocol (preferred for browsers), or the ?token= query fallback. Identical to the live socket.

Fresh run. Client sends:

JSON
{
  "invoke_type": "fresh",
  "messages": [{"role": "user", "content": "What is the weather in Paris?"}],
  "config": {"thread_id": "my-thread-1"}
}

messages must be non-empty. config.thread_id is optional; omit it to start a new thread.

Resume after a remote tool call. When the stream contains a remote tool call, the client executes the tool and sends:

JSON
{
  "invoke_type": "resume",
  "tool_result": [{"role": "tool", "content": "24 degrees and sunny"}],
  "config": {"thread_id": "my-thread-1"}
}

tool_result must be non-empty and config.thread_id is required, so the server can resume the right checkpoint.

Both frames also accept initial_state, recursion_limit, and response_granularity with the same meaning as POST /v1/graph/invoke.

Server frames. The server sends StreamChunk JSON messages, identical to the HTTP stream, followed by a terminator:

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

The server does not inspect chunks for tool calls; detecting a remote tool call is the client’s job. invoke_type is used only for validation and logging.

Close codes.

Code Meaning
1000 Normal closure
1008 Auth or authorization rejected, or the graph is a live agent (use /v1/graph/live)
1011 Unexpected server error
1013 Rate limit or websocket.max_connections exceeded

Recoverable problems (an invalid frame, an unauthorized thread_id) are sent as an error StreamChunk and the connection stays open for the next request.


GET /v1/graph:StateSchema

Get the JSON schema for the graph’s state class.

Response:

JSON
{
  "success": true,
  "data": {
    "type": "object",
    "properties": {
      "context": {"type": "array"},
      "user_id": {"type": "string"}
    }
  }
}

Authentication

When auth is configured, all endpoints require an Authorization header:

plaintext
Authorization: Bearer <token>

See Authentication for configuration details.

Last updated for v0.9.2Edit this page on GitHubReport an issue