---
title: API conventions
description: Authentication, pagination, errors, and idempotency.
---

The Core API is at `https://api.us.cloud.subako.ai`, every route under `/v1`. The [API reference](/api) lists every endpoint, with a playground that calls the live server.

## Authentication

Every call sends `Authorization: Bearer <token>`. The token's prefix says what it is:

| Prefix | | Lives |
| --- | --- | --- |
| `sbk_ak_` | An [API key](/console/api-keys): one workspace, its permissions | Until revoked, or its expiry |
| `sbk_st_` | A [session token](/build/sessions-api#session-tokens): one session | One hour |
| `sbk_at_` | A person's access token, as the CLI holds after `subako login` | Minutes; `subako token` prints a fresh one |

## The workspace header

A person belongs to many workspaces, so a call made with a person's token names the one it acts in, by ID, in `Kikuvi-Workspace`:

```bash
curl https://api.us.cloud.subako.ai/v1/agents \
  -H "Authorization: Bearer $(subako token | jq -r .access_token)" \
  -H "Kikuvi-Workspace: $WORKSPACE_ID"
```

It is required on a workspace's collections — `/v1/agents`, `/v1/sessions`, `/v1/skills`, `/v1/functions`, `/v1/vaults`, `/v1/model-providers`, `/v1/api-keys`. A route that names a resource by ID finds the workspace from it. An API key already belongs to one workspace, so its calls need no header; one that names another workspace is refused.

The workspace's ID is on its **Overview** page in the console.

## Pagination

Lists answer `{ "items": [...], "next_cursor": ..., "prev_cursor": ... }`, newest first:

| Parameter | |
| --- | --- |
| `limit` | 1 to 200, 50 by default. Outside that range is an error, not clamped. |
| `after` | `next_cursor` from the previous page, for the next one |
| `before` | `prev_cursor`, for the one before |
| `order` | `desc` (the default) or `asc` |

A session's events page by sequence number instead — see [Streaming events](/build/streaming-events#pages).

## Errors

Every failure answers the same envelope:

```json
{ "error": { "code": "not_found", "message": "session not found" } }
```

| `code` | Status |
| --- | --- |
| `invalid_request` | 400 |
| `unauthorized` | 401 |
| `forbidden` | 403 — also when the plan's cap, or the organization's credit, refuses it |
| `not_found` | 404 |
| `conflict` | 409 |
| `precondition_failed` | 412 |
| `too_many_requests` | 429 |
| `internal` | 500 |

Branch on `code`; the `message` is for people and may change.

## Idempotency

Calls that create something take an `Idempotency-Key` header, 1 to 255 characters. The same key again answers what the first call did, with `Idempotency-Replayed: true`, instead of doing it twice — so a retry after a timeout is safe. The same key with a different body is a conflict. A secret shown once, such as a new session's token or a new API key, is not replayed. Keys are remembered for at least seven days.
