API · v1 · Read + webhooks

Brand state,on tap.

— Reference / v1

Programmatic, read-only access to your brand. Mint a workspace key, hit /api/v1/*, build dashboards, CI checks, custom reports, and integrations. Every response is versioned (application/vnd.brama.v1+json) so you can pin against the contract.

01

Authentication

Every request carries a workspace-scoped Bearer key. Mint one in Settings → API keys — you'll see the plaintext exactly once; copy it then. The prefix (brama_pk_…) is permanent and visible in the dashboard, so leaks are easy to identify and revoke.

Authorization header
Authorization: Bearer brama_pk_xxxxxxxxxxxxxxxxxxxxxxxx
Scope

Workspace-scoped — the key can read everything in its workspace and nothing outside it. v1 keys carry a single 'read' scope; future write scopes plug in without a migration.

Lifetime

Keys don't expire on a clock. Revoke from the dashboard when you don't need them — revocation is immediate.

Storage

We hash keys with sha256 at rest; the plaintext never touches the database. If you lose a key, mint a new one — we can't recover it.

02

Rate limits

Each endpoint is limited to 60 requests / minute / key. The limits are per-endpoint, so a CI run polling /scoredoesn't starve a dashboard polling /blocks. Exceeding the limit returns 429 with { code: "rate_limited" } — back off + retry.

03

Errors

Every error response uses the same shape so you can branch on code without parsing message text:

Error shape
{
  "error": "Brand not found",
  "code": "not_found",
  "details": null
}
401unauthorized

Missing or invalid API key

403forbidden

Key scope insufficient

404not_found

Resource missing or not in your workspace

429rate_limited

60 req/min/key exceeded

500server_error

Bug on our end (Sentry catches; alert us if persistent)

04

Endpoints

GET/api/v1/brands

List brands

All brands in the API key's workspace, newest first.

curl
curl https://app.brama.so/api/v1/brands \
  -H "Authorization: Bearer $BRAMA_API_KEY"
TypeScript SDK
const { brands } = await brama.brands.list();
Response
{
  "brands": [
    {
      "id": "f1e2d3c4-…",
      "name": "Acme",
      "slug": "acme",
      "status": "complete",
      "consistencyScore": 87,
      "createdAt": "2026-04-01T12:00:00.000Z",
      "updatedAt": "2026-04-20T09:15:00.000Z"
    }
  ]
}
GET/api/v1/brands/:id

Get brand

Brand metadata + completion progress (approved blocks vs expected).

curl
curl https://app.brama.so/api/v1/brands/$BRAND_ID \
  -H "Authorization: Bearer $BRAMA_API_KEY"
TypeScript SDK
const brand = await brama.brands.get(brandId);
Response
{
  "id": "f1e2d3c4-…",
  "name": "Acme",
  "slug": "acme",
  "status": "complete",
  "consistencyScore": 87,
  "brief": {
    "industry": "B2B SaaS",
    "audience": "Engineering managers at Series B startups",
    "problemSolved": "…",
    "keywords": ["focus", "calm", "speed"],
    "toneWords": ["confident", "warm"],
    "vibeDescription": null
  },
  "blocksApproved": 9,
  "blocksTotal": 10,
  "createdAt": "2026-04-01T12:00:00.000Z",
  "updatedAt": "2026-04-20T09:15:00.000Z"
}
GET/api/v1/brands/:id/blocks

List blocks

Every block for the brand in pipeline order. Each block's output shape varies by blockType — check the documented keys for that type. Filter by status with ?status=approved.

curl
curl "https://app.brama.so/api/v1/brands/$BRAND_ID/blocks?status=approved" \
  -H "Authorization: Bearer $BRAMA_API_KEY"
TypeScript SDK
const { blocks } = await brama.brands.blocks(brandId, { status: "approved" });
Response
{
  "blocks": [
    {
      "id": "…",
      "brandId": "…",
      "blockType": "color_palette",
      "status": "approved",
      "isStale": false,
      "version": 3,
      "output": { "primary": "#0F172A", "accent": "#F59E0B", "…": "…" },
      "svg": null,
      "createdAt": "2026-04-01T12:01:00.000Z",
      "updatedAt": "2026-04-12T14:30:00.000Z"
    }
  ]
}
GET/api/v1/brands/:id/score

Brand Score

Single 0-100 metric plus the five sub-scores it composes from. Snapshot reuse on a 2-minute TTL — fresher polls trigger a recompute.

curl
curl https://app.brama.so/api/v1/brands/$BRAND_ID/score \
  -H "Authorization: Bearer $BRAMA_API_KEY"
TypeScript SDK
const score = await brama.brands.score(brandId);
if (score.score < 70) {
  console.warn(`Score dropped: ${score.score}`);
}
Response
{
  "score": 82,
  "completeness": 100,
  "congruence": 87,
  "voiceFidelity": 95,
  "visualFidelity": 88,
  "activation": 50,
  "computedAt": "2026-04-25T08:30:00.000Z"
}
GET/api/v1/brands/:id/watch/summary

Watch summary

Drift summary across the brand's watch targets in a window (default 7d, max 90d). Compact shape for CI checks: 'is anything drifting?'

curl
curl "https://app.brama.so/api/v1/brands/$BRAND_ID/watch/summary?window=14" \
  -H "Authorization: Bearer $BRAMA_API_KEY"
TypeScript SDK
const summary = await brama.brands.watchSummary(brandId, { window: 14 });
Response
{
  "windowDays": 14,
  "targets": [
    {
      "id": "…",
      "url": "https://acme.com",
      "label": "Marketing site",
      "enabled": true,
      "cadenceHours": 24,
      "lastScanAt": "2026-04-25T03:00:00.000Z",
      "averageDrift": 12.4
    }
  ],
  "overallDrift": 12.4,
  "scanCount": 14
}
GET/api/v1/brands/:id/microcopy

List microcopy sets

Every microcopy set generated for the brand, newest first. The full agent output isn't included — fetch the detail route for one set's structure.

curl
curl https://app.brama.so/api/v1/brands/$BRAND_ID/microcopy \
  -H "Authorization: Bearer $BRAMA_API_KEY"
TypeScript SDK
const { sets } = await brama.brands.microcopy.list(brandId);
Response
{
  "sets": [
    {
      "id": "…",
      "label": "Onboarding flow",
      "contextInput": "First-time user setup screens for the dashboard",
      "audience": "Series B engineering managers",
      "createdAt": "2026-04-22T17:11:00.000Z"
    }
  ]
}
GET/api/v1/brands/:id/microcopy/:setId

Get microcopy set

One set's full structured output — categories, items, alternates, tone.

curl
curl https://app.brama.so/api/v1/brands/$BRAND_ID/microcopy/$SET_ID \
  -H "Authorization: Bearer $BRAMA_API_KEY"
TypeScript SDK
const detail = await brama.brands.microcopy.get(brandId, setId);
Response
{
  "id": "…",
  "label": "Onboarding flow",
  "contextInput": "First-time user setup screens for the dashboard",
  "audience": "Series B engineering managers",
  "creditsCost": 6,
  "createdAt": "2026-04-22T17:11:00.000Z",
  "output": {
    "context": "…",
    "toneSummary": "Confident, warm, technical",
    "categories": [
      { "kind": "button", "items": [ … ] }
    ]
  }
}
05

Webhooks

Skip the polling loop. Subscribe an HTTPS endpoint to brand state events from Settings → Webhooks and we'll POST signed payloads as they happen.

Event catalog

block.approved

A block reached approved status.

block.regenerated

A block produced a new version.

block.failed

A block run errored out.

brand.created

A new brand was created in the workspace.

brand.updated

Brand name or brief changed.

brand.exported

A brand-kit export was downloaded.

score.changed

Brand Score moved by ≥3 points vs. last snapshot.

watch.drift_detected

Watch scan landed with drift_score ≥ 25.

webhook.test

Synthetic event from the dashboard's Test button.

Envelope

Every event ships in this shape. id is the idempotency key — receivers should dedupe on it across retries.

Example body
{
  "id": "8f2c1e7d-3b6a-4f9e-9c12-3f8d5a4b1c2e",
  "type": "score.changed",
  "createdAt": "2026-04-25T08:30:00.000Z",
  "workspaceId": "f1e2d3c4-…",
  "data": {
    "brandId": "…",
    "score": 78,
    "previousScore": 82,
    "delta": -4
  }
}

Signature verification

Each request carries X-Brama-Signature: t=<unix>,v1=<hmac>. Recompute HMAC-SHA256(secret, "<t>.<raw-body>") and constant-time compare against v1. Reject anything where t is more than 5 minutes off.

Node.js verifier
import { createHmac, timingSafeEqual } from "node:crypto";

const REPLAY_WINDOW_SECONDS = 300;

export function verify(secret, rawBody, signatureHeader) {
  const parts = Object.fromEntries(
    signatureHeader.split(",").map(p => p.trim().split("="))
  );
  const t = Number.parseInt(parts.t, 10);
  if (!t || !parts.v1) return false;
  if (Math.abs(Math.floor(Date.now() / 1000) - t) > REPLAY_WINDOW_SECONDS) {
    return false;
  }
  const expected = createHmac("sha256", secret)
    .update(`${t}.${rawBody}`)
    .digest("hex");
  if (expected.length !== parts.v1.length) return false;
  return timingSafeEqual(
    Buffer.from(expected, "hex"),
    Buffer.from(parts.v1, "hex"),
  );
}
Python verifier
import hmac, hashlib, time

REPLAY_WINDOW_SECONDS = 300

def verify(secret: str, raw_body: bytes, signature_header: str) -> bool:
    parts = dict(p.strip().split("=", 1) for p in signature_header.split(","))
    try:
        t = int(parts["t"])
        v1 = parts["v1"]
    except (KeyError, ValueError):
        return False
    if abs(int(time.time()) - t) > REPLAY_WINDOW_SECONDS:
        return False
    signed_payload = f"{t}.".encode() + raw_body
    expected = hmac.new(secret.encode(), signed_payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, v1)
Retries

5xx, timeout, 408, 425, and 429 retry up to 5 times with exponential backoff. Other 4xx are terminal — fix the receiver, then manually retry from the dashboard.

Auto-pause

5 consecutive failures auto-pauses the webhook for 24 hours so a broken receiver doesn't drain the dispatch queue. Re-enable from Settings → Webhooks.

Idempotency

Each event has a stable `id` (the event_id). Dedupe on it; retries reuse the same id. The receiver also gets X-Brama-Delivery-Id for per-attempt tracking.

06

TypeScript SDK

Typed client, published to npm. Mirrors the API 1:1 with full type definitions. Install and use:

Install + use
npm install @brama/sdk

import { Brama, BramaApiError } from "@brama/sdk";

const brama = new Brama({ apiKey: process.env.BRAMA_API_KEY! });

try {
  const { brands } = await brama.brands.list();
  const score = await brama.brands.score(brands[0].id);
  if (score.score < 80) {
    process.exit(1); // CI gate on Brand Score
  }
} catch (err) {
  if (err instanceof BramaApiError && err.status === 429) {
    // back off + retry
  }
  throw err;
}
Brama API · v1 · Read + write