Developers

Authentication and API keys

How to create, scope, store, rotate, and revoke API keys for the Performance Blocks API, plus auth error handling.

Plan availability: The Performance Blocks API is available on the Agentic plan. Team plan customers can upgrade in Settings → Billing.

The Performance Blocks API uses bearer-token authentication. Every request must include an Authorization: Bearer pb_live_... header. Tokens are organization-scoped API keys that you create and manage in the admin console.

This article covers how to create keys, scope them to least privilege, store them safely, separate test from live, rotate or revoke when needed, and interpret authentication errors.

How authentication works

Every request to https://api.performanceblocks.com/v1/... carries the API key in the Authorization header:

Authorization: Bearer pb_live_4f2a9b1c8d3e7f64a1b2c3d4e5f60718

The API:

  1. Validates the key signature and looks up the owning organization.
  2. Verifies the key is active (not revoked, not expired).
  3. Checks that the requested operation is allowed by the key's scopes.
  4. Records the request against the key's rate-limit window.

If any check fails, the request is rejected before it reaches the resource handler. Successful requests are processed in the context of the key's organization — you cannot reach data outside that organization, even with elevated scopes.

There is no OAuth flow. The public API does not support delegated end-user access tokens. If you need to act on behalf of a specific user inside the application, use the application UI; the API operates at the organization level with audit attribution to the API key, not to a human user.

Creating an API key

API keys are managed by org admins.

  1. In the application, go to Settings → API.
  2. Click Create API key.
  3. Name the key (e.g. Production HRIS sync, BI dashboard read-only, Local development).
  4. Choose an environment — Test or Live.
  5. Choose scopes (see below).
  6. Optionally set an expiration date. Keys without an expiration last until you revoke them.
  7. Click Create. The full key is displayed once — copy it into your secret store immediately. You can never view it again; if you lose it, revoke and re-issue.

After creation, the API key list shows the key's name, prefix, scopes, environment, last-used timestamp, and creator. The full secret is no longer recoverable.

Key prefix convention

Every key has a prefix that tells you what kind of key it is:

Prefix Environment Notes
pb_live_ Live Reads and writes against your production organization data.
pb_test_ Test Reads and writes against your sandbox organization data.

Live and test data are fully separated. A pb_test_ key cannot read or modify live data and vice versa. The prefix is also visible in audit logs, making it easy to confirm that a request originated from the right environment.

Always check the prefix in your secret manager and CI variables before deploying. Mixing environments is the most common cause of "the API call worked locally but did nothing in production" bugs.

Key scopes

Scopes follow a resource:action format. The action is one of read or write. write implies read for the same resource.

Scope Allows
employees:read List, get employees.
employees:write Create, update, deactivate, reactivate, rehire employees; CSV upsert.
observations:read List, get observations.
observations:write Create, update, archive, restore observations.
summaries:read List, get summaries.
summaries:write Create, update, share, approve summaries; submit feedback on a summary.
team_summaries:read List, get, preview team summaries.
team_summaries:write Create, reassign, restore team summaries.
conversations:read List, get conversations and messages.
conversations:write Create conversations and messages; update / delete messages.
objectives:read Read employee and manager objectives.
objectives:write Create, update, transition objectives.
assignments:read List, get feedback assignments and submissions.
assignments:write Create, update, complete assignments.
review_items:read List pending review items.
review_items:write Approve, reject, request clarification.
notifications:read List notifications, count unread.
notifications:write Mark notifications as read.
preferences:read Read user preferences.
preferences:write Update user preferences.
search:read Use the search endpoint.
webhooks:read List webhook subscriptions and delivery logs.
webhooks:write Create, update, delete webhook subscriptions; trigger test deliveries.
session:read Call /session. Included by default in every key.

There is no *:* super-scope. To grant a key full access, explicitly list the scopes it needs. This makes audit log review and incident scoping faster.

Convenience presets

The Settings → API key creation form offers three presets you can use as a starting point and then customize:

  • Read-only — every :read scope, no :write. Good for BI tools and dashboards.
  • HRIS integrationemployees:read, employees:write, webhooks:write. Good for one-way employee sync.
  • Full access — every documented scope. Use only when you genuinely need it; prefer narrower keys.

Storing keys securely

API keys are credentials. Treat them like passwords.

  • Never commit keys to source control. Add .env* files to .gitignore and use a secret scanner in CI.
  • Use a secret manager for production — Cloudflare Workers Secrets, AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, or Doppler.
  • Pass keys through environment variables, not config files checked into the repo.
  • Use separate keys per environment. Don't share a pb_live_ key between staging and production deployments — when one rotates, the other breaks.
  • Use separate keys per integration. If your BI dashboard and your HRIS sync share a key and you need to revoke for one of them, you take the other down too.

