---
title: API Keys
description: Configure Plainterms organization API keys and understand the
  current security model.
editUrl: true
head: []
template: doc
sidebar:
  order: 3
  label: API Keys
  hidden: false
  attrs: {}
pagefind: true
draft: false
---

Plainterms API keys authenticate `/api/v1` clients. A key belongs to one
organization, not to a user. Endpoints that need user attribution accept a user
email in the request body and resolve it inside the key's organization.

## Configure

Administrators manage API keys in **Settings -> API Keys**.

1. Select **Add API key**.
2. Enter a label.
3. Copy the raw key when Plainterms displays it.
4. Store it in the calling system as a secret.

Plainterms shows the raw key only once. If it is lost, create a new key and
revoke the old key.

## Authentication

Use the raw key as a Bearer token:

```http
Authorization: Bearer ptk_<lookup>.<secret>
```

The raw key format is:

```text
ptk_<22-character base64url lookup id>.<43-character base64url secret>
```

Plainterms authenticates only the `Authorization: Bearer` header for `/api/v1`.
Cookies and other auth schemes are ignored.

## Security Model

- The API key scopes access to one organization.
- The raw key is not stored.
- Plainterms stores metadata in `organization_api_keys` and verifier material
  in the service-role-only `organization_api_key_verifiers` table.
- The verifier is
  `base64url(HMAC-SHA-256(API_KEY_PEPPER for verifier_version, raw key))`.
- Authentication looks up the non-secret lookup id embedded in the key, checks
  revoked/expired metadata, recomputes the verifier, and compares in constant
  time.
- Successful authentication attempts update key usage metadata with
  `last_used_at` and `use_count` through a service-role-only RPC.

The current verifier version is `1`.

## Rotation And Revocation

There is no separate rotate operation or public key-management API today.
Rotate by creating a new key, updating the integration, and revoking the old
key in **Settings -> API Keys**.

Keys do not receive a user-configurable expiry in the current UI. The database
has an `expires_at` field and authentication fails closed if it is set and in
the past, but new keys created through the current settings UI leave it unset.