Overview
This page shows how to chat with a Genesis agent from your own code using the public REST API. The same flow powers the React dashboard, the Python SDK, and any external integration. You will:- Authenticate with a Personal Access Token (PAT) — see Personal Access Tokens.
- Create a thread for an agent.
- Post a message to that thread.
- Read the response synchronously, by polling, or by streaming deltas.
All examples below assume you have a PAT and a base URL. Set them once:
1. Discover available agents
403 from a bot-scoped call later means the user does not
have RBAC access to that bot — ask an admin to grant access in
Users and Roles, or use a different bot.
Reuse the bot_id values from GET /api/bots exactly as returned for later
/api/bots/{bot_id}/... calls. Genesis treats those values as the canonical,
case-sensitive runtime agent identifiers (Eve and eve are different
agents), so clients should not lowercase them or derive alternate IDs from
storage directory names. Thread metadata and storage paths key off the exact
database bot_id.
2. Create a thread
mcp_<32-char-hex>):
POST /api/bots/{bot_id}/threads accepts an optional JSON body with
thread_id (string). When omitted, Genesis generates a thread id. The title is
auto-generated from the first message.
Clients that load the new thread immediately can also see an initial assistant
welcome message. Genesis uses the bot’s bot_intro when it is non-empty;
otherwise it falls back to the generic Hello! I'm {bot_name}, here to help you. How can I assist you today? greeting.
3. Send a message
There are three patterns for getting the agent’s response.3a. Synchronous — one HTTP call
The simplest pattern. Genesis blocks server-side until the response is complete and returns the final payload.
This is the recommended starting point for CI jobs and “ask one question”
scripts. It uses up an HTTP connection for the full generation, so
prefer the polling or delta patterns for very long generations.
When a dashboard or other client is already subscribed to the thread over
WebSocket,
POST .../messages/sync also emits the same message_sent and
new_message notifications as the async submit path so the UI can show the
user prompt and thinking state without a separate REST poll.
3b. Submit + poll — most flexible
POST /api/bots/{bot_id}/threads/{thread_id}/messages— submit; returns amessage_idimmediately.GET /api/bots/{bot_id}/threads/{thread_id}/messages/{message_id}/response— current state.statusis"in_progress"or"finished".responseis the running text (Markdown).
POST /messages body:
files is an optional list of file-manager IDs to attach to the message.
Upload them first via POST /api/files.
Genesis rejects new messages to agents with
status="DISABLED". REST
POST /messages and POST /messages/sync return an error such as
Agent 'Eve' is deactivated and cannot receive direct chats. Reactivate it or choose an active agent. WebSocket clients receive a message_send_error
with error_code="BOT_DEACTIVATED". Deactivated agents are omitted from the
default GET /api/bots list; use include_disabled=true (requires
manage_agents) or reactivate the agent from Deactivated Agents in the
Agents workspace.Both
POST /api/bots/{bot_id}/threads/{thread_id}/messages and
POST /api/bots/{bot_id}/threads/{thread_id}/messages/sync route through the
async completion-loop dispatcher, as does Genesis WebSocket chat, whenever
the agent has a registered WebSocket adapter. The request and response
shapes stay the same. If the async dispatcher fails, Genesis falls back
automatically to the legacy queue; an agent with no registered WebSocket
adapter is served through the synchronous ChatManager path instead. There
is no separate express prompt lane to enable or select. Calls from a second
event loop are forwarded to the dispatcher’s owning loop; if that loop is not
running, the existing legacy-queue fallback applies.A sandbox thread token receives HTTP
403 when it posts to its own thread through
POST /api/bots/{bot_id}/threads/{thread_id}/messages,
/api/bots/{bot_id}/threads/{thread_id}/messages/sync, or
/api/bots/{bot_id}/threads/{thread_id}/messages/websocket. Messages to other threads remain allowed. See Sandbox
Thread Token Controls.3c. Submit + delta — incremental UIs without WebSockets
Use the delta endpoint when you want to render incremental output without WebSockets:GET /api/bots/{bot_id}/threads/{thread_id}/messages/{message_id}/response/delta?last_length=N&timeout=T
returns only the bytes generated since position N, or HTTP 204
(no body) if nothing arrived within T seconds.
3d. WebSocket streaming
The dashboard receives token-by-token streaming and tool-call events through the unified realtime socket at/api/ws/unified_v2. That
protocol is intended for the dashboard and is more involved than the
REST patterns above. For direct request/response frames on that socket,
Genesis echoes request_id in the response data payload, generating one
when the client omits it, and also echoes the request’s bot_id plus any
supplied thread_id. When the client supplies a non-empty request_id on
send_message, Genesis also includes that same value on the terminal
message_processing_complete event so realtime clients can correlate the
end-of-turn signal with the originating request.
For external integrations, prefer 3a–3c. If you do need it, see the
Unified realtime WebSocket
section of the API reference for the full event envelopes and subscription
control frames.
4. End-to-end Python example
5. Working with threads
Once a thread exists you have a full set of management endpoints. Common ones: Concurrent async turns keep their tool and runtime context scoped to the thread receiving each turn. A turn does not inherit another thread’s context because that thread ran more recently on the event loop.GET /api/bots/{bot_id}/threads/{thread_id}/goal returns the following goal
snapshot fields in addition to the standard response fields:
The two limits are configured with
goal_mode.max_nudges and
goal_mode.max_total_nudges; see Goal mode limits.
For declaration behavior and when nudges are sent, see
Thread goal tools.
For request fields, payload variants, and debrief version-history endpoints,
see API Reference — debrief endpoints.
For title updates, PUT /api/bots/{bot_id}/threads/{thread_id}/title now distinguishes invalid input (400), missing threads (404), and retryable thread-scoped write conditions (503) such as an in-flight compression or another thread-scoped write. On 503, retry the same request after a short wait.
POST /api/bots/{bot_id}/threads/{thread_id}/fork accepts optional body fields
title, end_index, end_message_id, and end_node_id. Prefer
end_node_id when forking from a specific message — it is stable across thread
compression, unlike message indices. The response includes
estimated_context_tokens and source_context_tokens when Genesis can
approximate LLM-visible context size. Use
GET /api/bots/{bot_id}/threads/{thread_id}/fork-info on the forked thread to
read forked_from_thread_id, fork_point_preview, and related lineage fields.
For edit-and-resume on prior user messages (not just the last turn), use the
unified WebSocket client message edit_message_and_resume; see
API Reference — edit_message_and_resume.
GET /api/bots/{bot_id}/threads/{thread_id}/activity is the lightest polling
endpoint for live chat state. data.state can be idle, tool_call,
completion, or compression. data.active_run_user_relation classifies
whether the in-flight run was started by the caller (self), another user
(other), or could not be determined (unknown). Poll this field before
interrupting a busy shared thread (for example, by sending a queued follow-up
immediately).
Use POST /api/bots/{bot_id}/threads/{thread_id}/background-work/dismiss to
hide one or all listed runs from the chat card without stopping the work. See
the API Reference for the request and
response fields.
When state="compression",
data.active_compression can include both a human-readable progress_message
and structured progress fields such as progress_pct, progress_phase,
work_done, work_total, summary_done, summary_total, and parallelism
so polling clients can render richer summarization progress while Genesis is
reducing older thread history.
For sharing metadata, GET /api/bots/{bot_id}/threads/{thread_id}/share-info follows the same access rules as viewing the thread itself. Callers who can already access a private thread can read its visibility and owner metadata, while access-list changes still use the stricter write routes.
When auth.enabled is true, sending a prompt to a legacy thread whose stored
owner_id is system_default_user repairs the stored owner to the
authenticated non-system caller. That keeps later my_threads_only listings,
share-info responses, and owner-scoped thread actions aligned with the user
who resumed the thread. When auth.enabled is false, Genesis continues to
use system_default_user as the canonical owner.
GET /messages supports a source query param:
message_cache(default) — processed messages from cache or DB. Genesis refreshes current thread/message metadata on read without invalidating the cached message list for metadata-only updates such as title, checklist, or usage/session metadata changes. It rebuilds that cached snapshot only when message content changes or history compression rewrites the thread.bot_os_thread— full BotOsThread serialized state.thread_json— raw on-disk thread JSON, plus artefacts.message_log—MESSAGE_LOGtable, returned as CSV by default or JSON when the request sendsAccept: application/json.
When stored assistant-message metadata is available,
GET /messages returns
it in each message’s metadata object. Each message may also include
node_id — a stable plist node identifier used as the branch point for
fork-from-message and edit-and-resume operations. LLM usage metadata may include
model, provider, prompt_tokens, completion_tokens, total_tokens,
cache_read_input_tokens, cache_creation_input_tokens, cached_tokens,
provider_request_id, provider_session_id, and execution_origin. Cortex
message metadata can also include finish_reason for a successful reply and
max_output_tokens, the output-token cap sent with the request. Some Cortex
models do not report a finish reason; reaching the reported cap in
completion_tokens is also a signal that the reply may have been truncated.
When a provider returns an empty or otherwise degraded reply, metadata can
include empty_response, response_status, finish_reason,
error_diagnostic, and event_diagnostic.
Classified LLM failures can also include llm_failure with kind, a safe
message, a suggested action, and an error_id correlation ID. The kind is
one of connection_required, authentication, access_denied,
not_configured, rate_limit, quota_exhausted, timeout, unavailable,
context_limit, invalid_request, invalid_response, or unknown. This
public object does not contain the provider’s raw error text.
Genesis strips internal auth/session-only keys such as _sf_caller_token,
_user_context, and _sf_user_info before returning metadata to API
callers. Message-list responses also include sender_provenance for user
prompts; see API Reference — sender provenance for its values and trust rules.
LiteLLM-backed providers normalize cache counters into these standard field
names even when the upstream provider reports them in different shapes.For hosted MCP threads, intentionally recorded direct-tool request and output
messages are included in the returned history rather than being treated as
ordinary assistant tool requests. The dashboard presentation of those
client-owned calls is described in Follow tool activity.
Profile pre-LLM prompt preparation
UsePOST /api/bots/{bot_id}/threads/{thread_id}/profile-pre-llm when you
want to measure prompt assembly on a loaded live thread without sending a model
request. Genesis clones the current thread state, optionally primes caches
once, then measures only the pre-LLM steps: building llm_messages,
appending runtime/system context, and estimating request size.
The response returns
trial_count, per-trial timing data in trials, and
aggregate summary stats for update_llm_messages_ms,
append_system_context_ms, build_runtime_request_snapshot_ms, and
total_prep_ms. The route uses the same agent and thread access checks as the
message endpoints, but it only works against a live loaded thread rather than
an offline archived export.
Chat pre-dispatch cache tuning
Genesis caches context metadata and related skill-resolution work on the WebSocket and REST message hot paths so subsequent turns in the same thread avoid repeated worker RPCs and database reads. Cache keys fold in bot/thread context epochs and capability-registry stamps, so mutations invalidate stale entries without waiting for TTL expiry.
Use
POST /api/bots/{bot_id}/threads/{thread_id}/profile-pre-llm to compare
cold versus warm prompt-preparation timings after changing these values.
Generate a provider request export
UseGET /api/bots/{bot_id}/threads/{thread_id}/messages/{message_id}/provider-request-export
when you want a provider-ready request body for a specific turn without digging
through runtime debug logs. Genesis reconstructs the request from stored thread
history plus the thread’s current provider/model configuration, then returns a
sanitized endpoint_url, structured request_json, and a copyable curl
command.
data.simulated flag is always true: Genesis generates this
export from the thread transcript instead of replaying a captured upstream HTTP
request. The exported messages array includes the conversation prefix up to
the selected turn. If the supplied message_id no longer matches a stored
message, Genesis falls back to the latest user turn in that thread.
For Claude-compatible prompt-cached requests, Genesis preserves
cache_control markers on the exported messages[*].content[] blocks so the
copied JSON and curl payload stay usable in Postman or another
OpenAI-compatible client. In checkpoint mode with the default rolling anchor,
the deep breakpoint is on stable history behind the newest user turn (which
carries per-turn date/time context). See Setup AI Model
for ANTHROPIC_CACHE_CHECKPOINT_ROLLING_ANCHOR.6. Files and uploads
To send a file along with a message:-
Upload it to the file manager:
The response contains a
file_id. -
Pass the
file_id(or several) in thefilesarray on thePOST /messagesbody.
uploads repository.
Uploaded files also work for blueprint install workflows. After
POST /api/files, attach the returned file_id to a message for Eve such as:
7. Common pitfalls
403from/api/bots/{bot_id}/...— the user does not have RBAC access to that bot. Update agent access in Users and Roles, or use a bot you do own.401from any chat call — the PAT is missing, malformed, or expired. Check theAuthorization: Bearer gp_...header.Bot not found or unavailableon a fresh deployment — Eve / Adam need an LLM backend. Configure one in Setup AI Model before chatting.- Sync call returns
508— the response did not finish within thetimeoutwindow. Either raise the timeout, switch to delta polling, or callGET …/messages/{message_id}/responseto fetch whatever the server has so far. - A thread stays stuck in a loading or streaming state after work was interrupted. Call
POST /api/bots/{bot_id}/threads/{thread_id}/force-resetto clear stuck in-memory work without deleting the thread or its messages. When Genesis clears state, it also emits the standard terminal realtime events so dashboard and other realtime clients can leave the stuck state without reopening the thread. 413 Payload Too Large— the outbound message exceededthreads.max_message_size_bytes(default16384bytes). Trim the prompt or attach long content as a file.

