Getting StartedAuthentication

Authentication

Learn how to authenticate your API requests with Flameup

Authentication Overview

Flameup uses API keys to authenticate requests. Each API key is scoped to a specific workspace and has granular permissions that control what operations it can perform.

Keep your API keys secure. Never expose them in client-side code, public repositories, or logs. If a key is compromised, revoke it immediately from your dashboard.

API Key Format

Flameup API keys follow this format:

{prefix}.{secret}

Where:

  • Prefix: {type}_{env}_{workspace_short}_{random} (e.g., ws_live_abc12345_abc123)
  • Secret: 64 hexadecimal characters

Full example:

ws_live_abc12345_abc123.a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2

The prefix includes a shortened workspace ID for identification. Only the prefix is stored in readable form — the secret is stored as a hash, which is why a key cannot be retrieved after creation.

Two kinds of key

The prefix tells you where a key is allowed to live.

PrefixWhere it belongsWhat it can hold
ws_live_Your server only. Backends, scripts, integrationsAny permission, including *
pk_live_Inside your app or website. Shipped to users' devicesevents:write, devices:register, people:identify — and nothing else

These are the only two prefixes. The environment label on a key (live or test) does not change them — a test-labelled key is still minted with a ws_live_ or pk_live_ prefix.

A public key is restricted by the server, not by convention: a permission outside that list is rejected when the key is created and when it is updated, so one cannot be widened later.

Never ship a ws_ key in a mobile app or a web page. A ws_ key can be granted permissions a public key cannot hold — reading your whole person table, sending messages to anyone — so a leaked one is only as limited as whatever it happens to carry. The pk_ prefix exists so a secret scanner can tell the two apart: allowlist pk_, alert on ws_.

Authentication Methods

Authorization Header

Pass your API key in the Authorization header using the Bearer scheme. This is the only method — there is no query-parameter or cookie alternative:

const response = await fetch(
  'https://api.flameup.ai/api/v1/people',
  {
    method: 'GET',
    headers: {
      'Authorization': 'Bearer ws_live_abc12345_abc123.your_secret_here'
    }
  }
);

What an API key can reach

An API key reaches the ingestion and messaging surfaces. Everything else in Flameup is built in the dashboard and authenticated with your dashboard session, not a key.

With an API key:

SurfaceRoutes
IngestionPOST /identify (people:write or people:identify), /track, /track/batch (events:write)
PeopleGET/POST/PUT/DELETE /people, /people/{id}, /people/search, /people/upsert, /people/batch
EventsGET /people/{external_id}/events, GET /events/search
DevicesPOST/DELETE/GET /people/{external_id}/devices
PushPOST .../push/send, /send/batch, POST .../push/tokens/validate, POST .../push/tokens/suppress, DELETE .../push/tokens/suppress/{id}, GET .../push/status/{message_id}
CampaignsPOST /campaigns/{id}/trigger — starting one, not authoring it

Dashboard only — an API key will not work: workflows, segments, message templates, schedules, campaign create/edit/delete, messaging channel setup, team and workspace management.

If a call returns 403 and the permission looks right, check this list first — the likeliest cause is that the route is a dashboard one and no key can reach it.

Permissions

API keys have granular permissions that control access to different resources:

Permission Categories

These twelve strings are the complete set any route checks:

CategoryPermissionsDescription
Eventsevents:read, events:writeTrack user events, and read them back
Peoplepeople:read, people:write, people:identify, people:deleteManage user profiles. people:identify is the narrow, app-safe one — see below
Campaignscampaigns:triggerTrigger webhook campaigns. There is no key permission for creating or editing campaigns — that is dashboard-only
Devicesdevices:register, devices:read, devices:writedevices:register registers and unregisters only, and is safe to embed in an app; devices:read lists only; devices:write does both
Pushpush:send, push:readpush:send sends push (single and batch), validates tokens, and suppresses or unsuppresses a device token (POST .../push/tokens/suppress, DELETE .../push/tokens/suppress/{id}) — the only way to block a token from delivery. push:read gates the working push-status lookup (GET .../push/status/{message_id}) plus the delivery-history and analytics routes that answer 501 today. Neither is grantable on a public key: sending renders a template against the recipient's stored record
Full Access*Wildcard granting access to all resources

Thirteen other permission names (events:list, people:list, campaigns:read, campaigns:write, analytics:read, analytics:write, segments:read, segments:write, webhooks:read, webhooks:write, workspace:read, workspace:write, admin) are accepted when a key is created but gate no route — a key holding only them can reach nothing. Do not grant them expecting access. admin is the one to watch: despite the name it grants nothing at all, and it is not a synonym for *.

people:identify deserves its own note. It authorizes POST /identify and nothing else — creating or updating one person's traits by an identifier you already know. It is deliberately narrower than people:write, which also carries /people CRUD and a 1000-operation batch route, and it is the only people: permission a public key may hold.

Common Permission Sets

For integrations that only need to view data:

{
  "permissions": [
    "events:read",
    "people:read"
  ]
}

These two cover person and event reads. An API key can hold other read scopes too — add devices:read to list a person's registered devices, or push:read to look up a push delivery status. What no key can reach is campaign and analytics reads: those are dashboard-only surfaces.

