> ## Documentation Index
> Fetch the complete documentation index at: https://docs.genesiscomputing.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Chat with Agents over REST

> Drive a Genesis agent end-to-end over plain HTTP — create threads, send messages, and read responses synchronously, by polling, or by deltas.

## 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](/docs/User-Guide/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](/docs/Genesis-API/API-Reference#chat-thread-access-endpoints)
section.

<Note>
  All examples below assume you have a PAT and a base URL. Set them once:

  ```bash theme={null}
  export GENESIS_URL="https://<your-genesis-host>"
  export GENESIS_PAT="gp_replace-me"
  AUTH=(-H "Authorization: Bearer $GENESIS_PAT")
  ```
</Note>

## 1. Discover available agents

```bash theme={null}
curl -sS "${AUTH[@]}" "$GENESIS_URL/api/bots"
```

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](/docs/Setup/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

```bash theme={null}
curl -sS -X POST "${AUTH[@]}" -H "Content-Type: application/json" \
  "$GENESIS_URL/api/bots/Eve/threads"
```

Optional body to supply a caller-chosen id (hosted MCP v2 uses ids such as
`mcp_<32-char-hex>`):

```bash theme={null}
curl -sS -X POST "${AUTH[@]}" -H "Content-Type: application/json" \
  -d '{"thread_id": "mcp_a1b2c3d4e5f6789012345678abcdef01"}' \
  "$GENESIS_URL/api/bots/Eve/threads"
```

```json theme={null}
{
  "success": true,
  "thread_id": "chat_3f4c…",
  "title": "New chat"
}
```

`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.

```bash theme={null}
THREAD_ID="chat_3f4c…"

curl -sS -X POST "${AUTH[@]}" -H "Content-Type: application/json" \
  -d '{"message": "What datasets do we have?"}' \
  "$GENESIS_URL/api/bots/Eve/threads/$THREAD_ID/messages/sync?timeout=120"
```

| Param | Default | Notes |
| - | - | - |
| `timeout` | `300` (seconds) | On timeout the call returns HTTP `508` with any partial response that was generated. |

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

```bash theme={null}
RESP=$(curl -sS -X POST "${AUTH[@]}" -H "Content-Type: application/json" \
  -d '{"message": "What datasets do we have?"}' \
  "$GENESIS_URL/api/bots/Eve/threads/$THREAD_ID/messages")
MSG_ID=$(echo "$RESP" | jq -r .message_id)

while :; do
  R=$(curl -sS "${AUTH[@]}" \
    "$GENESIS_URL/api/bots/Eve/threads/$THREAD_ID/messages/$MSG_ID/response")
  STATUS=$(echo "$R" | jq -r .status)
  if [ "$STATUS" = "finished" ]; then
    echo "$R" | jq -r .response
    break
  fi
  sleep 1
done
```

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:

```json theme={null}
{
  "message": "string",
  "files": ["file_id_1", "file_id_2"]
}
```

`files` is an optional list of file-manager IDs to attach to the message.
Upload them first via `POST /api/files`.

<Note>
  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.
</Note>

<Note>
  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.
</Note>

<Note>
  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](/docs/Security-Controls/Genesis-Agent-Security-Controls#sandbox-thread-token-controls).
</Note>

### 3c. Submit + delta — incremental UIs without WebSockets

Use the **delta** endpoint when you want to render incremental output
without WebSockets:

```bash theme={null}
LAST=0
while :; do
  R=$(curl -sS "${AUTH[@]}" \
    "$GENESIS_URL/api/bots/Eve/threads/$THREAD_ID/messages/$MSG_ID/response/delta?last_length=$LAST&timeout=10")
  # HTTP 204 = no new content within timeout; just call again.
  if [ -z "$R" ]; then continue; fi
  echo "$R" | jq -r .delta
  LAST=$(echo "$R" | jq -r .total_length)
  STATUS=$(echo "$R" | jq -r .status)
  [ "$STATUS" = "finished" ] && break
done
```

`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](/docs/Genesis-API/API-Reference#unified-realtime-websocket)
section of the API reference for the full event envelopes and subscription
control frames.

## 4. End-to-end Python example

```python theme={null}
import os, time, requests

BASE = os.environ["GENESIS_URL"].rstrip("/")
HEADERS = {"Authorization": f"Bearer {os.environ['GENESIS_PAT']}"}

bots = requests.get(f"{BASE}/api/bots", headers=HEADERS, timeout=10).json()["data"]
bot_id = "Eve" if any(b["bot_id"] == "Eve" for b in bots) else bots[0]["bot_id"]

# Create a thread.
thread = requests.post(
    f"{BASE}/api/bots/{bot_id}/threads",
    headers=HEADERS, json={}, timeout=10,
).json()
thread_id = thread["thread_id"]

# Send a message synchronously and wait for the final response.
final = requests.post(
    f"{BASE}/api/bots/{bot_id}/threads/{thread_id}/messages/sync",
    headers=HEADERS,
    json={"message": "List the data sources you can query."},
    params={"timeout": 120},
    timeout=130,
).json()

print(final["response"])
```

## 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.

| Operation | Endpoint |
| - | - |
| List threads for a bot | `GET /api/bots/{bot_id}/threads` |
| Search inside one bot's threads | `POST /api/bots/{bot_id}/threads/search` |
| Search across many bots | `POST /api/threads/search` |
| Get messages in a thread | `GET /api/bots/{bot_id}/threads/{thread_id}/messages` |
| Read the thread's goal and nudge state | `GET /api/bots/{bot_id}/threads/{thread_id}/goal` |
| Inspect current thread activity | `GET /api/bots/{bot_id}/threads/{thread_id}/activity` |
| Hide listed background work | `POST /api/bots/{bot_id}/threads/{thread_id}/background-work/dismiss` |
| Get / set the thread title | `GET`/`PUT /api/bots/{bot_id}/threads/{thread_id}/title` |
| Hide / unhide / delete | `PUT …/hide`, `PUT …/unhide`, `DELETE …` |
| Pin / unpin | `PUT …/pin`, `PUT …/unpin` |
| Fork a thread | `POST /api/bots/{bot_id}/threads/{thread_id}/fork` |
| Get fork lineage for a forked thread | `GET /api/bots/{bot_id}/threads/{thread_id}/fork-info` |
| Generate a saved debrief for a thread, mission, or journey | `POST /api/debriefs` |
| List saved debriefs for a thread | `GET /api/threads/{thread_id}/debriefs?bot_id=<bot_id>` |
| List saved debriefs for a mission | `GET /api/missions/{mission_id}/debriefs` |
| Recover a stuck thread | `POST /api/bots/{bot_id}/threads/{thread_id}/force-reset` |
| Profile prompt preparation on a live thread | `POST /api/bots/{bot_id}/threads/{thread_id}/profile-pre-llm` |
| Generate a provider request export for one turn | `GET /api/bots/{bot_id}/threads/{thread_id}/messages/{message_id}/provider-request-export` |
| Export thread (Markdown / PDF / ZIP) | `POST /api/bots/{bot_id}/threads/{thread_id}/export` |
| Import thread from ZIP export | `POST /api/bots/{bot_id}/threads/import-zip` |
| Import thread from JSON export | `POST /api/bots/{bot_id}/threads/import` |
| Sharing / access | `…/share-info`, `…/access`, `…/access/invite`, `…/access/remove` |

`GET /api/bots/{bot_id}/threads/{thread_id}/goal` returns the following goal
snapshot fields in addition to the standard response fields:

| Field | Type | Description |
| - | - | - |
| `has_goal` | boolean | Whether the thread is pursuing a goal. When false, goal details are empty. |
| `goal_id`, `statement`, `finish_line_scope` | string or null | Goal identifier and statement; `finish_line_scope` describes a narrower scope such as one blueprint phase. |
| `is_final_phase` | boolean or null | Whether `finish_line_scope` names the final phase: true means no further phase follows, false means another phase follows, and null means this is not knowable at entry. For a phase that can end with either `@END` or a further phase, the value is decided at that phase's exit. |
| `status`, `status_reason` | string or null | Goal lifecycle status and why it reached that status. `paused` means nothing is nudging the thread on because the agent is waiting on someone, a person paused it, or the last turn was the person's own; `status_reason` distinguishes the cause. |
| `verdict`, `blocked_on` | string or null | Goal outcome and any action a person must take for a blocked goal. |
| `progress_summary` | string or null | Latest progress report from the agent. |
| `questions_for_person` | array of strings | Questions queued for a person while the agent carries on. Questions do not pause or end the goal; an empty array means none are queued. |
| `started_at_epoch` | number or null | Epoch-second timestamp for when work on the goal started. |
| `criteria` | array | Finish-line items, each with `id`, `text`, and agent-attested `status` (`met`, `unmet`, `failed`, or `blocked`). |
| `nudges`, `nudges_sent` | object or null | Each includes integer `used`, `limit`, and `left`. `nudges` counts since progress; `nudges_sent` counts the lifetime total. |
| `nudge_budget_spent` | boolean | True when either nudge limit stops further nudging; the goal remains open. |
| `declaration_recorded` | boolean | Whether the current turn's status declaration has been recorded. |

The two limits are configured with `goal_mode.max_nudges` and
`goal_mode.max_total_nudges`; see [Goal mode limits](/docs/Setup/Advanced-Settings#goal-mode-limits).
For declaration behavior and when nudges are sent, see
[Thread goal tools](/docs/Genbot-Toolkit/Additional-Tool-Groups#thread-goal-tools).

For request fields, payload variants, and debrief version-history endpoints,
see [API Reference — debrief endpoints](/docs/Genesis-API/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`](/docs/Genesis-API/API-Reference#edit_message_and_resume-client-message).

`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](/docs/Genesis-API/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`.

<Note>
  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](/docs/Genesis-API/API-Reference#chat-thread-access-endpoints) 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.
</Note>

<Note>
  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](/docs/User-Guide/Chat-And-Threads#follow-tool-activity).
</Note>

For the full schemas, see
[Chat thread list endpoint](/docs/Genesis-API/API-Reference#chat-thread-list-endpoint)
and
[Chat thread access endpoints](/docs/Genesis-API/API-Reference#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.

```bash theme={null}
curl -sS -X POST "${AUTH[@]}" -H "Content-Type: application/json" \
  -d '{
    "repeats": 3,
    "execution_lane": "async",
    "append_input_message": true,
    "message_text": "Profile this next turn",
    "warm_caches": true
  }' \
  "$GENESIS_URL/api/bots/Eve/threads/$THREAD_ID/profile-pre-llm"
```

| Field | Default | Notes |
| - | - | - |
| `repeats` | `3` | Number of measured trials. Allowed range: `1` to `10`. |
| `execution_lane` | `async` | Prompt dispatch lane to simulate during prompt preparation. The `express` lane is no longer available. |
| `append_input_message` | `false` | When `true`, Genesis includes `message_text` as a synthetic next user turn before profiling. |
| `message_text` | `profile-pre-llm` | Probe text used only when `append_input_message=true`. Blank values normalize back to the default. |
| `warm_caches` | `true` | When `true`, Genesis runs one unreported warm-up pass before the measured trials. |

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.

| Environment variable | Default | Purpose |
| - | - | - |
| `CONTEXT_TOOLS_METADATA_CACHE_TTL_S` | `3600` | Maximum residency for the resolved context-metadata cache (seconds). Minimum effective value: `1`. |
| `GENESIS_CONTEXT_VERSIONS_CACHE_TTL_S` | `5` | Short-lived per-thread cache for context-version tuples read multiple times during one turn. Set to `0` to disable. |
| `GENESIS_BOT_SKILL_SUMMARIES_CACHE_TTL_S` | `3600` | Per-bot cache for `list_bot_skill_summaries` results on the unresolved-skill path. Set to `0` to disable. Skill-assignment mutations invalidate this cache explicitly. |
| `GENESIS_LIGHTWEIGHT_SKILL_SUMMARIES_CACHE_TTL_S` | `3600` | Catalog-level cache for lightweight skill summaries. Set to `0` to disable. Catalog mutations call `invalidate_lightweight_skill_summaries_cache()` for correctness. |

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.

```bash theme={null}
curl -sS "${AUTH[@]}" \
  "$GENESIS_URL/api/bots/Eve/threads/$THREAD_ID/messages/$MSG_ID/provider-request-export" \
  | jq
```

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.

<Note>
  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](/docs/Setup/Setup-AI-Model)
  for `ANTHROPIC_CACHE_CHECKPOINT_ROLLING_ANCHOR`.
</Note>

## 6. Files and uploads

To send a file along with a message:

1. Upload it to the file manager:

   ```bash theme={null}
   curl -sS -X POST "${AUTH[@]}" \
     -F "file=@./report.pdf" \
     "$GENESIS_URL/api/files"
   ```

   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](/docs/Genbot-Toolkit/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:

```bash theme={null}
curl -sS -X POST "${AUTH[@]}" -H "Content-Type: application/json" \
  -d '{
        "message": "Install the uploaded blueprint ZIP as customer_onboarding in the managed repo genesis_blueprints.",
        "files": ["file_id_1"]
      }' \
  "$GENESIS_URL/api/bots/Eve/threads/$THREAD_ID/messages/sync?timeout=300"
```

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](/docs/Setup/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](/docs/Setup/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.

## Related pages

* [Personal Access Tokens](/docs/User-Guide/Personal-Access-Tokens)
* [Call any tool with `tool_call`](/docs/Genesis-API/Tool-Call-Endpoint)
* [Manage document indexes over REST](/docs/Genesis-API/Document-Index-Endpoints)
* [Chat and Threads (UI guide)](/docs/User-Guide/Chat-And-Threads)
* [API Reference — chat thread access endpoints](/docs/Genesis-API/API-Reference#chat-thread-access-endpoints)
