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

# Sessions & models

> List, read, rename, and delete conversations on an instance, and see which models a harness can run.

A session is one conversation on an instance. An instance holds many sessions, one per thread, and each session keeps its own full history, so you only ever send the new input. These endpoints are served by the gateway running inside the instance.

The gateway keeps no session index of its own. The agent harness owns both the transcript and the list — Hermes' session store, OpenClaw's history — and the gateway projects them on read. So a session's fields are the harness's own, and a list or read always reflects the live state of that harness.

Everything on this page lives on the instance URL, not the hosting API: the base is `https://{instanceId}.agent37.app`, with the same `sk_live_` key sent as the `X-Agent37-Key` header. The platform edge authenticates the key and checks the instance belongs to your workspace, then the gateway answers. See [Instance and preview URLs](/docs/agents-api/urls).

## Endpoints

| Method   | Path                | Returns                                                                                         |
| -------- | ------------------- | ----------------------------------------------------------------------------------------------- |
| `GET`    | `/v1/sessions`      | `200` `{ "agent": "...", "data": [...] }`, the harness's session list, newest first             |
| `GET`    | `/v1/sessions/{id}` | `200` `{ "id", "agent", "active_response_id", "history" }` — the conversation's full transcript |
| `PATCH`  | `/v1/sessions/{id}` | `200` `{ "id", "agent", "renamed" }` — rename a session (Hermes)                                |
| `DELETE` | `/v1/sessions/{id}` | `200` `{ "id": "...", "deleted": true }`                                                        |
| `GET`    | `/v1/models`        | `200` `{ object, agent, default_model, default_provider, data }`                                |
| `GET`    | `/v1/health`        | `200` `{ "ok": true, "agent": "hermes", "healthy": true, "hermes": true }`                      |
| `GET`    | `/v1/version`       | `200` `{ name, version }`                                                                       |

<Note>
  You never create a session directly. The first [`POST /v1/responses`](/docs/agents-api/chat) without a `session_id` mints one and returns its id; reuse that id to continue the thread.
</Note>

## Choosing the harness

Every read on this page takes an optional `?agent=` query — `hermes` or `openclaw` — that selects which harness on the instance answers. Omit it (or send it empty) and the instance's configured default harness answers; the response echoes which `agent` it was. An unknown value is `400 validation_error`. On the data reads (`/v1/sessions`, `/v1/sessions/{id}`, `/v1/models`), targeting a harness the instance was not provisioned with is `503 agent_unavailable`; `/v1/health` instead reports an unreachable or unprovisioned harness as `healthy: false` with a `200`. Your `agent37-hermes` Cloud instances serve Hermes, so you can leave `?agent=` off.

## The session object

There is no gateway-defined session shape. `GET /v1/sessions` passes each entry through from the harness's own store, native fields untouched, so the exact fields depend on the harness and can evolve with it.

<ResponseField name="id" type="string">
  The only field the gateway guarantees across harnesses. It is the session id you pass back to `GET`, `PATCH`, and `DELETE /v1/sessions/{id}`, and the `session_id` every response in the conversation carries. 32 hex characters, no prefix.
</ResponseField>

A Hermes session object also carries Hermes' own fields — `title`, `model`, `message_count`, `started_at`, `last_active`, and `preview` (see the example below). OpenClaw returns its own native fields. These come straight from the harness; the gateway does not normalize them, so field names, types, and timestamp units track the harness rather than this API (unlike a history message's `created_at`, which the gateway does normalize to epoch milliseconds). Read what you need by name; don't assume a field exists on every harness.

## List sessions

`GET /v1/sessions` returns the harness's sessions, newest first (Hermes orders by most recent activity), wrapped in `{ "agent": "...", "data": [...] }`. The `agent` names which harness the list is for. Pass `?agent=hermes` or `?agent=openclaw` to pick a harness; omit it for the instance default. The list carries session metadata only, never history, so it stays cheap to poll for a sidebar. A harness without a list API returns `data: []`.

