# Control API

## Introduction

The Control API manages Capacity budgets, the pools on them, pool keys,
webhook destinations, and pool Metrics from your tools. It lives on your
swarmvia host (for example `https://swarmvia.com`), not on
`api.swarmvia.cloud`.

The API token is **not** a pool key. It never admits traffic on the proxy.

It is also not how AI assistants connect. Those use the MCP server at
`https://swarmvia.com/mcp`. See [Agents](/docs/agents) for how to add that URL
in Claude or Cursor. There is no token to paste. Use an API token for CI,
scripts, and infrastructure code, where a value in an environment variable is
the right shape.

## Authentication

1. Open **Organization settings → API tokens**.
2. Create a token. Choose when it expires: 1 month, 6 months, or 1 year.
3. Copy the plain token once. Send it as `Authorization: Bearer …`.

Only owners and admins can create or revoke tokens. A token authenticates as
the person who created it in that organization. It can call the Control API
for every budget and pool on the org. Verb abilities still apply
(`budgets:read`, `pools:read`, `pools:write`, `keys:write`, `metrics:read`).
Webhook create and attach use `pools:write`. Listing destinations uses
`pools:read`.

```bash
curl -sS "https://swarmvia.com/api/v1/me" \
  -H "Authorization: Bearer $NTHBOUNCER_CONTROL_TOKEN" \
  -H "Accept: application/json"
```

## Endpoints

Pool path segments use the pool **subdomain** (the `*.swarmvia.cloud` label).

| Method | Path | Ability | Purpose |
|--------|------|---------|---------|
| `GET` | `/api/v1/me` | (any valid token) | Current user, team, abilities |
| `GET` | `/api/v1/budgets` | `budgets:read` | List granted budgets |
| `GET` | `/api/v1/budgets/{id}` | `budgets:read` | Budget detail |
| `GET` | `/api/v1/budgets/{id}/pools` | `pools:read` | Pools under that budget |
| `GET` | `/api/v1/pools` | `pools:read` | List granted pools |
| `POST` | `/api/v1/pools` | `pools:write` | Admit an https origin (same as MCP `create_pool`) |
| `POST` | `/api/v1/pools/database` | `pools:write` | Admit a public Postgres host |
| `POST` | `/api/v1/pools/{subdomain}/auth-handoff` | `pools:write` | Flip to managed auth and mint a one-shot intake |
| `PUT` | `/api/v1/pools/{subdomain}/credential` | `pools:write` | Attach a stored credential (secret allowed on this plane) |
| `GET` | `/api/v1/pools/{subdomain}` | `pools:read` | Pool detail |
| `POST` | `/api/v1/pools/{subdomain}/keys` | `keys:write` | Mint a pool key |
| `DELETE` | `/api/v1/pools/{subdomain}/keys/{id}` | `keys:write` | Revoke a pool key |
| `GET` | `/api/v1/pools/{subdomain}/metrics` | `metrics:read` | Admit / queue / reject series |
| `GET` | `/api/v1/webhooks` | `pools:read` | List webhook destinations |
| `POST` | `/api/v1/webhooks` | `pools:write` | Create a destination (signing secret once). Optional `pool_ids` attaches and enables async |
| `PUT` | `/api/v1/pools/{subdomain}/webhooks` | `pools:write` | Replace destinations on a pool. Enables async when the list is not empty |

`POST /api/v1/pools` takes `origin` (required) and `name` (optional). It
reuses a live HTTP pool that already fronts that host, or creates a
pass-through pool with dashboard defaults. It does not attach a Capacity
budget or an origin credential. 201 on create, 200 on reuse.

Attach budgets and managed credentials in the dashboard.

`POST /api/v1/webhooks` takes `name`, `url`, optional `description`, and
optional `pool_ids`. The URL must be https and cannot be loopback, private,
or swarmvia itself. 201 returns `signing_secret` once (`whsec_…`).

`PUT /api/v1/pools/{subdomain}/webhooks` takes `webhook_endpoint_ids` (may
be empty). Owners and admins only, same as the dashboard.

### Mint a pool key (optional TTL)

```bash
curl -sS -X POST "https://swarmvia.com/api/v1/pools/$POOL_SUBDOMAIN/keys" \
  -H "Authorization: Bearer $NTHBOUNCER_CONTROL_TOKEN" \
  -H "Accept: application/json" \
  -H "Content-Type: application/json" \
  -d '{"label":"session","expires_preset":"1h"}'
```

`expires_preset` accepts `none`, `1h`, `24h`, or `7d`. You may send `expires_at`
(ISO-8601) instead. The raw key is returned once.

Session runs should prefer a TTL. Project keys in a repo skill may omit expiry.

## Key access

`POST /v1/pools/{pool}/keys` accepts `access`:

| Value | Methods admitted |
|-------|------------------|
| `write` (default) | Every method |
| `read` | `GET`, `HEAD`, `OPTIONS`, plus any methods the pool treats as reads |

A read-only key is refused with `403` on any other method, before the request
reaches your origin and before it takes a concurrency slot. The response
includes `access` and `allowed_methods` so a caller can check what it holds.
When the pool scopes extra read methods to path prefixes, the response also
includes `read_paths`, keyed by method (`{ "POST": ["/services/rest/query/"] }`).

Omitting `access` keeps the previous behavior, so keys minted before this
existed are unchanged.

On a [database pool](/docs/pools/database), `access: read` and `access: write`
both connect as the read role. Method scope is not applied: every query is
`POST`. Writes are refused by Postgres, not by the key.

## MCP

AI assistants use `https://swarmvia.com/mcp`, not this token. Connection
steps are on [Agents](/docs/agents). Tools and session keys are on
[MCP server](/docs/mcp).

## Related

- [OpenAPI 3.1 spec](/docs/api/openapi.json)
- [Agents](/docs/agents)
- [MCP server](/docs/mcp)
- [Reuse one pool across agent runs](/docs/knowledge-base/agent-pools)
- [Access](/docs/pools/access)
- [Concurrency locks](/docs/api/locks) (lock API on `api.swarmvia.cloud`)