Creating API Keys

Open Dashboard

Log in to your Flameup Dashboard and navigate to Settings > API Keys.

Say where the key will be used

On my server — you pick the permissions.

In my app or website — there are no permissions to pick. Flameup applies the public-key set for you, because there is exactly one combination that is safe to ship and choosing it by hand is a chance to get it wrong.

Name it

A descriptive name (e.g. "Backend Server", "Android app"). This is what you will see in the list later when deciding whether a key can be revoked.

Copy Your Key

Copy the full API key immediately. For security, the full key is only shown once.

The full API key is only displayed once when created. Store it securely - you cannot retrieve it later.

Creating API Keys via the API

Key creation is authenticated with a dashboard session token, not with an API key — a key cannot mint another key. Use this when provisioning programmatically.

Endpoint: POST /api/v1/workspaces/{workspace_id}/api-keys

curl -X POST "https://api.flameup.ai/api/v1/workspaces/{workspace_id}/api-keys" \
  -H "Authorization: Bearer {dashboard_token}" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Backend Server",
    "permissions": ["events:write", "people:write", "people:read"],
    "environment": "live"
  }'
FieldRequiredNotes
nameyesShown in the dashboard list
permissionsyesSee Permissions. Validated against key_type
key_typenosecret (default) or public
environmentnolive (default) or test. A label only — see below
expires_atnoISO 8601. Must be in the future
descriptionnoFree text, shown alongside the name
ip_whitelistnoArray of single IPs ("203.0.113.7") or CIDR blocks ("203.0.113.0/24"). See below
The IP allowlist is enforced only when the server can trust the client IP it sees. Behind an ingress or load balancer that is not in the server's trusted-proxy set, a mismatch is logged at WARN and the request proceeds — enforcing there would reject every legitimate caller, since the observed address would be the proxy's. Rejection happens only in deployments where the client IP is trustworthy (TRUSTED_PROXIES configured, or API_KEY_IP_ALLOWLIST_ENFORCE set). Treat the allowlist as defence in depth, not as the sole control on a key. A malformed entry never matches — it fails closed.

Response (201):

{
  "success": true,
  "api_key": {
    "id": "8f2a...",
    "key_prefix": "pk_live_abc12345_abc123",
    "key": "pk_live_abc12345_abc123.a1b2c3...",
    "permissions": ["events:write", "devices:register", "people:identify"],
    "key_type": "public"
  },
  "warning": "This is the only time the full API key will be shown. Please save it securely."
}

key is the value you authenticate with. It appears here and in a refresh response — nowhere else. key_prefix is safe to log and to display later.

Asking for a permission that a public key may not hold returns 400 with the allowed list in the message, before any key is created — so a rejected request never leaves a half-made key behind.
environment does not isolate data. It is recorded on the key and shown in the dashboard, but no query filters by it: a test-labelled key writes to the same tables a live one reads (and both carry the same ws_live_/pk_live_ prefix). To keep test traffic out of your real data, use a separate workspace — that boundary is enforced on every read and write.

Managing Keys

The same dashboard-token surface carries the full key lifecycle:

MethodEndpointDoes
GET/workspaces/{workspace_id}/api-keysList keys (prefixes and metadata, never secrets)
GET/workspaces/{workspace_id}/api-keys/{key_id}One key's metadata
PUT/workspaces/{workspace_id}/api-keys/{key_id}Partial update — see below
DELETE/workspaces/{workspace_id}/api-keys/{key_id}Revoke the key
POST/workspaces/{workspace_id}/api-keys/{key_id}/refreshRotate the secret — see Key Rotation
GET/workspaces/{workspace_id}/api-keys/{key_id}/usageUsage statistics for the key

PUT updates only the fields present in the body: name, description, permissions, environment, expires_at, ip_whitelist. Permissions are revalidated against the key's stored type, so a public key cannot be widened after the fact. expires_at is three-state: omit it to leave the expiry unchanged, send a timestamp to set it, or send an explicit null to clear it and make the key non-expiring.

Security Features

Key Rotation

Regularly rotate your API keys for security. The refresh endpoint mints a new secret for the same key: the key_prefix, id, and permissions are unchanged, and the response carries the new full key — the only time it is shown. The old secret stops working immediately.

curl -X POST "https://api.flameup.ai/api/v1/workspaces/{workspace_id}/api-keys/{key_id}/refresh" \
  -H "Authorization: Bearer {dashboard_token}"

Error Responses

401 Unauthorized

Returned when no API key is provided or the key is invalid:

{
  "error": "Unauthorized",
  "message": "Invalid or expired API key",
  "details": "Please provide a valid API key in the Authorization header: Bearer <your_api_key>"
}

403 Forbidden

Returned when the API key doesn't have the required permissions:

{
  "error": "Forbidden",
  "message": "Insufficient permissions"
}

429 Too Many Requests

Returned when rate limits are exceeded:

{
  "error": "Rate limit exceeded",
  "message": "Too many requests. Please try again later.",
  "scope": "ws-ingest",
  "gate": "local"
}

See Rate Limits for what scope and gate mean.

Best Practices

Use a separate workspace per environment, with its own keys. Separate keys alone do not separate data — only the workspace boundary does. Never use production keys in development.