<CodeGroup>
  ```bash curl theme={null}
  curl https://ab12cd34ef.agent37.app/v1/sessions \
    -H "X-Agent37-Key: sk_live_..."
  ```

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

  sessions = requests.get(
      "https://ab12cd34ef.agent37.app/v1/sessions",
      headers={"X-Agent37-Key": "sk_live_..."},
  ).json()["data"]
  ```

  ```javascript node theme={null}
  const { data: sessions } = await (await fetch(
    "https://ab12cd34ef.agent37.app/v1/sessions",
    { headers: { "X-Agent37-Key": "sk_live_..." } },
  )).json();
  ```

  ```json response theme={null}
  {
    "agent": "hermes",
    "data": [
      {
        "id": "7f3e0b6c52a949d2b1c4a8e9d0f31726",
        "title": "EV makers memo",
        "model": "claude-sonnet-4-5",
        "message_count": 4,
        "started_at": 1781049600000,
        "last_active": 1781049642000,
        "preview": "Research the top 3 EV makers, write a memo."
      }
    ]
  }
  ```
</CodeGroup>

## Retrieve a session with history

`GET /v1/sessions/{id}` returns `{ "id", "agent", "active_response_id", "history" }`: `history` is the full transcript, in order, projected from the harness. You read it for display or audit; you never resend it, because the session already holds it. An unknown id returns an empty `history` rather than a `404` — the harness owns whether a session exists. Pass `?agent=` to pick the harness.

<ResponseField name="active_response_id" type="string | null">
  The id of the response currently running on the session, or `null` when it is idle. The harness writes a turn's messages at turn end, so while this is set the running turn is normally **not** in `history` yet — follow it live with [`GET /v1/responses/{id}/stream`](/docs/agents-api/streaming#reconnect-after-a-drop). This is how a client that lost its state (page reload, new device) rediscovers a running turn and reattaches. Two timing edges: right at turn end, one read can briefly show the finished turn in `history` and its id still here — reattaching is still correct, the replay just ends immediately. And once it reads `null` the transcript is complete, with one exception: a cancelled OpenClaw turn is persisted by OpenClaw on its own schedule and can surface in `history` shortly after.
</ResponseField>

Each entry in `history` is a message:

<ResponseField name="id" type="string">
  The message id. Treat it as opaque; it uses a different format from session and response ids.
</ResponseField>

<ResponseField name="session_id" type="string">
  The session the message belongs to.
</ResponseField>

<ResponseField name="role" type="string">
  `user`, `assistant`, or `system`.
</ResponseField>

<ResponseField name="content" type="string">
  The message text.
</ResponseField>

<ResponseField name="thinking" type="string">
  The assistant's reasoning for that turn, when the agent recorded any. Absent otherwise.
</ResponseField>

<ResponseField name="created_at" type="number">
  When the message was created, in epoch milliseconds.
</ResponseField>

<CodeGroup>
  ```bash curl theme={null}
  curl https://ab12cd34ef.agent37.app/v1/sessions/7f3e0b6c52a949d2b1c4a8e9d0f31726 \
    -H "X-Agent37-Key: sk_live_..."
  ```

  ```json response theme={null}
  {
    "id": "7f3e0b6c52a949d2b1c4a8e9d0f31726",
    "agent": "hermes",
    "active_response_id": null,
    "history": [
      {
        "id": "hermes:7f3e0b6c52a949d2b1c4a8e9d0f31726:1",
        "session_id": "7f3e0b6c52a949d2b1c4a8e9d0f31726",
        "role": "user",
        "content": "Research the top 3 EV makers, write a memo.",
        "created_at": 1781049601000
      },
      {
        "id": "hermes:7f3e0b6c52a949d2b1c4a8e9d0f31726:2",
        "session_id": "7f3e0b6c52a949d2b1c4a8e9d0f31726",
        "role": "assistant",
        "content": "Here is the memo...",
        "thinking": "Comparing deliveries, margins, and charging networks...",
        "created_at": 1781049642000
      }
    ]
  }
  ```
</CodeGroup>

## Rename a session

`PATCH /v1/sessions/{id}` sets a session's title, writing it straight into the harness's own store, and returns `{ "id", "agent", "renamed" }`. `renamed` is `false` when no session matched the id. Send the new title in the body:

<ParamField body="title" type="string" required>
  The new title. Cannot be empty. Hermes enforces a unique, length-capped title: a title already used by another session returns `409 title_conflict`, and an over-long one returns `400 validation_error`.
</ParamField>

Rename is only available on harnesses that natively store an editable title. Hermes supports it; a harness that does not returns `405 rename_unsupported`. Pass `?agent=` to pick the harness.

<CodeGroup>
  ```bash curl theme={null}
  curl -X PATCH https://ab12cd34ef.agent37.app/v1/sessions/7f3e0b6c52a949d2b1c4a8e9d0f31726 \
    -H "X-Agent37-Key: sk_live_..." \
    -H "Content-Type: application/json" \
    -d '{ "title": "EV makers memo" }'
  ```

  ```json response theme={null}
  { "id": "7f3e0b6c52a949d2b1c4a8e9d0f31726", "agent": "hermes", "renamed": true }
  ```
</CodeGroup>

## Delete a session

`DELETE /v1/sessions/{id}` is a best-effort removal from the harness's store and returns `{ "id": "...", "deleted": true|false }`. `deleted` is `true` when a transcript was removed and `false` when nothing matched the id, so the call is idempotent: repeating it simply returns `deleted: false` rather than erroring. A harness with no delete route (OpenClaw) always returns `deleted: false` and keeps its copy. Pass `?agent=` to pick the harness.

<CodeGroup>
  ```bash curl theme={null}
  curl -X DELETE https://ab12cd34ef.agent37.app/v1/sessions/7f3e0b6c52a949d2b1c4a8e9d0f31726 \
    -H "X-Agent37-Key: sk_live_..."
  ```

  ```json response theme={null}
  { "id": "7f3e0b6c52a949d2b1c4a8e9d0f31726", "deleted": true }
  ```
</CodeGroup>

<Warning>
  Deleting a session removes the conversation and its history from the harness, but leaves the instance (its files, memory, and connected accounts) untouched. The per-turn response receipts are in-memory and short-lived; they expire on their own.
</Warning>

## One turn at a time

A session runs one response at a time. Posting new input while a turn is in flight returns `409 session_busy`, normally with the running response's id in `error.response_id` — reattach to it with [`GET /v1/responses/{id}/stream`](/docs/agents-api/streaming#reconnect-after-a-drop), cancel it with [`POST /v1/responses/{id}/cancel`](/docs/agents-api/chat), or start the new input on another session. Two sessions on the same instance run independently.

## List models

`GET /v1/models` lists the models a harness can run, in the OpenAI list shape — `{ "object": "list", "data": [...] }` — so any OpenAI-compatible client works against it. It reports on one harness: the instance default, or the one named by `?agent=` (e.g. `?agent=openclaw`), and the response echoes which `agent` answered. The result is cached for about 60 seconds, so a newly available model can take up to a minute to appear.

<ResponseField name="object" type="string">
  Always `"list"`.
</ResponseField>

<ResponseField name="agent" type="string">
  Which harness this list is for, `hermes` or `openclaw`.
</ResponseField>

<ResponseField name="default_model" type="string | null">
  The model used when a turn does not name one.
</ResponseField>

<ResponseField name="default_provider" type="string | null">
  The provider of the default model.
</ResponseField>

<ResponseField name="data" type="array">
  One entry per model. Each is an OpenAI-compatible model object plus a few additive fields a UI can group and label on:

  * `id` — the model id. Pass it as `model` on a turn.
  * `object` — always `"model"`.
  * `created` — Unix seconds. We don't track per-model creation time, so this is a stable placeholder (`0`).
  * `owned_by` — the upstream provider, e.g. `nous` or `anthropic`.
  * `label` — a human-readable name.
  * `source` — where the entry comes from: `current`, `catalog`, `custom`, or `alias`.
  * `is_default` — `true` for the default model.
</ResponseField>

<CodeGroup>
  ```bash curl theme={null}
  curl https://ab12cd34ef.agent37.app/v1/models \
    -H "X-Agent37-Key: sk_live_..."
  ```

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

  models = requests.get(
      "https://ab12cd34ef.agent37.app/v1/models",
      headers={"X-Agent37-Key": "sk_live_..."},
  ).json()
  ```

  ```javascript node theme={null}
  const models = await (await fetch(
    "https://ab12cd34ef.agent37.app/v1/models",
    { headers: { "X-Agent37-Key": "sk_live_..." } },
  )).json();
  ```

  ```json response theme={null}
  {
    "object": "list",
    "agent": "hermes",
    "default_model": "claude-sonnet-4-5",
    "default_provider": "anthropic",
    "data": [
      {
        "id": "claude-sonnet-4-5",
        "object": "model",
        "created": 0,
        "owned_by": "anthropic",
        "label": "Claude Sonnet 4.5",
        "source": "catalog",
        "is_default": true
      },
      {
        "id": "gpt-5.2",
        "object": "model",
        "created": 0,
        "owned_by": "openai",
        "label": "GPT-5.2",
        "source": "catalog",
        "is_default": false
      }
    ]
  }
  ```
</CodeGroup>

`model` and `provider` are dials you set per turn on [`POST /v1/responses`](/docs/agents-api/chat): omit them to keep the session's current model, or send them to switch. A continuation that sets them updates the session's model for the turns that follow.

## Health and version

`GET /v1/health` returns `{ "ok": true, "agent": "hermes", "healthy": true, "hermes": true }`. `ok` is true whenever the gateway is up; `agent` is the harness that was probed, and `healthy` reports whether that harness behind it is reachable. By default it probes the instance's configured harness; pass `?agent=` to probe a specific one. When Hermes is probed, the body also carries a `hermes` field mirroring `healthy`, kept for backward compatibility; other harnesses omit it. Use it as a readiness probe after [create or start](/docs/agents-api/instances).

`GET /v1/version` returns the gateway build, e.g. `{ "name": "agent37-gateway", "version": "0.1.3" }`.
