Authentication
Every request carries one header, whatever the credential behind it:
Authorization: Bearer <credential>
The API tells the credentials apart by their shape, so there is nothing else to declare.
Credential types
| Credential | Who holds it | How to get one | Lifetime |
|---|---|---|---|
API key (nat_sk_…) | Your backend, CI, a script | POST /v1/api-keys while signed in — see API Keys | Until rotated or revoked |
| Session token (JWT) | A person in the app or the CLI | Sign in with a code emailed to the address — see Auth | 15 minutes; refresh for 30 days |
| OAuth access token (JWT) | An MCP client such as Claude or Cursor | The client runs OAuth 2.1 and you approve it — see MCP Server | 1 hour; the client refreshes for 30 days |
Use an API key for anything that runs without a person in front of it, and scope it to one project unless it genuinely needs the whole account. A session token is what the app holds; never store one where an API key would do. An OAuth token is minted for and held by the client that asked for it — you never copy it anywhere.
The raw nat_sk_… secret is returned once, at creation or rotation. A key
cannot be minted by an OAuth token: creating one is a human act.
What a credential reaches
A request acts in the project named in its path, /v1/projects/{project_id}/….
Two independent checks decide whether it may:
- Credential scope. A project-scoped API key reaches its one project. An OAuth token reaches the projects chosen on its consent screen. An account-scoped key and a session token reach every project the user belongs to.
- Membership. The user behind the credential must be a member of that
project, with a role wide enough for the action.
memberreads and writes every resource in the project;adminalso manages who is in it and how it is configured;owneralso deletes it. See Projects → Membership.
Scope narrows membership; it never widens it. A project-scoped key whose user is removed from the project stops reaching it.
Credentials are resolved on every request, not trusted from what a token claims. Revoking a key, disconnecting an app or changing a member's role takes effect on the next call, not when a token expires.
When a request is refused
| Status | code | Means |
|---|---|---|
401 | unauthorized | No Authorization header, a malformed one, or a credential that is unknown, expired or revoked. Refresh a session; replace a key. |
404 | not_found | The user is not a member of the project. A project you cannot reach looks the same as one that does not exist. |
403 | access_denied | The credential's scope excludes the project, or the role is too narrow — the message names the role the action needs. |
401 is about the credential and 403 about what it may do. Sign-in endpoints
also answer 429; see Rate limits.
Check which account a credential resolves to
GET /v1/users/me answers for every
credential type, which makes it the first call to try with a new one.
- CLI
- SDK
- curl
export NATURALI_TOKEN=nat_sk_...
naturali get-current-user
import { NaturaliClient } from '@naturali/sdk';
const naturali = new NaturaliClient({ token: process.env.NATURALI_TOKEN });
const { data: me } = await naturali.users.getCurrentUser();
curl https://api.naturali.ai/v1/users/me \
-H "Authorization: Bearer $NATURALI_TOKEN"