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

# Send a message

> The core call: POST /v1/responses on your instance URL runs a turn through your agent, streams or returns the result, and continues the thread.

`POST /v1/responses` is the core call. You make it against your instance, not against `api.agent37.com`: every instance serves its own chat API at `https://{instanceId}.agent37.app`, the `url` of the default port in the create response. This page uses `https://ab12cd34ef.agent37.app`. Authenticate with the same `sk_live_` key you use on the hosting API, sent as the `X-Agent37-Key` header.

The call is agentic by default: the agent can browse, run code, use a terminal, read and write files, call connected tools, and reason across many steps before answering.

## Request body

Request bodies are capped at 2 MB; anything larger returns `413 payload_too_large`.

<ParamField body="input" type="string" required>
  The message or task, a plain string. There is no image field; to attach files, upload them first and list their paths in `files`. See [Sessions and models](/docs/agents-api/sessions) for how history carries across turns.
</ParamField>

<ParamField body="files" type="string[]">
  Paths of files on the instance to attach to this turn, typically the `path` returned by [`PUT /v1/files/content`](/docs/agents-api/files). Each must name an existing file on the instance, or the call returns `400 validation_error`. The paths are appended to the input, and the agent reads them from disk.
</ParamField>

<ParamField body="session_id" type="string">
  Continue an existing conversation. Omit it to start a new one; the response returns the new session's id. The harness owns sessions and creates one on first use, so an id it has not seen simply starts a fresh thread under that id rather than erroring.
</ParamField>

<ParamField body="stream" type="boolean" default="false">
  `true` returns a Server-Sent Events stream; `false` returns the finished response as one JSON body. See [Streaming](/docs/agents-api/streaming).
</ParamField>

<ParamField body="model" type="string">
  The LLM to run this turn on. Omit it to use the session's current model (the instance default on a new session). List what the instance can run with `GET /v1/models`; see [Sessions and models](/docs/agents-api/sessions).
</ParamField>

<ParamField body="provider" type="string">
  The model's provider, for example `anthropic`. Both `model` and `provider` are set per turn, and sending them on a continuation updates the session's stored pair for the turns that follow.
</ParamField>

<ParamField body="reasoning_effort" type="string">
  How hard the model thinks: `none`, `minimal`, `low`, `medium`, `high`, or `xhigh`.
</ParamField>

<ParamField body="metadata" type="object">
  Up to 16 key/value pairs, at most 64 KB serialized. Echoed back on the response object, never interpreted.
</ParamField>

<ParamField body="agent" type="string">
  Which agent harness runs the turn, `hermes` or `openclaw`. Omit it to use the instance's configured default (`hermes` on `agent37-hermes`). Routing is per request: the gateway keeps no session-to-agent binding, so if a session runs on a non-default harness, send `agent` on every turn of it. Targeting a harness the instance was not provisioned with returns `503 agent_unavailable`.
</ParamField>

<ParamField body="mode" type="string" default="chat">
  `chat` runs one turn and replies. `goal` is reserved: sending it returns `400 validation_error` today.
</ParamField>

<Note>
  `instance_id` in the body is accepted and ignored. The URL names the instance: one gateway per instance, so there is nothing to route.
</Note>

## Response

The response object. Ids are 32-character hex strings; timestamps from the gateway are epoch milliseconds.

<ResponseField name="id" type="string">
  The response id. Use it to reconnect or cancel the turn.
</ResponseField>

<ResponseField name="session_id" type="string">
  The conversation this turn belongs to. Reuse it on the next call to continue the thread.
</ResponseField>

<ResponseField name="status" type="string">
  `in_progress`, then a terminal `completed`, `failed`, or `cancelled`.
</ResponseField>

<ResponseField name="agent" type="string">
  The agent that ran the turn, `hermes` or `openclaw`.
</ResponseField>

<ResponseField name="model" type="string | null">
  The model the turn ran on, `null` when none was set.
</ResponseField>

<ResponseField name="provider" type="string | null">
  The model's provider, `null` when none was set.
</ResponseField>

<ResponseField name="output_text" type="string">
  The agent's final answer. Always a string, empty if the turn produced none.
</ResponseField>

<ResponseField name="usage" type="object | null">
  Token counts and cost for the turn: `{ input_tokens, output_tokens, cost_usd }`. `cost_usd` is absent or `null` when the provider did not report a cost.
</ResponseField>

<ResponseField name="error" type="object | null">
  Set when the turn failed: `{ code, message, param?, hint? }`. See [Errors](/docs/agents-api/errors).
</ResponseField>

<ResponseField name="metadata" type="object | null">
  Your request metadata, echoed back verbatim.
</ResponseField>

<ResponseField name="created" type="number">
  When the turn started, epoch milliseconds.
</ResponseField>

<Note>
  A failed turn does not reject the HTTP call. The POST still returns 200 with `status: "failed"` and `error` set. Branch on `status`, not on the HTTP code.
</Note>

<Note>
  On a non-streaming call the gateway sends the `200` and headers as soon as the turn starts, then keeps the connection alive with a whitespace tick every 25 seconds while the agent works. The JSON body arrives when the turn finishes, prefixed by that whitespace — leading whitespace is valid JSON, so standard parsers handle it unchanged. Don't treat the early headers as the response being ready.
</Note>

## Example

