Skip to main content

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:
  1. Authenticate with a Personal Access Token (PAT) — see Personal Access Tokens.
  2. Create a thread for an agent.
  3. Post a message to that thread.
  4. Read the response synchronously, by polling, or by streaming deltas.
For low-level field-by-field schemas, see the API Reference — chat thread access endpoints section.
All examples below assume you have a PAT and a base URL. Set them once:

1. Discover available agents

The response includes one entry per agent the authenticated user has access to. A 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

Optional body to supply a caller-chosen id (hosted MCP v2 uses ids such as 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

Endpoints:
  • POST /api/bots/{bot_id}/threads/{thread_id}/messages — submit; returns a message_id immediately.
  • GET /api/bots/{bot_id}/threads/{thread_id}/messages/{message_id}/response — current state. status is "in_progress" or "finished". response is 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_LOG table, returned as CSV by default or JSON when the request sends Accept: 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.
For the full schemas, see Chat thread list endpoint and Chat thread access endpoints.

Profile pre-LLM prompt preparation

Use POST /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

Use GET /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.
The returned 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:
  1. Upload it to the file manager:
    The response contains a file_id.
  2. Pass the file_id (or several) in the files array on the POST /messages body.
The agent receives the file through its file-aware tools (for example the Document Index tools or file-reading tools) and can refer to it in follow-up turns. File IDs do not bypass conversation access: file-list, read, download, and delete requests expose chat uploads only when the caller may open the conversation that contains the file. The same scope applies to files reached through the shared 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:
Eve can install the uploaded blueprint into the default local Blueprint Library or directly into a managed blueprint repo. If you later decide a local install belongs in a managed repo, ask Eve to promote the existing blueprint instead of uploading the ZIP again.

7. Common pitfalls

  • 403 from /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.
  • 401 from any chat call — the PAT is missing, malformed, or expired. Check the Authorization: Bearer gp_... header.
  • Bot not found or unavailable on 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 the timeout window. Either raise the timeout, switch to delta polling, or call GET …/messages/{message_id}/response to 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-reset to 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 exceeded threads.max_message_size_bytes (default 16384 bytes). Trim the prompt or attach long content as a file.