API Keys
The public key and scoped secret keys explained — how to create, scope, and revoke keys, and how to authenticate with them
Every Bitelio project has one public key and any number of secret keys. The public key is generated once, when the project is created, and can only reach POST /v1/track. Secret keys are minted on demand — from the dashboard or the API — and each one carries an explicit set of scopes: the exact slice of the API it's allowed to touch. There's no single all-powerful secret anymore — create one scoped key per integration, and revoke any one of them without disturbing the rest.
Finding your keys
Open your project and go to Settings → General. The public key sits in its own card near the top, visible to every project member — there's nothing to protect, since it can only ever reach /v1/track. That card also carries a Rotate button, shown to members with the apikeys:manage permission. Scoped secret keys live in the API Keys card just below it. That card requires the same apikeys:manage permission to see at all; without it, you'll get a locked notice instead of a list.
The two kinds of keys
| Key | Prefix | How many | Allowed endpoints | Safe environments |
|---|---|---|---|---|
| Public | pk_ | One per project, replaceable | POST /v1/track only | Client-side code (browser, mobile apps) |
| Secret | sk_ | As many as you create | Whatever its scopes allow (see below) | Server-side only |
The public key is deliberately narrow: it exists so browsers and mobile apps can record events through /v1/track without gaining access to the rest of the API. If it's ever compromised, the exposure is limited to /v1/track (creating or updating contacts and firing workflows via tracked events), not the rest of your project's data.
You can still replace it: Settings → General → Rotate, or POST /apikeys/public/rotate, generates a new pk_ and returns your project with the new value on it. There is no overlap window — the old key stops working immediately, so anything still shipping it stops recording events until you deploy the replacement. Rotating requires the apikeys:manage permission and is recorded in your project's Activity log.
A secret key, by contrast, is only ever as powerful as the scopes you give it — from a single permission (say, just email:send, for a service that only sends transactional mail) up to nearly everything an Owner can do. Bitelio works out which project a request targets from the key itself, so API calls carry no separate project ID parameter.
Scopes
A secret key's scopes are drawn from the same permission catalog that powers roles — campaigns:send, contacts:edit, email:send, and so on. Grant a key only what it needs: a transactional-sending integration needs email:send and nothing else; a read-only analytics dashboard needs analytics:view.
Five permissions can never be granted to any key, no matter who creates it — they're account- or approval-level decisions that have to stay with a signed-in human: apikeys:manage, team:manage, billing:manage, project:manage, and campaigns:approve. Every other permission in the catalog is grantable.
A key also can't exceed the person creating it: you can only hand out scopes you hold yourself. An Owner can grant any grantable scope; anyone else is limited to their own role's permissions.
The SMTP relay (Settings → SMTP) checks scopes too — a key used as the SMTP password needs the email:send scope, or the connection is rejected at AUTH with a message naming the missing scope.
Creating and revoking keys
Creating or revoking a key requires the apikeys:manage permission — by default, only the Owner and Admin roles carry it — and every creation and revocation is recorded in the project's audit log.
To create one: in the API Keys card, click Create key, give it a name you'll recognize later ("Order sync service", "Zapier"), pick its scopes, and optionally set an expiration date. Its plaintext is shown exactly once, in the confirmation dialog immediately after creation — copy it now. Bitelio stores only its SHA-256 hash, so the value cannot be shown again, recovered, or emailed to you later. If you lose it, revoke it and create a replacement.
Every key you've created appears in a table: its name, a short prefix for telling keys apart (sk_ plus a handful of characters — never the full value), its scopes, when it was last used, and when it was created. Revoking a key stops it immediately and permanently; every other key belonging to the project keeps working exactly as before. There's no undo — minting a fresh key with the same scopes is the only way back.
Authenticating requests
Both kinds of keys are sent the same way: as a Bearer token in the Authorization header, on every request.
curl https://api.bitelio.com/v1/send \
-H "Authorization: Bearer $BITELIO_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{ "to": "user@example.com", "subject": "Hello", "body": "<p>Hi</p>" }'const response = await fetch('https://api.bitelio.com/v1/send', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.BITELIO_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
to: 'user@example.com',
subject: 'Hello',
body: '<p>Hi</p>',
}),
});import os
import requests
response = requests.post(
'https://api.bitelio.com/v1/send',
headers={
'Authorization': f"Bearer {os.environ['BITELIO_SECRET_KEY']}",
'Content-Type': 'application/json',
},
json={
'to': 'user@example.com',
'subject': 'Hello',
'body': '<p>Hi</p>',
},
)Using a key on an endpoint outside its type produces a 401 with the code INVALID_API_KEY — for example, a pk_ key against /v1/send. Using a secret key that's valid but missing the scope an endpoint requires produces a 403.
Storing keys safely
- Keep secret keys (
sk_) in server-side environment variables —.env.localwhile developing, your platform's secret manager in production. They must never end up in a frontend build. - Public keys (
pk_) may ship in browser code, but don't treat them as fully public — if someone starts abusing your tracking endpoint, rotate the key from Settings → General and redeploy your client code. - Give each integration its own key, scoped to only what it needs, instead of sharing one key everywhere. A leaked key then costs you one integration's worth of access, not the whole project's.
- Give staging and production separate Bitelio projects, so a leaked staging key has no reach into production data.
Responding to a compromised key
- Revoke the key right away — from Settings → API Keys, or
POST /apikeys/:id/revoke. Once its replacement is rolled out everywhere the old one was used, you're done; there's no shared pair to re-issue. For the public key there's nothing to revoke: rotate it instead, from Settings → General orPOST /apikeys/public/rotate. - Review the Activity tab for sends, contact edits, or campaign changes you don't recognize from the exposure window.
- Look at Billing → Consumption for usage spikes that would indicate abuse.
- If you find unauthorized activity, pass the request IDs from those entries to support.
API reference
POST /apikeys— creates a scoped secret key. Body:{ name, scopes, expiresAt? }. Requiresapikeys:manage; a key can only be granted scopes you yourself hold. Returns the key's metadata plus its plaintext (key) — the only response that will ever contain it.GET /apikeys— lists the project's keys: name, prefix, scopes, and timestamps. Never the plaintext, and never the stored hash.POST /apikeys/:id/revoke— revokes one key immediately, leaving every other key untouched. Idempotent: revoking an already-revoked key just returns its existingrevokedAt.POST /apikeys/public/rotate— replaces the project's public (pk_) key. No body. Returns the project with its newpublicvalue; the previous key stops working at once.
All four require the apikeys:manage permission on a signed-in dashboard session — none of them can be called with an API key.