---
title: Sessions over the API
description: Create sessions from your backend and hand them to a browser.
---

The console is one client of the Core API; your product is another. The usual shape is two halves:

- Your **backend** holds an [API key](/console/api-keys). It creates a session for a user, and keeps the key to itself.
- Your **browser** page gets the session's **session token**, and does everything inside that one session: sends messages, follows the run, answers approvals and forms, offers [tools of its own](/build/client-tools).

All of it is plain HTTP at `https://api.us.cloud.subako.ai/v1`, and the [SDKs](/build/sdks) wrap it.

## Creating a session

```bash
curl -X POST https://api.us.cloud.subako.ai/v1/sessions \
  -H "Authorization: Bearer $SUBAKO_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: ticket-4192-session" \
  -d '{
    "agent_id": "<agent-id>",
    "display_name": "ticket-4192",
    "vault_ids": ["<vault-id>"],
    "metadata": { "tenant_id": "t_123" }
  }'
```

| Field | |
| --- | --- |
| `agent_id` | Required. The session runs the agent's latest version, and keeps it. |
| `display_name` | Optional, up to 200 characters. An agent allowed to name its session fills an empty one in. |
| `vault_ids` | Optional, up to 10. The [vaults](/console/vaults) whose credentials the session's tools may use. |
| `metadata` | Optional, up to 50 keys of lowercase letters, digits, and `_`, each value a string up to 500 characters. Fixed at creation and copied onto every usage record the session makes — so usage can be broken down by your own tenant or customer. It is kept on usage after the session is deleted: put opaque IDs here, never personal data. |

The key needs **Sessions** › **Create**. The response is the session, with its first session token:

```json
{
  "id": "01a0877f-d3b3-7e73-9eed-a16caee8cff7",
  "workspace_id": "01a0877c-03d9-7823-b997-f42147b8b607",
  "agent_id": "01a0877d-9a1f-7c40-8e2b-5b3c4d6e7f80",
  "agent_version_id": "01a0877e-1b2c-7d3e-9f40-a1b2c3d4e5f6",
  "display_name": "ticket-4192",
  "status": "idle",
  "session_token": "sbk_st_...",
  "session_token_expires_at": "2026-09-09T19:48:16Z",
  "metadata": { "tenant_id": "t_123" },
  "created_at": "2026-09-09T18:48:16Z"
}
```

The same `Idempotency-Key` sent again answers the same session rather than creating a second one — but without the token, which is shown once. Mint a fresh one instead.

## Session tokens

A **session token** (`sbk_st_…`) carries no permissions of its own. It is good for one session — every route under `/v1/sessions/{its id}/` — and nothing else: it cannot list sessions, create one, or read anything outside its own.

| | API key | Session token |
| --- | --- | --- |
| Scope | A workspace | One session |
| Carries | The permissions it was minted with | Everything inside its session |
| Lives | On your server | In the browser, or a worker |
| Expires | When revoked, or at its expiry | One hour after it was minted |

Inside its session a token can read and follow the log, send messages, cancel, allow or deny approvals, answer forms, register clients, rename or delete the session, and read and write its volume.

A token lasts an hour. Mint the next one from your backend, before the last runs out:

```bash
curl -X POST https://api.us.cloud.subako.ai/v1/sessions/$SESSION_ID/tokens \
  -H "Authorization: Bearer $SUBAKO_API_KEY"
```

That takes **Sessions** › **Manage**; a session token cannot mint tokens itself.

## Talking to a session

```bash
# Say something: starts a run, or steers the one under way.
curl -X POST https://api.us.cloud.subako.ai/v1/sessions/$SESSION_ID/events \
  -H "Authorization: Bearer $SUBAKO_SESSION_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"type": "input", "text": "Summarize ticket 4192."}'
# {"run_id": "01a08780-...", "outcome": "new_run"}

# Stop the run under way.
curl -X POST https://api.us.cloud.subako.ai/v1/sessions/$SESSION_ID/cancel \
  -H "Authorization: Bearer $SUBAKO_SESSION_TOKEN"
```

Input sent while a run is working answers `"outcome": "steered"`: it joins that run rather than waiting for it. Cancelling is cooperative — the run stops at its next checkpoint and ends with `run_cancelled`.

Follow what happens with [the event stream](/build/streaming-events).

## Calling from a browser

A page calls the API with the session token, and the browser asks first whether the origin may: **CORS**. Origins are allowed per agent, on its page in the console (**CORS** › **Edit**), with `subako agent set-security`, or with the API:

```bash
curl -X PUT https://api.us.cloud.subako.ai/v1/agents/$AGENT_ID/security \
  -H "Authorization: Bearer $SUBAKO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"allowed_origins": ["https://app.acme.example"]}'
```

An origin is `scheme://host[:port]`, with no path; `*` allows any. The list applies to the routes under `/v1/sessions/{id}/` of that agent's sessions, and nothing else: no page may create a session or use an API key, which is what keeps the key on your server.

A browser's `EventSource` cannot send an `Authorization` header, so read the stream with `fetch` — the TypeScript SDK does.

## Listing and cleaning up

```bash
curl "https://api.us.cloud.subako.ai/v1/sessions?agent_id=$AGENT_ID&limit=20" \
  -H "Authorization: Bearer $SUBAKO_API_KEY"

curl -X DELETE https://api.us.cloud.subako.ai/v1/sessions/$SESSION_ID \
  -H "Authorization: Bearer $SUBAKO_API_KEY"
```

Deleting a session deletes its log and its volume. Usage it recorded stays.

See [API conventions](/reference/api) for authentication, the workspace header, pagination, errors, and idempotency.
