Invoke and stream over REST

In shortCall your graph over HTTP: synchronous invoke, streaming with NDJSON, thread management, stop and fix operations.

  • 7 min read
  • 9 sections
  • Updated
  • v0.10.0
  • Markdown

The 10xGraph API server exposes your graph over HTTP, allowing you to invoke it synchronously, stream results in real time, manage thread-scoped state, and control running executions. This page covers the REST interface for graph execution.

Invoke and wait for results

Use POST /v1/graph/invoke to call the graph and wait for the final result. The server processes all nodes and returns the complete output when done.

Terminal
curl -X POST http://localhost:8000/v1/graph/invoke \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": [{"type": "text", "text": "What is the capital of France?"}]
      }
    ]
  }'

Message content is a list of content blocks, not a plain string. The response wraps the graph output in a data envelope with request metadata:

JSON
{
  "data": {
    "messages": [
      {
        "message_id": "msg-1",
        "role": "assistant",
        "content": [{"type": "text", "text": "The capital of France is Paris."}]
      }
    ],
    "state": null,
    "context": null,
    "summary": null,
    "meta": {
      "thread_id": "conv-xyz",
      "is_new_thread": true
    }
  },
  "metadata": {
    "request_id": "abc123",
    "message": "OK"
  }
}

Each message also carries fields such as timestamp, usages and metadata; they are trimmed here.

Request parameters

The request body is a JSON object with these fields:

Field Type Default Description
messages array [] List of message objects to send to the graph. At least one message is required unless resume is provided.
config object {} Optional configuration for the run, such as thread_id for continuing a conversation.
initial_state object null Optional initial state for the graph execution.
response_granularity string low How much to return: low, partial or full. state in the response is filled only at full.
recursion_limit integer 25 Maximum depth of graph execution (1-100). Raise this if your graph has deep loops.
resume any null Set this to resume a paused thread (see human-in-the-loop section below).

Response fields

The data object has these fields:

Field Type Description
messages array Final messages from the graph after all processing.
state object or null The graph’s final state. Returned only with response_granularity: "full".
context array or null Context messages, when the graph produces them.
summary string or null A summary, when the graph produces one.
meta object Metadata such as thread_id and is_new_thread.

Stream results in real time

Use POST /v1/graph/stream to get results as they are produced. The response body is NDJSON (newline-delimited JSON), one chunk per line. The Content-Type header is text/event-stream, but lines are plain JSON, not data: frames.

Terminal
curl -X POST http://localhost:8000/v1/graph/stream \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [
      {
        "role": "user",
        "content": [{"type": "text", "text": "Generate a short story"}]
      }
    ]
  }'

The stream emits chunks as the graph runs (fields trimmed for readability):

JSON
{"event":"updates","data":{"status":"invoking_graph","step":0},"thread_id":"thread-abc123","run_id":"run-123"}
{"event":"message","message":{"role":"assistant","delta":true,"content":[{"type":"text","text":"Once upon"}]},"thread_id":"thread-abc123","run_id":"run-123"}
{"event":"message","message":{"role":"assistant","delta":true,"content":[{"type":"text","text":" a time..."}]},"thread_id":"thread-abc123","run_id":"run-123"}
{"event":"updates","data":{"status":"graph_invoked","reason":"Graph execution completed successfully"},"thread_id":"thread-abc123","run_id":"run-123"}

Each line is a separate JSON object (a StreamChunk). Your client must parse each line independently. Message chunks carry the message in the message field, and delta is true for partial text. Chunks also carry metadata and timestamp. When the graph runs to completion, the last chunk is an updates chunk; if you have a thread name generator configured, a final updates chunk with "status": "completed" follows.

Stream event types

The event field in each chunk is one of four values:

Event When emitted Where the content is
message The graph produces a message or a partial message message field (a message object; delta marks partial text).
updates Progress and lifecycle changes data.status, for example invoking_graph, invoking_node, graph_invoked, interrupted, plus node, step and reason.
state The graph emits its state state field.
error An error occurred data.reason (a sanitized message in production).

A stop request ends the stream with an updates chunk whose data.reason is Graph execution stopped by request.

Use threads for conversation memory

A thread is a persistent conversation context. Use thread_id in the config to keep messages and state across multiple calls, so your graph can remember previous exchanges.

Start a new thread

If you do not provide a thread_id, the server generates one:

Terminal
curl -X POST http://localhost:8000/v1/graph/invoke \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [{"role":"user","content":[{"type":"text","text":"Hello"}]}],
    "config":{}
  }'

The response includes data.meta.thread_id:

