Skip to content
Docs · Start here

API keys & authentication

Every call carries a Bearer credential. API keys can be full or read-only, and workspace-wide or limited to one project; give each app the narrowest key that works.

View as Markdown
All topics
On this page

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 works until it is revoked, unless you give it an expiry date, and an owner or admin can pause it without revoking it (below). Revoking takes effect immediately.

Kinds of key

KeyActs asCan
Full, whole workspacedeveloperCreate, change and delete resources in every project, deploy, send mail, use AI, buy domains from the Balance. It can also delete projects.
Full, one projectdeveloperThe same, but only on that project's endpoints, plus sending mail as that project and calling AI for it. Workspace-wide endpoints refuse it.
Read-only, whole workspaceviewerGET requests, plus reads sent as POST (vector query, memory search, storage download links). No environment variables, no SQL, no uploads.
Read-only, one projectviewerReads 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

UseKey
Your laptop, CI, setup scriptsFull, whole workspace
A deployed app that uses its database, storage, payments, vectors, memory or auth poolFull, one project
A deployed app that also sends mail or calls the AI GatewayFull, one project: it sends mail and calls AI for its own project, paid for by the workspace
Dashboards, monitoring, read-only AI assistantsRead-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 also sends mail (POST /api/mail/send) and calls the AI Gateway (/api/ai/chat, /api/ai/playground, /api/ai/v1/chat/completions and /api/ai/embeddings) for its own project. Leave projectId out (for AI, also the X-Harakumo-Project header) and the call acts for the key's project; naming another project is refused. The email quota and the AI spend are the workspace's, as with any key, and a read-only key can neither send mail nor spend on AI.

It is refused, with 403 "This API key only works on project N", on workspace-wide routes: listing projects, the rest of /api/mail/* (messages, settings, suppressions and sending domains), /api/ai/requests, /api/domains/*, /api/repos/*, /api/usage and billing.

Its mail goes out only as its own project. It sends from an address on the project's own sending domain (the domain of the project's saved From address) or from the shared no-reply; another domain of the workspace answers 403 from_not_project_domain. It never sends under another project's name or sender name, or a close variant of one (a name that contains one of five letters or more, or is one letter off one of six or more): 403 sender_name_other_project. A variant of a name its own project's name already contains is its own ("Fruitly API" may send as "Fruitly Receipts"), but another project's exact name never is. The same rules apply when it changes its project's mailFrom or mailSenderName with PATCH /api/projects/:id. To move a project to a new sending domain, a person sets its From address on the Mail page.

Create a key with the API

curl (an owner or admin session or key)
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.

Expiry and pause

  • Give a key an end date: PUT /api/orgs/:id/api-keys/:keyId/expiry { days } (1 to 730, counted from now) or { expiresAt } (a date within 730 days); null, or DELETE on the same path, means it never expires. Past the date every request with it is refused with 401 key_expired. An expired key given a new date works again.
  • Pause a key that may have leaked but that an app you cannot update yet still holds: POST /api/orgs/:id/api-keys/:keyId/pause. Every request with it is refused with 403 key_paused until DELETE on the same path resumes it. Nothing else about the key changes.
  • Both need an owner or admin, answer the key with its state (active, expired, paused or revoked), and are written in the workspace's audit log. The same controls are in Settings → API access and in an admin's AI connector (set_api_key_expiry, pause_api_key).
  • A key made by a person whose account Harakumo has suspended is refused too (403 key_creator_suspended) until the account is restored. Create a new key for the app.

Other credentials

CredentialWhere it comes fromActs as
Browser sessionSigning in to the dashboard; an httpOnly cookie, 30 daysYou, 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 poolOnly that pool's sign-in actions — never anything else
Auth-pool client id (ap_…)Enabling an auth poolPublishable: 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

StatusMessageWhat to do
401UnauthorizedThe header is missing, or the key was revoked or mistyped.
401 key_expiredThis API key expired on …Give the key a new date, or create a new key.
403 key_pausedThis API key is pausedAn owner or admin resumes it in Settings → API access.
403 key_creator_suspendedThis API key was created by an account that is suspendedCreate a new key; nothing has been deleted.
403This API key is read-onlyUse a full key for writes.
403This API key only works on project NUse a key for that project, or a workspace key for workspace-wide routes.
403Your role (developer) does not allow this actionThe action needs an admin or owner (members, keys, billing).
404Not foundThe id does not exist or belongs to another workspace — the two look the same on purpose.

Reference

SDK
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).