# API keys & authentication

Send the credential in the Authorization header: `Authorization: Bearer hk_live_…`. The same header works for REST, the SDK, the CLI and the MCP connector.

Owners and admins create keys in Dashboard → Settings → API access. A key is shown once and stored only as a hash; it does not expire, and revoking it takes effect immediately.

## Kinds of key

| Key | Acts as | Can |
| --- | --- | --- |
| Full, whole workspace | developer | Create, change and delete resources in every project, deploy, send mail, use AI, buy domains from the Balance. It can also delete projects. |
| Full, one project | developer | The same, but only on that project's endpoints. Workspace-wide endpoints refuse it. |
| Read-only, whole workspace | viewer | GET requests, plus reads sent as POST (vector query, memory search, storage download links). No environment variables, no SQL, no uploads. |
| Read-only, one project | viewer | Reads on that project only. |

> No API key can manage billing, members or other keys, or move money out: withdrawals, refunds and payout setup need the workspace owner signed in to the dashboard.

## Which key to give what

| Use | Key |
| --- | --- |
| Your laptop, CI, setup scripts | Full, whole workspace |
| A deployed app that uses its database, storage, payments, vectors, memory or auth pool | Full, one project |
| A deployed app that also sends mail or calls the AI Gateway | Full, whole workspace (those endpoints are workspace-wide and refuse project keys today) |
| Dashboards, monitoring, read-only AI assistants | Read-only |

## Where a project key works

A project key passes every route that names its project: `/api/projects/<id>/…`, and item routes whose resource belongs to it — `/api/databases/:id`, `/api/storage/:id`, `/api/functions/:id`, `/api/vectors/:id`, `/api/memory/:id`, `/api/agents/:id`, `/api/payments/:accountId`, `/api/auth-pools/:id`, `/api/deployments/:id`.

It is refused, with 403 "This API key only works on project N", on workspace-wide routes: listing projects, `/api/mail/send`, `/api/ai/*`, `/api/domains/*`, `/api/repos/*`, `/api/usage` and billing.

## Create a key with the API

**curl (an owner or admin session or key)**

```bash
curl -X POST https://harakumo.com/api/orgs/<workspaceId>/api-keys \
  -H "Authorization: Bearer $HARAKUMO_API_KEY" -H "content-type: application/json" \
  -d '{ "name": "notes-app production", "access": "full", "projectId": 12 }'
# 201 → { "key": { "id", "prefix", "access", "project_id" }, "secret": "hk_live_…" }   (secret shown once)
```

> Creating keys needs the admin role, so an API key (developer or viewer) cannot mint more keys. Use the dashboard, or an admin's connector.

## Other credentials

| Credential | Where it comes from | Acts as |
| --- | --- | --- |
| Browser session | Signing in to the dashboard; an httpOnly cookie, 30 days | You, with your role in the chosen workspace |
| Connector token (hk_mcp_…) | Connecting Claude, ChatGPT or another MCP client (OAuth) | You, read-only or read-and-manage, in one workspace or all of them |
| Auth-pool client secret (as_…) | Enabling an auth pool | Only that pool's sign-in actions — never anything else |
| Auth-pool client id (ap_…) | Enabling an auth pool | Publishable: browser sign-in for that pool, when turned on |

## Choosing a workspace

A key belongs to one workspace. A session or an all-workspaces connector acts in the workspace named by `?org=<id>` or the `x-harakumo-org` header, else the one last chosen in the dashboard.

## When a key is refused

| Status | Message | What to do |
| --- | --- | --- |
| 401 | Unauthorized | The header is missing, or the key was revoked or mistyped. |
| 403 | This API key is read-only | Use a full key for writes. |
| 403 | This API key only works on project N | Use a key for that project, or a workspace key for workspace-wide routes. |
| 403 | Your role (developer) does not allow this action | The action needs an admin or owner (members, keys, billing). |
| 404 | Not found | The id does not exist or belongs to another workspace — the two look the same on purpose. |

## Reference

**SDK**

```js
import Harakumo from '@harakumo/sdk';
const hk = new Harakumo({ apiKey: process.env.HARAKUMO_API_KEY });   // baseUrl defaults to https://harakumo.com
// Errors throw HarakumoError with .status and .body (the JSON the API sent).
```

**CLI**

```bash
harakumo login --key hk_live_…     # or set HARAKUMO_API_KEY; --key and --url work on every command
harakumo whoami
```

**REST**

```http
Authorization: Bearer hk_live_…
GET    /api/orgs/:id/api-keys                 # list (admin+); secrets are never returned
POST   /api/orgs/:id/api-keys                 { "name", "access": "full" | "read", "projectId"? }
DELETE /api/orgs/:id/api-keys/:keyId          # revoke
```