JSON
{
  "data": {
    "messages": [...],
    "meta": {
      "thread_id": "thread-abc123"
    }
  },
  "metadata": {"request_id": "abc123", "message": "OK"}
}

Continue on the same thread

Pass the thread_id in the next request to continue the conversation:

Terminal
curl -X POST http://localhost:8000/v1/graph/invoke \
  -H "Content-Type: application/json" \
  -d '{
    "messages": [{"role":"user","content":[{"type":"text","text":"What did I say before?"}]}],
    "config":{
      "thread_id":"thread-abc123"
    }
  }'

The graph now sees all previous messages in the thread, so it can answer contextually. The checkpointer (configured in 10xgraph.json) stores the thread state durably.

Read thread history

To see all messages in a thread without running the graph, use GET /v1/threads/{thread_id}/messages:

Terminal
curl http://localhost:8000/v1/threads/thread-abc123/messages

Returns the thread messages:

JSON
{
  "data": {
    "messages": [
      {"role":"user","content":[{"type":"text","text":"Hello"}]},
      {"role":"assistant","content":[{"type":"text","text":"Hi there"}]}
    ]
  },
  "metadata": {"request_id": "abc123", "message": "OK"}
}

The endpoint also accepts search, offset and limit query parameters.

Stop a running execution

If a graph is running and you want to halt it, POST to /v1/graph/stop with the thread_id:

Terminal
curl -X POST http://localhost:8000/v1/graph/stop \
  -H "Content-Type: application/json" \
  -d '{
    "thread_id":"thread-abc123"
  }'

Stop is a request flag. The server marks the thread, and the running graph checks the flag between nodes and then exits. The response reports whether a run was active:

JSON
{
  "data": {"ok": true, "running": true},
  "metadata": {"request_id": "abc123", "message": "OK"}
}

If nothing is running, running is false (or ok is false with a reason such as no-state or no-checkpointer). A stopped graph exits cleanly and the checkpoint is saved. You can continue the thread later by sending a new invoke or stream request with the same thread_id.

Fix corrupted graph state

If a graph execution is interrupted or fails mid-tool-call, the checkpoint may contain tool-call messages with empty content. Use POST /v1/graph/fix to clean these up:

Terminal
curl -X POST http://localhost:8000/v1/graph/fix \
  -H "Content-Type: application/json" \
  -d '{
    "thread_id":"thread-abc123"
  }'

The response shows how many messages were removed. state is the updated state serialized as a JSON string.

JSON
{
  "data": {
    "success": true,
    "message": "Successfully removed 2 message(s)",
    "removed_count": 2,
    "state": "..."
  },
  "metadata": {"request_id": "abc123", "message": "OK"}
}

After fixing, the thread is safe to resume. Empty tool calls typically occur when:

  • A tool was called but the client disconnected before sending the result.
  • An interrupt happened while a tool was running.
  • A network error cut off the tool response.

Human-in-the-loop: pause and resume

If your graph calls interrupt() to pause for human approval, the stream ends with an updates chunk whose data.status is interrupted, with the node that paused and the interrupt payload. To resume, send a resume request with your approval:

Terminal
curl -X POST http://localhost:8000/v1/graph/invoke \
  -H "Content-Type: application/json" \
  -d '{
    "resume":{"approved":true,"comment":"Looks good"},
    "config":{
      "thread_id":"thread-abc123"
    }
  }'

The graph resumes from the paused node with your response. The resume value is passed directly to the interrupt() call, so shape it however your node expects.

Error handling

If an error occurs during execution, the response will include an error chunk:

JSON
{"event":"error","data":{"reason":"Tool call failed"},"run_id":"run-123"}

Failures during a stream arrive as an error chunk, because the HTTP status is already 200 once streaming starts. Authentication, authorization and validation errors are returned before the stream begins. For synchronous invoke, the call fails with an HTTP error status:

Status Meaning
422 Invalid request (bad schema, missing messages, or invalid input).
401 Authentication required (token missing or invalid).
403 Permission denied (you do not own this thread, or a required scope is missing).
429 Rate limit exceeded. Retry after the Retry-After header.
500 Internal server error (bug in your graph or infrastructure failure).

Common issues

Validation error: “messages must contain at least one message” (422)

You sent an empty messages array. Include at least one message, or use resume to continue an interrupted thread.

403 on a thread you expect to own

With "authorization": "ownership" a thread belongs to the user who created it. Check that the token carries the same user_id as the original request.

Stream stops abruptly

Your graph may have crashed or the server restarted. Check server logs and the thread checkpoint to diagnose. Use /v1/graph/fix if needed.

Tool call returns empty result

The client may not have sent the tool result back to the server. Resend it with invoke or stream and use the same thread_id to let the graph retry.

Next steps

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