BricqsBricqs

API auth and keys

Two credentials, one hard boundary: tenant-scoped API keys with mint-time scopes stay on your server, and short-lived participant JWTs are the only thing the browser sees. This page covers the key model, the token mint flow, rate limits, and rotation.

Last updatedJuly 2026

Key takeaways

Quick read
  • API keys go in the X-API-Key header, server-side only. Default scopes: events:write plus gamify:read.
  • Value movement (award, deduct, assign, claim) needs gamify:write, granted explicitly at mint. Token minting and webhook management need admin scope.
  • The browser gets a participant JWT only: minted server-side, bound to one participant, default TTL 5 minutes, max 60.
  • Two environments by key prefix: bq_test_ resolves to your isolated test sibling tenant, bq_live_ to production.
  • Rate limits: 1,000/min per key (default), 5,000/min per tenant, 100 events/min per participant. Respect Retry-After on 429.

The model

One key type, mint-time scopes, two environments

CredentialScopesUse forWhere it lives
bq_live_... (default scopes)events:write, gamify:readEmit events and read participant state from your backend. The standard integration loop.Server only.
bq_live_... (+ gamify:write)adds value movementAward points, assign tiers, award badges, record streaks, claim rewards from trusted backend flows.Server only.
bq_live_... (+ admin)everythingWebhook destination CRUD and participant-token minting.Server only, ideally a dedicated service.
Participant token (JWT)read:state, write:eventsBrowser and mobile: read own state, emit own events, open the SSE stream.Client, expires in minutes.
Default rule:There is no browser-safe API key. The SDK rejects bq_live_/bq_test_ values client-side by prefix sniff; the browser credential is always a short-lived participant token.

Sending the key

X-API-Key on every server request

curl·bash
curl https://api.bricqs.co/api/v1/gamify/state/user_42 \
  -H "X-API-Key: bq_live_aV7p..."

The tenant is resolved from the key itself; there is no separate tenant header.

lib/bricqs.ts (server-only)·ts
const BRICQS_API = "https://api.bricqs.co/api/v1";

export async function bricqs<T>(
  path: string,
  init: RequestInit = {}
): Promise<T> {
  const res = await fetch(`${BRICQS_API}${path}`, {
    ...init,
    headers: {
      "X-API-Key": process.env.BRICQS_API_KEY!,
      "Content-Type": "application/json",
      ...init.headers,
    },
    cache: "no-store",
  });
  if (!res.ok) {
    const body = await res.json().catch(() => null);
    // Standard envelope: { error: { type, code, message, request_id } }
    throw new Error(
      `Bricqs ${res.status} ${body?.error?.code ?? ""}: ${body?.error?.message ?? "unknown"}`
    );
  }
  return res.json();
}

Participant tokens

Short-lived JWTs for client code

Browsers and mobile apps never see the raw API key. Your backend trades an admin-scoped key for a participant-bound JWT and hands only the JWT to the client.

app/api/bricqs-token/route.ts (Next.js)·ts
import { mintParticipantToken } from '@bricqs/sdk-server';

export async function GET() {
  const { token, expiresAt } = await mintParticipantToken({
    adminApiKey: process.env.BRICQS_ADMIN_API_KEY!, // admin-scoped key
    participantId: 'user_42',                       // your stable user ID
    expiresInMinutes: 30,                           // default 5, max 60
  });
  return Response.json({ token, expiresAt });
}
POST /api/v1/auth/participant-token (raw)·bash
# A standard bq_live_... key; "admin" is a SCOPE on the key, not a
# different key format. Keys are always bq_live_/bq_test_ + 32 hex chars.
curl -X POST https://api.bricqs.co/api/v1/auth/participant-token \
  -H "X-API-Key: bq_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "participant_id": "user_42",
    "expires_in_minutes": 30
  }'

# response
{
  "token": "eyJhbGciOi...",
  "participant_id": "user_42",
  "tenant_id": "…",
  "expires_at": "2026-07-19T11:12:00Z"
}

The token is pinned to one participant: it reads that participant's state (read:state) and emits that participant's events (write:events), nothing more. Requests for another participant return 403. There is no refresh token; re-mint before expires_at.

Rate limits

The limits and how to handle them

Per-key limit

1,000 requests/minute by default, configurable per key at creation. 429 carries Retry-After plus X-RateLimit-* headers and code RATE_LIMIT_API_KEY_EXCEEDED.

Per-tenant limit

5,000 requests/minute aggregate across all keys, so one runaway service cannot starve the tenant.

Ingestion burst

100 requests/second IP-level burst guard on the event endpoints, plus the per-participant cap of 100 events/minute (batched events count individually).

Backoff strategy

On 429: respect Retry-After first, then exponential backoff with jitter. Event retries are safe thanks to idempotency keys.

Rotation

Rotate without downtime

text
When to rotate:
- On any suspected leak, immediately.
- On personnel change for anyone with key access.
- Periodically as your security policy dictates.

Process (dashboard: Settings -> API Keys):
1. Create a new key with the SAME scopes as the old one.
2. Deploy it to the service (one key per service makes this surgical).
3. Watch the old key's last_used_at stop advancing.
4. Deactivate the old key. In-flight traffic during cutover never 401s
   because both keys were valid through the window.

Webhook destination secrets rotate differently: there is no rotation
endpoint, so create a second destination with the same URL, verify
deliveries, then delete the old one. See /docs/webhooks.

Common mistakes

The mistakes that leak keys

01Mistake

Putting an API key in NEXT_PUBLIC_ env. Now it is in the JS bundle.

Fix

Server-only env names (BRICQS_API_KEY, BRICQS_ADMIN_API_KEY). The browser gets participant tokens via your mint route.

02Mistake

Using one admin-scoped key for everything, everywhere.

Fix

Default-scope keys for emit-and-read services; gamify:write only on the service that moves value; admin only on the token-mint and webhook-management path. One key per service.

03Mistake

Reusing the live key in test. Test traffic pollutes production data.

Fix

bq_test_ keys hit the isolated test sibling tenant. Assert the expected prefix on service boot.

04Mistake

Minting hour-long participant tokens to avoid writing refresh logic.

Fix

60 minutes is the hard server cap, but short TTLs are the point. Use the SDK's getToken callback and re-mint on demand.

05Mistake

Treating 403 as an auth outage and retrying.

Fix

403 means the credential works but lacks the scope (most often gamify:write) or a participant token crossed identities. Fix scopes; retrying never helps. Branch on error.code from the envelope.

Developer FAQ

Common questions when integrating gamification with Bricqs.

Ready to ship?

Wire it up with the Bricqs SDK or API

Headless SDK for React UIs, REST API for any backend. Same engine behind both.

1 brief to align the room2 mechanics max in version one
What happens next
01
Pick the mechanic
Choose the smallest working system for the brief.
02
Launch without rebuilds
Configure rules and rewards in one place.