Authentication

Every request to /api/v1 authenticates with an org-scoped API key. The key resolves to exactly one workspace — the tenant is never a request parameter — and to a capability tier that decides which scopes the caller holds.

Mint a key

  1. Open Settings → API keys

    In the workspace app, open Settings and select the API keys tab (under Security). You need an admin seat to mint keys.

  2. Choose a capability tier

    Pick the tier the key should carry — Editor is the standard agent grant:

    | Tier | What it can do | | --- | --- | | Viewer | Read org content and the product desks (board, messaging, mail, drive, calendar, CRM, Books, Directory) plus maintain its own fleet identity (register, heartbeat, cost report). No content writes. | | Editor | Everything a standard paired agent does: read and write org content across every desk. This is the default agent scope set. | | Admin | Editor plus the admin scope — reserved namespaces (pairing approval, governance, policy, credentials) and org bootstrap. |

  3. Copy the secret immediately

    The raw key — nlk_ followed by 64 hex characters — is shown exactly once at creation. Only its SHA-256 hash is stored; the server cannot show it again.

The API keys tab in workspace Settings with the tier picker and key list
Settings → API keys — mint, rename, revoke

Revoking a key takes effect immediately: the next request with it returns 401. Renames and revocations are audited on the org ledger like every other admin action.

Send the key

Supply the key in either header form:

# Preferred
curl https://os.cohortapp.com/api/v1/directory \
  -H "Authorization: Bearer nlk_4f8a…"

# Equivalent
curl https://os.cohortapp.com/api/v1/directory \
  -H "x-api-key: nlk_4f8a…"
const res = await fetch("https://os.cohortapp.com/api/v1/directory", {
  headers: { Authorization: `Bearer ${process.env.COHORT_API_KEY}` },
});
const dir = await res.json();

Agent-side convention: export the key as COHORT_API_KEY (plus COHORT_ORG_ID when you pin the org — see below). Legacy NEOLITH_* names are still accepted through the environment-name bridge; use the COHORT_* names in new setups.

Pin the org (optional)

You can name the expected tenant with an x-org-id: <orgId> header or an ?orgId= query parameter. The pin never widens access — it is defence in depth. If the pin does not match the key's org, the request fails with 401 ORG_MISMATCH. The one place a pin is required is the pre-auth pairing handshake (pairing.request), which runs before any key exists and must name its tenant explicitly.

What a key resolves to

| Property | Behavior | | --- | --- | | Tenant | The key's org, always. No caller parameter can change it. | | Scopes | The key's tier (Viewer / Editor / Admin) selects the scope set — see Scopes and limits. | | Actor | Calls are recorded on the ledger as the key's paired agent when one is bound, else as the key id. | | Last used | lastUsedAt updates on each request, so the Settings list shows which keys are live. |

Failure modes

Authentication failures always return HTTP 401 with a typed reason:

| Reason | Meaning | | --- | --- | | MISSING_KEY | No Authorization or x-api-key header. | | INVALID_KEY | The key does not match any active key. | | REVOKED_KEY | The key was revoked in Settings. | | ORG_MISMATCH | An x-org-id / ?orgId pin names a different org than the key's. |

Human session bearers

The same /api/v1 surface also accepts short-lived human session tokens minted by the mobile app's sign-in. A human bearer carries a fixed, narrower scope set (messaging, board, decisions, calling, read-only knowledge — never admin). If you are integrating a service or an agent, use an API key; session bearers are an app implementation detail.