API keys
An API key authenticates your account. No redirect, no consent screen, no expiring access token — which makes it the right credential for your own backend, and the wrong one to put anywhere a customer could read it.
Create one
In the admin center: Integrations → API Keys.
| Setting | Notes |
|---|---|
| Name | For your own reference. Required. |
| Scopes | What the key may do — see Scopes. Select only what the integration needs. |
| Expiry | Required. There is no never-expiring key. You are emailed before it lapses. |
| Owners | Account users notified about this key's expiry, and shown as responsible for it. Defaults to whoever created it. At least one is required. |
| Allowed IPs | Optional allowlist of exact IPs or CIDR blocks. If set, requests from anywhere else are refused. |
| Flow pin | Only relevant to the flows call-trigger API. Leave unset for the REST API. |
Scopes to select for the whole API
Every endpoint on this API accepts a key — there is no OAuth-only endpoint. A key reaches an endpoint only if it carries that endpoint's scope, so a key meant to do everything needs all ten:
webhooks:read webhooks:write
sms:read sms:send sms:write
messages:read messages:write
users:read reports:read recordings:read
The admin center renders this list from the server, so it is always current —
GET /api/v1/api-keys/scopes is where it comes from, and a scope we add appears
there without a portal release.
:::danger A key with no scopes selected reaches nothing
It does not default to "everything" — it defaults to the flows call-trigger
scope, which no REST endpoint uses. Every request returns
403 insufficient_scope. Select scopes explicitly.
:::
Scopes are fixed at creation and are not extended automatically. When we add an
endpoint that needs a new scope, existing keys 403 on it until their owner
edits the key and selects it.
The key is displayed once, at creation. Store it in your secret manager immediately — we keep only a hash and cannot show it to you again. If you lose it, reissue.
Send it
curl https://api.account.telebroad.com/api/public/v1/sms/lines \
-H "Authorization: Bearer tbk_live_…"
Authorization: Bearer is the only accepted header. There is no X-API-Key
fallback — the same header carries an OAuth token, so your HTTP layer needs one
code path either way.
What a key can reach
A key is checked on six things. All six must pass:
- The key exists and matches its stored hash.
- It has not been revoked.
- It has not expired.
- It carries the scope the endpoint requires.
- The request's source IP is in the allowed IPs, if any are set.
- The object it names is within the key's resource grants, if any are set.
Without resource grants, a key gets the whole account, within its scopes —
there is no user role narrowing it, because a key authenticates an account rather
than a person. GET /users returns every user; GET /sms/lines returns every
SMS-capable number.
Resource grants: narrowing a key to specific objects
A scope says what a key may do. A grant says which objects it may do it to.
{
"scopes": ["sms:send", "messages:read"],
"resources": {
"mode": "selected",
"grants": [
{ "type": "number", "id": "12125550188" },
{ "type": "number", "id": "12125550199" }
]
}
}
That key can text and read messages on those two numbers and nothing else,
even though the account owns forty. Ask for
GET /api/v1/api-keys/resource-types for the current list of grantable types —
today flow and number.
mode | Meaning |
|---|---|
all | No restriction. The default, and what every key created before this feature means. |
selected | The grants apply. At least one grant is required. |
:::caution Grants apply per type, not globally
Under selected, a type with grants is restricted to them, and a type
with no grants is unrestricted.
So a key granted two flows and no numbers is limited on flows and reaches every number. That is deliberate: the alternative would mean pinning a key to a flow silently removed its messaging access, and every resource type we add later would break every existing key.
To restrict a type, grant at least one object of it. :::
Grants are validated when you save them. A flow or number that is not on your account is rejected there and then, rather than stored as a grant that can never match anything.
flowId still works
The older flowId field is the same idea in its original one-flow shape, and
keys and clients still using it are unaffected — it is read as
{"type": "flow", "id": "…"}. If you send both, resources wins, because it is
the one that can express what flowId cannot. Prefer resources for anything
new; flowId cannot describe two flows, or a number.
:::info This is changing A key will run as its owner, so the owner's admin-center role applies on top of the scopes — the same rule OAuth already follows:
effective access = key scopes ∩ owner's role
When it ships, a key whose owner lacks a permission loses the matching access, and this page will say so plainly. Nothing has changed yet; what is documented above is what the API does today. :::
One thing no key unlocks: if the account has sensitive-content re-verification enabled, call recordings are unavailable over the API entirely. That gate wants a live human at a browser, and there is nothing a machine credential can present. See Recordings.
Rotate a key
Reissue keeps the key's identity — same name, scopes, IP allowlist, owners — and mints a new secret with a fresh expiry.
:::caution There is no grace period The old secret stops working the moment the new one is issued. Deploy the new secret first, or accept the gap. If you need overlapping validity, create a second key, migrate, then revoke the first. :::
A revoked key cannot be reissued — revocation is final. Create a new key instead.
Revoke a key
Revoking takes effect on the next request. Do it immediately if a key is exposed — in a log, a git commit, a screenshot, a support ticket.
Because a key carries the whole account within its scopes, a leaked key is an account-level incident, not a per-user one. Revoke first, investigate second.
Keeping a key safe
- Server-side only. Never in a browser bundle, a mobile app, or anything shipped to a customer.
- One key per integration. Shared keys cannot be revoked without breaking everything at once, and the audit trail cannot tell you which system acted.
- Narrow scopes. An export job does not need
sms:send. See Scopes. - Narrow resources. A key that texts from one number should be granted that one number. A leaked key is then limited to it — see Resource grants.
- Use the IP allowlist when the caller has a stable egress address. It is the one control that survives the key itself leaking.
- Never log the key. Log its prefix (
tbk_live_ab…) if you need to correlate.
Audit trail
Writes made with a key are recorded with source api and the key's id, so an
account owner can tell a portal action from an integration's. Reads are not
audited — an entry per number-picker refresh would bury the writes that matter.
Rate limits
None are enforced today, on keys or otherwise. The per-key call-limit fields are not applied to this API. See Rate limits before you build on that.