A reasonable naming convention is <service>-<environment>-<purpose>, for example bi-dashboard-prod-read or workday-prod-employee-sync.

Rotating keys

Plan to rotate keys at least once a year, and immediately if you suspect compromise (key leaked into a log, ex-employee with access, etc.).

The recommended zero-downtime rotation:

  1. Create a new key with the same scopes as the old key.
  2. Deploy the new key to your secret manager and roll your services with the new value.
  3. Verify traffic is flowing on the new key — the API key list shows a last_used timestamp.
  4. Wait at least a full request cycle (e.g. one webhook delivery, one cron run).
  5. Revoke the old key from Settings → API.

If you rotate by deleting the old key first, in-flight requests will fail with 401.

Revoking keys

To revoke a key:

  1. Go to Settings → API.
  2. Find the key in the list.
  3. Click the menu and choose Revoke.
  4. Confirm.

Revocation is immediate and irreversible. Any request using the revoked key returns 401 authentication_required within seconds. Revoke a key the moment you no longer need it, no matter how recently it was created.

Audit log

Every API key action is recorded in the organization audit log:

  • Key created (who, when, scopes, environment).
  • Key revoked (who, when).
  • Key used to perform a state-changing operation (which endpoint, which resource, which key).

Filter the audit log by API key to see every write a given key has made. This is useful when investigating an unexpected change or scoping the blast radius of a leaked key.

Read operations are not written to the audit log to keep volume manageable. Use rate-limit headers and webhook delivery logs as proxies for read traffic.

Multiple environments

Most teams keep at least three keys per integration:

  • Local development — a pb_test_ key with broad scopes, scoped to a sandbox org.
  • Staging / preview — a pb_test_ key with the same scopes you'll use in production.
  • Production — a pb_live_ key with the narrowest scopes that work.

If you have a sandbox organization for QA, keep its keys distinct from your production organization's keys even when both are pb_live_. The prefix tells you live vs test, but only the key name and audit log tell you which organization.

Auth error responses

The API returns two error codes for authentication problems. The distinction matters: 401 means the API doesn't know who you are; 403 means it knows, but you can't do that.

401 authentication_required

The key is missing, malformed, expired, or revoked.

{
  "error": {
    "code": "authentication_required",
    "message": "API key is missing or invalid"
  }
}

Common causes:

  • The Authorization header was omitted entirely.
  • The header used Token or ApiKey instead of Bearer.
  • The key was copied with a leading or trailing space.
  • The key was revoked or has expired.
  • The key was for the wrong environment (e.g. you sent a pb_test_ key to a production-only endpoint — though this normally surfaces as 403).

403 permission_denied

The key is valid, but it does not have the scope required for the operation, or the operation targets a resource outside its organization.

{
  "error": {
    "code": "permission_denied",
    "message": "Required scope: observations:write"
  }
}

If you see this on an endpoint you expected to work, check the key's scope list in Settings → API. Update the key's scopes (or create a new key with broader scopes), then retry.

Example requests

curl

# Read your session
curl https://api.performanceblocks.com/v1/session \
  -H "Authorization: Bearer $PB_API_KEY"

# Create an observation (write scope required)
curl -X POST https://api.performanceblocks.com/v1/observations \
  -H "Authorization: Bearer $PB_API_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "employee_id": "emp_01HXX...",
    "type": "strength",
    "observation": "Led the Q2 launch retro and surfaced two systemic gaps.",
    "observation_date": "2026-04-12"
  }'

TypeScript

A small client wrapper that pulls the key from the environment, sets sensible defaults, and surfaces errors:

const BASE_URL = 'https://api.performanceblocks.com/v1';

export class PerfBlocksClient {
  constructor(private readonly apiKey: string) {
    if (!apiKey?.startsWith('pb_')) {
      throw new Error('Missing or malformed Performance Blocks API key');
    }
  }

  async request<T>(
    path: string,
    init: RequestInit & { idempotencyKey?: string } = {}
  ): Promise<T> {
    const headers = new Headers(init.headers);
    headers.set('Authorization', `Bearer  

For environments without process.env, pull the key from your platform's secret API instead — for example platform.env.PB_API_KEY on Cloudflare Workers.

Checklist for going to production

Before you flip a workflow from pb_test_ to pb_live_:

  • The production key is stored in your secret manager, not in source control or a config file.
  • The key has only the scopes the integration needs — nothing more.
  • You have a separate key for staging / preview that uses pb_test_ against the sandbox organization.
  • You have a documented rotation plan and an owner for the key.
  • You've subscribed to the Webhooks you need rather than polling.
  • Your client respects Rate limits and retries on 429 with backoff.

© 2026 Performance Blocks. All rights reserved.