Skip to content
Dashboard

API Keys

Create, list, and revoke the keys that authenticate every other endpoint.

These three routes are session-authenticated, not Bearer-authenticated. They read your Supabase auth cookie. You cannot create, list, or revoke an API key using an API key, so a Authorization: Bearer bp_... header against /api/v1/api-keys will not work. In practice you manage keys in the dashboard at brainpercent.app/chat/developers. The routes below are documented because that page calls them from the browser.

Each user can have a maximum of 10 active keys. Keys follow the format bp_ plus 64 hex characters. They are SHA-256 hashed at rest and shown exactly once, at creation. A lost key cannot be recovered, revoke it and create a new one.

MethodEndpointAuthDescription
POST/api/v1/api-keysSessionCreate a new API key
GET/api/v1/api-keysSessionList your API keys
DELETE/api/v1/api-keys/:idSessionRevoke an API key
POST/api/v1/api-keys

Create a new API key. The full key value is returned only in this response, store it immediately. Session-authenticated, so this must be called from a signed-in browser. Maximum 10 active keys per user.

Request Body

NameTypeRequiredDescription
namestringRequiredHuman-readable label for the key (1-100 characters)
permissionsstring[]OptionalPermissions to grant: read, write, delete(default: ["read"])
scopesstring[]OptionalResource scopes to restrict access: articles, social, projects, user
expires_in_daysintegerOptionalKey expiration in days (1-365). Omit for no expiration.
Request
// Runs in a signed-in browser session. The auth cookie is the credential.
const res = await fetch('https://www.brainpercent.app/api/v1/api-keys', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  credentials: 'include',
  body: JSON.stringify({
    name: 'My App Key',
    permissions: ['read', 'write'],
    scopes: ['articles', 'social'],
    expires_in_days: 90,
  }),
});

const { data } = await res.json();
// data.key is the only time you will ever see the full key
console.log('API Key:', data.key);
201Response
{
  "success": true,
  "data": {
    "id": "b7e2f1a0-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
    "name": "My App Key",
    "key": "bp_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4e5f6a1b2",
    "key_prefix": "bp_a1b2",
    "permissions": [
      "read",
      "write"
    ],
    "scopes": [
      "articles",
      "social"
    ],
    "created_at": "2026-02-01T12:00:00Z",
    "expires_at": "2026-05-02T12:00:00Z"
  }
}
GET/api/v1/api-keys

List the API keys on your account. Returns masked key prefixes only, never the full key. Session-authenticated.

Request
const res = await fetch('https://www.brainpercent.app/api/v1/api-keys', {
  credentials: 'include',
});
const { data } = await res.json();
console.log(`You have ${data.length} API key(s)`);
data.forEach(k => console.log(`  ${k.name} (${k.key_prefix}...) active=${k.is_active}`));
200Response
{
  "success": true,
  "data": [
    {
      "id": "b7e2f1a0-3c4d-4e5f-8a9b-0c1d2e3f4a5b",
      "name": "My App Key",
      "key_prefix": "bp_a1b2",
      "permissions": [
        "read",
        "write"
      ],
      "scopes": [
        "articles",
        "social"
      ],
      "created_at": "2026-01-15T10:00:00Z",
      "last_used_at": "2026-02-01T08:30:00Z",
      "expires_at": "2026-04-15T10:00:00Z",
      "is_active": true
    },
    {
      "id": "c8f3a2b1-4d5e-4f6a-9b0c-1d2e3f4a5b6c",
      "name": "CI/CD Pipeline",
      "key_prefix": "bp_c3d4",
      "permissions": [
        "read"
      ],
      "scopes": [
        "articles",
        "projects"
      ],
      "created_at": "2026-01-20T14:00:00Z",
      "last_used_at": null,
      "expires_at": null,
      "is_active": true
    }
  ]
}
DELETE/api/v1/api-keys/:id

Revoke an API key. Any application using it loses access immediately and the key cannot be restored. Session-authenticated.

Request
const keyId = 'b7e2f1a0-3c4d-4e5f-8a9b-0c1d2e3f4a5b';

const res = await fetch(
  `https://www.brainpercent.app/api/v1/api-keys/${keyId}`,
  {
    method: 'DELETE',
    credentials: 'include',
  }
);
const result = await res.json();
console.log(result.message);
200Response
{
  "success": true,
  "message": "API key revoked"
}

Error Shape

These three routes return a flat error object, not the structured envelope the Bearer-authenticated endpoints use:

RoutesError body
/api/v1/api-keys{ "error": "..." }
every Bearer route{ "success": false, "error": { "code", "message" } }

If you share one error handler across both, branch on whether error is a string or an object.

Permission Reference

PermissionAllowsExample Endpoints
readRetrieve resources (GET requests)GET /articles, GET /projects, GET /user/credits
writeCreate and modify resources (POST requests)POST /articles/generate, POST /social/generate, POST /social/publish
deleteRemove resources (DELETE requests)None of the Bearer endpoints documented here require it. Revoking a key is session-authenticated.

A key created without an explicit permissions array gets ["read"]. Every write endpoint returns 403 for such a key, which is the most common cause of an unexplained FORBIDDEN.

Scope Reference

ScopeEndpoints Covered
articles/api/v1/articles/*, /api/v1/articles/generate, /api/v1/articles/:id/status
social/api/v1/social/content/*, /api/v1/social/generate, /api/v1/social/publish
projects/api/v1/projects/*
user/api/v1/user/credits, /api/v1/user/usage

A request is authorized only when the API key has both the required permission AND the matching scope. For example, generating an article requires the write permission and the articles scope.