Skip to main content

API Keys

The API Keys module provides long-lived programmatic credentials for users. An API key authenticates as its owning user, is always scoped to a single project, and optionally restricts access to a subset of that user's policies.

Overview

API keys are prefixed with sk_ and are identified in the system by a public id prefixed with key_. The raw key value is returned only at creation time and cannot be retrieved again. A truncated key_prefix (first 8 characters) is stored for identification.

API keys use the standard Authorization: Bearer <key> header — the same as JWTs.

See the Permissions Reference for the IAM action strings for this module.

Data Model

FieldTypeDescription
idstringPublic identifier prefixed with key_
namestringHuman-readable label
key_prefixstringFirst 8 characters of the raw key (for identification, never the full secret)
user_idstringPublic ID of the owning user
project_idstringRequired — the single project this key is scoped to
policy_idsstring[]Optional — public IDs of policies that further restrict key permissions
created_atstringISO 8601 creation timestamp
updated_atstringISO 8601 last-updated timestamp

Key Concepts

Permission Inheritance

Every key is scoped to one project; policy_ids optionally narrow it further:

ConfigurationEffective permissions
project_id onlyUser permissions, restricted to that project
project_id + policy_idsIntersection of user policies and key policies, restricted to that project

Intersection semantics: when a key has policy_ids, both the user's policies and the key's own policies must independently allow the requested action. The key can never exceed the permissions of the user who owns it. See this ceiling demonstrated end to end in Permissions in Practice - Step 7 (Verify permissions), where a key granted a full-access policy is still limited to its owner's read-only permissions.

Project Scoping

project_id is required at creation — a key cannot span projects. Any request made with the key is hard-locked to its project; attempts to access resources in any other project are denied regardless of what the policies say. An update may re-scope a key to a different project but can never clear the scope. For a worked example of creating project-scoped keys, see Permissions in Practice - Step 6 (Create API keys).

Implicit project id

Because a project-scoped key already identifies its project, project_id is optional on requests made with such a key:

  • Omit project_id and the request defaults to the key's project. An agent using a project-scoped MCP connector can upload a file, list files, create documents, etc. without first calling list-projects.
  • Supply a project_id that matches the key's project and it is accepted.
  • Supply a project_id that belongs to a different project and the request is rejected with 403.

JWT auth is unchanged: a write that omits project_id still returns 400, since a concrete project is never inferred from a user's set of accessible projects.

Policy Attachment

Policies listed in policy_ids are loaded from the global Policies store. policy_ids is the list of policy public IDs (pol_-prefixed) attached to the key; the REST API accepts and returns these public IDs.

Revoking a Key

Delete the key via DELETE /api/v1/api-keys/:id. The key immediately stops authenticating. There is no rotation endpoint — create a new key and delete the old one.

Examples

Create an API key

soat create-api-key \
--name "CI/CD Pipeline" \
--project-id proj_V1StGXR8Z5jdHi6B \
--policy-ids pol_V1StGXR8Z5jdHi6B

Store the key value securely — it is never returned again.

List API keys

The raw secret is never included in list or get responses — only the key_prefix is returned.

soat list-api-keys