# API keys and tokens

Everything that talks to oak.space — the CLI, the [HTTP API](/docs/api), CI jobs, agents — authenticates with a bearer token:

```text
Authorization: Bearer oak_...
```

There are three kinds of token.

| Token | Comes from | Lifetime | Use it for |
|---|---|---|---|
| **Login session** | `oak login` | 90 days | You, at your own terminal. |
| **Personal API key** | You create it | Until revoked | Scripts and tools acting as you. |
| **Service-account key** | An org admin creates it | Until revoked | Bots, CI systems, and agents with their own identity. |

## Personal API keys

Create one under **Settings → API keys** on oak.space, or over the API:

```bash
curl -X POST https://oak.space/api/api-keys \
  -H "Authorization: Bearer $OAK_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"name": "release script", "scope": "read", "repo": "acme/web"}'
```

| Field | Value |
|---|---|
| `name` | 1–100 characters, for your own reference. |
| `scope` | `full` (default) or `read` — a read key can only make read requests. |
| `repo` | Optional `owner/name`. The key then works only for that one repo. |

**The full key is shown once**, when it's created. After that only its first characters are displayed, so store it somewhere safe. List your keys with `GET /api/api-keys` and revoke one with `DELETE /api/api-keys/{id}`.

A personal key acts as you, with your access — a repo-scoped or read-only key can only do less, never more.

## Service accounts

A **service account** is a non-human identity owned by an organization — a deploy bot, an external CI system, a coding agent that runs unattended. Its actions show up under its own name in the audit log, and its keys carry explicit **scopes**, so you can give it exactly what its job needs.

Organization owners and admins manage them under the organization's **Settings → Agents**, or over the API:

```bash
# Create the account
curl -X POST https://oak.space/api/orgs/acme/service-accounts \
  -H "Authorization: Bearer $OAK_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "nightly-agent", "display_name": "Nightly refactor agent"}'

# Give it a key that can push branches but never merge
curl -X POST https://oak.space/api/orgs/acme/service-accounts/nightly-agent/keys \
  -H "Authorization: Bearer $OAK_TOKEN" -H "Content-Type: application/json" \
  -d '{"name": "prod", "scopes": "read,write"}'
```

| Scope | Allows |
|---|---|
| `read` | Every read request: clone, pull, list, view. |
| `write` | Push branches, create repos and branches, and other changes — except the ones below. |
| `merge` | Merge branches onto `main`. |
| `admin` | Organization management (`/api/orgs/...`). |
| `release` | Authorize protected releases. Never implied by any other scope, including `admin`. |

Scopes are required on service-account keys. A key can never do more than the person who created it can. A request outside a key's scopes gets a **403** naming the missing scope.

Disable a service account (`PATCH …/service-accounts/{name}` with `{"disabled": true}`) to stop all its keys at once, or delete it. Revoke one key with `DELETE …/service-accounts/{name}/keys/{key_id}`.

> **For coding agents**, a `read,write` service-account key is a good default: the agent can clone, push its branches, and file what it did, but can't merge to `main` — merging stays a human decision.

## Using a key with the CLI

```bash
export OAK_API_KEY=oak_...
oak clone acme/web
oak auth status       # confirms which credential is in use and who it resolves to
```

The CLI picks its credential in this order:

1. `OAK_API_KEY`, if set.
2. A key stored in the checkout itself (`oak clone` saves one, bound to the server it came from).
3. Your `oak login` session for that server, from `~/.oak/credentials`.

If a push or pull unexpectedly says a repo you know exists isn't found, a stale key from step 2 is the usual suspect — `oak auth status` shows which source is winning.

## Using a key with the API

```bash
curl https://oak.space/api/repos -H "Authorization: Bearer $OAK_API_KEY"
```

See the [HTTP API reference](/docs/api).
