GET /v1/status and the public feedback URL (POST /v1/feedback/{organizationSlug}) requires a bearer credential in the Authorization header. Two kinds of credential are accepted, and both are checked against the same scopes:
The API tells the two apart by shape: a value that parses as a JWT is verified as an OAuth token, anything else is verified as an API key.
How It Works
Authorization: Bearer <credential>. The scheme name is case-insensitive. Keys created in the dashboard start with klra_; OAuth access tokens are used the same way, see OAuth.
The SDK reads the key from the
bearerAuth option. Its generated README refers to a NOTRA_BEARER_AUTH environment variable; any variable name works as long as you pass the value in.Create an API Key
1
Open the API Keys page
In the dashboard, click API Keys at the bottom of the sidebar (
app.kolaria.com/{org-slug}/api-keys). Keys belong to the organization you are viewing.2
Start from a preset or a blank key
The page offers three presets: MCP Server (read and write on every resource), SDK (read on every resource) and CLI (read and write on every resource). Each one prefills a name and permissions. Create API Key opens the same form with read access on every resource.
3
Choose a name, expiration and permissions
Expiration is one of No expiry, 7 days, 30 days, 60 days or 90 days. Permissions use one of three access modes: Full Access (every scope), GEO Access (read and write on the GEO resources only) or Restricted, which shows a None / Read / Write selector for each resource.
4
Copy the key
The key is shown once after creation. Store it server-side. You can rename a key, change its expiration and adjust its permissions later from the same page, and delete it at any time.


Scopes
Every resource has a<resource>.read and a <resource>.write scope. The API derives the required scope from the request: GET needs the read scope, and POST, PUT, PATCH and DELETE need the write scope. A write scope does not imply the read scope, so a key that should both read and update posts needs posts.read and posts.write. In the dashboard, choosing Write for a resource grants both.
GET /v1/status and POST /v1/feedback/{organizationSlug} need no key at all.
Legacy scopes
Keys created before granular scopes existed carryapi.read or api.write. They keep working: api.write satisfies every scope, and api.read satisfies every read scope. The API Keys page shows these keys with their expanded permissions, and saving a key from the editor converts it to granular scopes.
Plan requirements
Scopes are checked first, then the organization’s plan:POST,PUTandPATCHrequests need an active paid plan or a positive AI credit balance. Otherwise the API returns402witherror: "Active subscription required".GETandDELETEalways work so you can read and remove your data.- Every GEO endpoint (
/v1/projects/*and/v1/geo/ingest/*), reads included, needs a GEO plan. Otherwise the API returns402witherror: "GEO requires a Starter, Growth, or Scale plan".
OAuth
OAuth lets a CLI, MCP client or agent obtain a token for a signed-in user without that user creating and pasting an API key. Tokens are issued by the Kolaria authorization server athttps://oauth.kolaria.com, are bound to one organization, carry the scopes the user approved, and can be refreshed and revoked. The Kolaria MCP server at https://mcp.kolaria.com/mcp accepts the same tokens.
Use OAuth when a person is present to approve access, when you want per-user identity on requests, or when the credential should expire on its own. Use an API key for unattended server-to-server work.
Discovery
Everything a client needs is published as standard metadata:
The protected resource metadata lists the authorization server,
scopes_supported (openid and offline_access) and bearer_methods_supported: ["header"]. Any 401 from the API or the MCP server carries WWW-Authenticate: Bearer resource_metadata="..." pointing at the matching document, so a client that starts with an unauthenticated request can discover the rest.
The authorization server exposes:
Supported: the authorization code grant with PKCE (
S256), the refresh token grant, and the device authorization grant. Clients are public (token_endpoint_auth_methods_supported: ["none"]), so no client secret is involved.
Register a client
Register once with dynamic client registration and store the returnedclient_id:
openid and add offline_access for refresh tokens. WorkOS does not allow custom OAuth scopes on dynamically registered clients, including ChatGPT CIMD clients. The consent screen instead asks the user to select a workspace and one access level: Read only, Write only, or Full access for all resources in the scope table. These choices are signed into access-token claims and mapped to the same granular permissions as API keys. Read only grants every read scope, Write only grants every write scope, and Full access grants both. Missing choices grant no access, and broader role permissions cannot override these choices. The API checks current workspace membership on every request.
Authorization code flow with PKCE
- Generate a random
code_verifierand itsS256code_challenge. - Open the user’s browser at the authorization endpoint:
- The user signs in, picks the workspace and approves an access level. The browser is redirected to
redirect_uriwithcodeandstate. - Exchange the code for tokens:
access_token, expires_in, token_type: "Bearer", the granted scope, and refresh_token when offline_access was granted.
Device flow for headless CLIs
When there is no browser on the machine (SSH sessions, containers, CI runners with a person watching), use the device authorization grant:verification_uri and user_code to the user, then poll the token endpoint at the returned interval until they approve:
error: "authorization_pending" (or slow_down); keep polling. Once approved you get the same token response as the code flow.
Use the access token
Send it exactly like an API key:401 with an expiry error, refresh and retry. Per-key rate limits apply per user and organization for OAuth tokens, so two users in the same organization have separate budgets.
Refresh with offline_access
If the user grantedoffline_access, exchange the refresh token for a new access token when the old one expires:
offline_access there is no refresh token and the user must sign in again when the access token expires.
Revocation
Revoke a token when the user signs out or the client is uninstalled:401, run discovery and the authorization flow again rather than retrying with the old token.
OAuth-specific errors
OAuth tokens go through the same{ error, code, recovery } envelope. In addition to the errors listed under Error Responses, you may see:
Error Responses
Authentication and authorization failures, for API keys and OAuth tokens alike, return a JSON body with three fields:code is the error string upper-cased with non-alphanumeric runs replaced by _. 401 responses also include WWW-Authenticate: Bearer resource_metadata="https://api.kolaria.com/.well-known/oauth-protected-resource", which is the entry point for OAuth discovery.