API keys and OAuth
API keys (pk_live_…) authenticate server-to-server calls to the Perceive8 API; this page covers creating, listing, and deleting them. The same service also hosts an OAuth 2.1 authorization server (PKCE authorization_code and client_credentials grants) that issues JWT access tokens — primarily used by MCP clients such as Claude Desktop and Cursor. Base URL for all endpoints: https://api.perceive8.com.
API keys
Key management endpoints operate on the current workspace. Select the workspace with the X-Workspace-Id header; when omitted, your Personal workspace is used. All three endpoints require the admin workspace role (owners always qualify).
GET /v1/keys
List all API keys for the authenticated workspace, newest first.
curl https://api.perceive8.com/v1/keys \
-H "X-API-Key: pk_live_xxxxxxxxxxxx" \
-H "X-Workspace-Id: 7c9e6679-7425-40de-944b-e07fc1f90ae7"
Response — 200 OK, an array of key objects:
[
{
"id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "production-backend",
"key_prefix": "pk_live_ab",
"scopes": ["*"],
"rate_limit_rpm": 60,
"is_active": true,
"last_used_at": "2026-08-18T14:02:11Z",
"expires_at": null,
"created_at": "2026-07-01T09:30:00Z"
}
]
| Field | Type | Description |
|---|---|---|
id |
string (UUID) | Key identifier, used for deletion. |
name |
string | Human-readable label. |
key_prefix |
string | First 10 characters of the key, for identification only. |
scopes |
array of strings | Scopes granted to the key. ["*"] means unrestricted. |
rate_limit_rpm |
integer | Per-key request budget, in requests per minute. |
is_active |
boolean | Whether the key can authenticate. |
last_used_at |
string (datetime) or null | Last successful authentication timestamp. |
expires_at |
string (datetime) or null | Expiry timestamp; null means the key does not expire. |
created_at |
string (datetime) | Creation timestamp. |
The full key value is never returned by this endpoint — only the prefix.
POST /v1/keys
Create a new API key for the authenticated workspace. Requires the admin workspace role.
Request body
{
"name": "ci-pipeline",
"scopes": ["*"],
"rate_limit_rpm": 120,
"expires_at": "2027-01-01T00:00:00Z"
}
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | — (required) | Human-readable label for the key. |
scopes |
array of strings | ["*"] |
Scopes to grant. The default ["*"] is unrestricted. |
rate_limit_rpm |
integer | 60 |
Requests-per-minute limit enforced for this key. |
expires_at |
string (datetime) or null | null |
Optional expiry timestamp (ISO 8601). |
Response — 200 OK. The creation response contains every field from GET /v1/keys plus the key field with the full plaintext key:
{
"id": "9b2f7c1e-2d4a-4f8e-9c1d-5e6a7b8c9d0e",
"name": "ci-pipeline",
"key_prefix": "pk_live_xY",
"scopes": ["*"],
"rate_limit_rpm": 120,
"is_active": true,
"last_used_at": null,
"expires_at": "2027-01-01T00:00:00Z",
"created_at": "2026-08-19T06:00:00Z",
"key": "pk_live_xY4…"
}
The key value is returned only once, in this response. Perceive8 stores only a bcrypt hash of the key, so it cannot be recovered later — copy it immediately. New keys always carry the pk_live_ prefix.
curl -X POST https://api.perceive8.com/v1/keys \
-H "X-API-Key: pk_live_xxxxxxxxxxxx" \
-H "Content-Type: application/json" \
-d '{"name": "ci-pipeline", "rate_limit_rpm": 120}'
DELETE /v1/keys/{key_id}
Delete an API key. The key must belong to the authenticated workspace. Requires the admin workspace role.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
key_id |
path | string (UUID) | ID of the key to delete. |
Response — 204 No Content on success. Returns 404 ({"detail": "API key not found"}) when no key with that ID exists in the current workspace.
curl -X DELETE https://api.perceive8.com/v1/keys/3fa85f64-5717-4562-b3fc-2c963f66afa6 \
-H "X-API-Key: pk_live_xxxxxxxxxxxx"
OAuth
Perceive8 implements an OAuth 2.1 authorization server: RFC 8414 metadata discovery, an RFC 7636 PKCE authorization_code flow for interactive (MCP) clients, an RFC 6749 client_credentials grant for server-to-server clients, and RFC 7662 token introspection. Access tokens are HS256 JWTs with a 1-hour lifetime; the token payload carries sub (user ID), scopes, token_type (oauth2), iat, exp, and aud (authenticated).
Endpoints that act on behalf of a user (/authorize, /clients) require a user JWT in the Authorization: Bearer header — API keys are explicitly rejected there with 403. Register and manage clients from the web app or via the API below.
GET /v1/oauth/metadata
Authorization server metadata (RFC 8414). Public — no authentication required. MCP clients fetch this document to discover the authorization and token endpoints.
Response — 200 OK:
{
"issuer": "https://api.perceive8.com",
"authorization_endpoint": "https://api.perceive8.com/v1/oauth/authorize",
"token_endpoint": "https://api.perceive8.com/v1/oauth/token",
"introspection_endpoint": "https://api.perceive8.com/v1/oauth/introspect",
"registration_endpoint": "https://api.perceive8.com/v1/oauth/clients",
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "client_credentials"],
"token_endpoint_auth_methods_supported": ["client_secret_post", "none"],
"code_challenge_methods_supported": ["S256"],
"scopes_supported": ["mcp:analyses:read", "mcp:transcripts:read", "…"],
"service_documentation": "https://perceive8.com/docs/mcp",
"ui_locales_supported": ["en"]
}
scopes_supported lists the full MCP scope catalog (see GET /v1/oauth/scopes).
GET /.well-known/oauth-authorization-server
Serves the same RFC 8414 metadata document as GET /v1/oauth/metadata. Public. MCP clients that probe the well-known discovery URL are answered here.
GET /v1/oauth/authorize
Authorization endpoint for the OAuth 2.1 PKCE flow. The client redirects the user's browser here; on success the server redirects back to the client's redirect_uri with an authorization code. Requires an authenticated Perceive8 user (Authorization: Bearer <user JWT>); API keys are rejected with 403, and requests without valid credentials fail with 401.
Parameters
| Name | In | Type | Description |
|---|---|---|---|
response_type |
query | string | Required. Must be code. |
client_id |
query | string | Required. ID of a registered, active OAuth client. |
redirect_uri |
query | string | Required. Must exactly match one of the client's registered redirect URIs (no substring or wildcard matching). |
scope |
query | string | Optional space-separated scope list. Defaults to mcp:analyses:read mcp:reports:read mcp:query:execute. Each scope must be in the MCP catalog (or *). |
state |
query | string | Optional opaque value echoed back to the client for CSRF protection. |
code_challenge |
query | string | Required. Base64url-encoded SHA-256 of the code verifier, 43–128 characters (RFC 7636). |
code_challenge_method |
query | string | Optional. Only S256 is supported. |
Response — 302 Found redirect to redirect_uri with ?code=…&state=…. The authorization code is single-use and expires after 600 seconds.
Error handling: an unknown client_id, an invalid redirect_uri scheme, or a redirect_uri not registered for the client returns 400 directly (the server does not redirect to untrusted URIs). Other failures — a non-code response_type (unsupported_response_type), a non-S256 method or malformed challenge (invalid_request), or unknown scopes (invalid_scope) — return a 302 redirect to the registered redirect_uri with error and error_description query parameters (plus state when provided).
POST /v1/oauth/token
Token endpoint. Public — no Authorization header; clients authenticate with form fields. The request body must be application/x-www-form-urlencoded.
Grant types
grant_type |
Required form fields | Notes |
|---|---|---|
client_credentials |
client_id, client_secret |
Server-to-server. The issued token carries the scopes registered on the client (default ["*"]); the optional scope form field is accepted but ignored. |
authorization_code |
code, redirect_uri, code_verifier |
PKCE flow. redirect_uri must match the value used at /authorize; code_verifier is checked against the stored S256 challenge. client_id is optional but validated against the code when present. Codes are single-use and expire after 600 seconds. |
Response — 200 OK:
{
"access_token": "eyJhbGciOiJIUzI1NiIs…",
"token_type": "bearer",
"expires_in": 3600,
"scope": "mcp:analyses:read mcp:reports:read mcp:query:execute"
}
Error responses use RFC 6749 error codes in a JSON body: unsupported_grant_type (400), invalid_request (400, missing fields), invalid_client (401, unknown client or bad secret), invalid_grant (400, unknown/expired/used code, redirect URI mismatch, or PKCE verification failure).
curl -X POST https://api.perceive8.com/v1/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=authorization_code" \
-d "code=SplxlOBeZQQYbYS6WxSbIA" \
-d "redirect_uri=http://localhost:8080/callback" \
-d "code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
POST /v1/oauth/introspect
Token introspection (RFC 7662). Requires authentication — any valid credential (user JWT or API key) works. Form field: token (required).
Response — 200 OK. For a valid token:
{
"active": true,
"sub": "a1b2c3d4-0000-4000-8000-abcdefabcdef",
"scopes": ["mcp:analyses:read", "mcp:reports:read"],
"exp": 1787123456
}
Expired or otherwise invalid tokens return {"active": false} (still 200 OK).
POST /v1/oauth/clients
Register a new OAuth client. Requires a user JWT (Authorization: Bearer); API keys are rejected with 403.
Request body
{
"name": "my-mcp-client",
"scopes": ["mcp:analyses:read", "mcp:reports:read"],
"redirect_uris": ["http://localhost:8080/callback"]
}
| Field | Type | Default | Description |
|---|---|---|---|
name |
string | — (required) | Client display name. |
scopes |
array of strings | ["*"] |
Scopes the client may request. |
redirect_uris |
array of strings | [] |
Allowed redirect URIs for the PKCE flow. Each must have a valid URI scheme (http(s):// or a custom scheme). |
Response — 200 OK. The response contains the client record plus client_secret:
{
"id": "5f3a2b1c-1111-4222-8333-444455556666",
"user_id": "a1b2c3d4-0000-4000-8000-abcdefabcdef",
"name": "my-mcp-client",
"client_id": "0a1b2c3d4e5f60718293a4b5c6d7e8f9",
"scopes": ["mcp:analyses:read", "mcp:reports:read"],
"redirect_uris": ["http://localhost:8080/callback"],
"is_active": true,
"created_at": "2026-08-19T06:00:00Z",
"client_secret": "Zk9…"
}
The client_secret is shown only once, in this response — only its bcrypt hash is stored. Copy it immediately. Public PKCE clients that use authorization_code with token_endpoint_auth_method none do not need the secret.
GET /v1/oauth/clients
List your active OAuth clients. Requires a user JWT; API keys are rejected with 403.
Response — 200 OK, an array of client objects with the fields id, user_id, name, client_id, scopes, redirect_uris, is_active, created_at. The client_secret is never included.
GET /v1/oauth/scopes
Return the full MCP scope catalog with descriptions. Public — no authentication required. Use it to decide which scopes to request when registering a client or redirecting to /v1/oauth/authorize.
Response — 200 OK:
{
"scopes": [
{ "scope": "mcp:analyses:read", "description": "Read meeting analyses and metadata" },
{ "scope": "mcp:transcripts:read", "description": "Read full meeting transcripts" }
]
}
Full catalog:
| Scope | Description |
|---|---|
mcp:analyses:read |
Read meeting analyses and metadata |
mcp:transcripts:read |
Read full meeting transcripts |
mcp:reports:read |
Read analysis reports and sections |
mcp:reports:write |
Generate new analysis reports |
mcp:query:execute |
Execute natural language queries against transcripts |
mcp:playbooks:read |
Read playbook templates and configurations |
mcp:playbooks:write |
Clone and configure playbook templates |
mcp:speakers:read |
Read speaker profiles and voice data |
mcp:alerts:read |
Read meeting alerts |
mcp:alerts:write |
Acknowledge and manage alerts |
mcp:aria:read |
Read Aria conversation history |
mcp:aria:write |
Send messages to Aria AI assistant |
mcp:calendar:read |
Read calendar upcoming meetings and settings |
mcp:calendar:write |
Manage calendar upcoming meetings and sync |
mcp:business_profile:read |
Read the workspace business profile |
mcp:business_profile:write |
Modify and reset the workspace business profile |
mcp:agents:read |
Read agent metadata and runtime status |
mcp:agents:write |
Provision, republish, archive, and manage agent secrets |
MCP
Perceive8 exposes an MCP server at https://api.perceive8.com/mcp for AI clients (Claude Desktop, Cursor, and similar). MCP clients authenticate through the OAuth server documented on this page using the authorization_code + PKCE flow: they discover endpoints from /.well-known/oauth-authorization-server, redirect the user to /v1/oauth/authorize, and exchange the resulting code at /v1/oauth/token. The issued JWT carries sub (user ID) and the granted mcp:* scopes, and the MCP server enforces per-tool scope restrictions based on them. See the service_documentation URL in the server metadata (https://perceive8.com/docs/mcp) for client setup instructions.