<CodeGroup>
  ```bash curl theme={null}
  curl https://ab12cd34ef.agent37.app/v1/responses \
    -H "X-Agent37-Key: sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "input": "Research the top 3 EV makers, write a memo."
    }'
  ```

  ```python python theme={null}
  import requests

  r = requests.post(
      "https://ab12cd34ef.agent37.app/v1/responses",
      headers={
          "X-Agent37-Key": "sk_live_...",
          "Content-Type": "application/json",
      },
      json={"input": "Research the top 3 EV makers, write a memo."},
  ).json()
  ```

  ```javascript node theme={null}
  const r = await (await fetch("https://ab12cd34ef.agent37.app/v1/responses", {
    method: "POST",
    headers: {
      "X-Agent37-Key": "sk_live_...",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      input: "Research the top 3 EV makers, write a memo.",
    }),
  })).json();
  ```

  ```json response theme={null}
  {
    "id": "c91d2a7e84f04b6f9a3d5e1c0b87f4a2",
    "session_id": "7f3e0b6c52a949d2b1c4a8e9d0f31726",
    "status": "completed",
    "agent": "hermes",
    "model": null,
    "provider": null,
    "output_text": "Memo: the top 3 EV makers...",
    "usage": { "input_tokens": 1840, "output_tokens": 920, "cost_usd": 0.0137 },
    "error": null,
    "metadata": null,
    "created": 1781136000000
  }
  ```
</CodeGroup>

<Tip>
  Set `stream: true` to receive Server-Sent Events as the agent reasons, calls tools, and writes its answer. The terminal event carries the final `output_text` and `usage`. See [Streaming](/docs/agents-api/streaming).
</Tip>

## Continue a conversation

The first message omits `session_id` and starts a session. The reply returns a `session_id`; pass it on the next message to continue the same thread. The session holds the full history, so you never resend a transcript: you send only the new input.

<CodeGroup>
  ```bash curl theme={null}
  # 1. start a session: omit session_id
  curl https://ab12cd34ef.agent37.app/v1/responses \
    -H "X-Agent37-Key: sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "input": "Research the top 3 EV makers, write a memo."
    }'
  # -> { "id": "c91d2a7e84f04b6f9a3d5e1c0b87f4a2",
  #      "session_id": "7f3e0b6c52a949d2b1c4a8e9d0f31726",
  #      "status": "completed", ... }

  # 2. continue it: reuse the session_id, send only the new input
  curl https://ab12cd34ef.agent37.app/v1/responses \
    -H "X-Agent37-Key: sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{
      "session_id": "7f3e0b6c52a949d2b1c4a8e9d0f31726",
      "input": "Make it shorter, add a quote."
    }'
  ```

  ```python python theme={null}
  import requests

  BASE = "https://ab12cd34ef.agent37.app"
  H = {"X-Agent37-Key": "sk_live_...", "Content-Type": "application/json"}

  # 1. start a session: omit session_id
  first = requests.post(
      f"{BASE}/v1/responses",
      headers=H,
      json={"input": "Research the top 3 EV makers, write a memo."},
  ).json()

  # 2. continue it: reuse the session_id, send only the new input
  requests.post(
      f"{BASE}/v1/responses",
      headers=H,
      json={
          "session_id": first["session_id"],
          "input": "Make it shorter, add a quote.",
      },
  )
  ```

  ```javascript node theme={null}
  const BASE = "https://ab12cd34ef.agent37.app";
  const H = {
    "X-Agent37-Key": "sk_live_...",
    "Content-Type": "application/json",
  };

  // 1. start a session: omit session_id
  const first = await (await fetch(`${BASE}/v1/responses`, {
    method: "POST",
    headers: H,
    body: JSON.stringify({
      input: "Research the top 3 EV makers, write a memo.",
    }),
  })).json();

  // 2. continue it: reuse the session_id, send only the new input
  await fetch(`${BASE}/v1/responses`, {
    method: "POST",
    headers: H,
    body: JSON.stringify({
      session_id: first.session_id,
      input: "Make it shorter, add a quote.",
    }),
  });
  ```
</CodeGroup>

<Note>
  **One active turn per session.** A session runs one response at a time. Sending new input while one is in flight returns `409 session_busy`, normally with the running response's id in `error.response_id`. Use another session, reattach to the running turn, or cancel it first.
</Note>

To list a user's threads, read a thread's history, delete one, or pick a model, see [Sessions and models](/docs/agents-api/sessions).

## Follow up on a response

Every response has an id you can use after the call returns.

| Method | Path                        | Returns                                                     |
| ------ | --------------------------- | ----------------------------------------------------------- |
| `GET`  | `/v1/responses/{id}/stream` | `200` an SSE stream: replays all events, then attaches live |
| `POST` | `/v1/responses/{id}/cancel` | `200` the current response object                           |

`GET /v1/responses/{id}/stream` replays every event so far in order, then stays attached live, so a dropped connection never loses the answer — including after the turn has finished, while the response is still retained. See [Streaming](/docs/agents-api/streaming) for the replay window. Lost the id (page reload, new device)? [`GET /v1/sessions/{id}`](/docs/agents-api/sessions#retrieve-a-session-with-history) returns the running response as `active_response_id`.

`POST /v1/responses/{id}/cancel` takes no body and stops a running turn, best effort. It returns 200 with the current response object. Cancelling a finished response is a no-op that returns its terminal state, still 200.

<Warning>
  Cancel does not rewind. Whatever the agent has already done (files written, emails sent, tools called) is not undone. The response ends with `status: "cancelled"`.
</Warning>

## Status values

A response moves from `in_progress` to exactly one terminal status.

| Status        | Meaning                                               |
| ------------- | ----------------------------------------------------- |
| `in_progress` | The turn is running.                                  |
| `completed`   | The turn finished and `output_text` holds the answer. |
| `failed`      | The turn ended on an error; `error` says why.         |
| `cancelled`   | You stopped the turn with `cancel`.                   |
