API keys
An API key proves a request is coming from your account. There is no login screen, no approval step, and no token that expires after an hour — which makes it the right choice for your own server software, and the wrong thing to put anywhere a customer could read it.
Create one
To create a key, open the admin center and go to Integrations → API Keys, then click to add a new one. You will be asked to fill in a few settings — here is what each one means:
| Setting | What it means |
|---|---|
| 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. |
| Limit it to specific objects | Optional resource grants. The flow picker appears once flows:call is ticked; it is only used by the flow call trigger. |
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 twelve:
webhooks:read webhooks:write
sms:read sms:send sms:write
messages:read messages:write
users:read reports:read recordings:read
fax:read fax:send
The admin center pulls this list live from the server
(GET /api/v1/api-keys/scopes) — new scopes appear there automatically.
Example — turn on only the scopes you use. Each scope unlocks one kind of action, so tick only the ones your key actually uses:
| To do this | It calls… | Scope |
|---|---|---|
| List your texting numbers | GET /sms/lines | sms:read |
| Send a text | POST /sms/messages | sms:send |
| Read conversations | GET /sms/conversations | messages:read |
| List account users | GET /users | users:read |
| Get a call recording | GET /calls/{callid}/recordings | recordings:read |
| Manage webhooks | GET /webhooks | webhooks:read |
You don't call GET /api/v1/api-keys/scopes yourself — it is just where the admin
center gets its checkbox list. You tick the scopes you want when you create the key.
It does not default to "everything" — it defaults to flows:call, the
flow call-trigger scope, which no REST endpoint uses. Every request returns
403 insufficient_scope. Select scopes explicitly.
The scopes are set when you create the key and are not added to on their own. If
we later add an endpoint that needs a new scope, your existing keys are refused
(403) on it until someone edits the key and turns that scope on.
The key is shown only once, right when you create it. Copy it somewhere safe straight away — we store only a scrambled (hashed) version and can never show it to you again. If you lose it, reissue it.
Use it in a request
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. (The one exception is the
flow call trigger, outside this API, which also
reads X-API-Key. Use Authorization there too.)
What a key can reach
Every request with a key is checked against six things. All six must pass:
- The key is genuine — it matches the scrambled (hashed) copy we saved when it was created.
- It has not been switched off (revoked).
- It has not expired.
- It has the scope (permission) the endpoint needs.
- The request comes from one of the allowed addresses, if you set any.
- The thing it is trying to touch is within the key's allowed objects (resource grants), if you set any.
Unless you limit it to specific objects, a key reaches the whole account, within
its scopes — no person's role narrows it, because a key stands for the account,
not an individual. For example, GET /users returns every user on the account.
Resource grants: narrowing a key to specific objects
A scope says what a key may do (e.g. send texts); a grant says which exact things it may do it to (e.g. which specific numbers it may text from). With no grants, a scope covers your whole account; with grants, only the items you list.
{
"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, number, hha and phone.
A number grant may name a texting number, a fax line or a voice number.
It limits which lines a key may text from, read messages on and act on
conversations for, and which fax lines it may send faxes from and read faxes on.
It is one list for all of them, so a key granted only a fax line is also limited
to that line for SMS.
On the fax endpoints a grant narrows the role of the user the key runs as; it
never adds a number that role does not have. See
Faxes.
A number grant also limits caller ID. A key with number grants may only
show one of those numbers as the caller ID on a call it places with
POST /calls. It may name any number of the account: a texting number, a fax
line or a voice number.
Phones: required for placing calls
A phone grant names a phone (line) the key may ring first with
POST /calls, for example {"type": "phone", "id": "6589455"}. Each phone
must be a phone of your account; it does not have to belong to the user the key
runs as.
A key with calls:make must list its phones. Unlike every other type, no
phone grants does not mean unrestricted: a key with no phones selected
cannot place calls, and every POST /calls gets 403 permission_denied
("this API key has no phones it may ring; add phones to the key to place
calls"). See Click to call.
mode | Meaning |
|---|---|
all | No limits. The default, and what every key created before this feature means. |
selected | The grants apply. You must list at least one. |
Under selected, a type of thing that has grants is limited to those grants,
and a type with no grants stays open.
So a key granted two flows and no numbers is limited on flows and reaches every
number — and a key granted only numbers can still call every flow, if it holds
flows:call. 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.
The one exception is phone: with no phone grants, a key cannot place calls at
all. See Phones.
A flow grant names a flow by its id — the number at the end of the flow
editor's address. It is enforced by the
flow call trigger: a key with flow grants may call
only those flows, and never a flow on another account, whatever its grants say.
An hha grant names one of the account's HHA Exchange connections by its id. A
key with hha grants looks callers up only in those connections, and only in
connections the user it runs as may already use; a grant never adds one.
Grants are validated when you save them. A flow, number or phone that is not on your account is rejected there and then, rather than stored as a grant that can never match anything. A number may be a texting number, a fax line or a voice number.
flowId still works
The older flowId field is the same idea in its single-flow form — it names one
call flow and is read as {"type": "flow", "id": "…"}. Anything using it keeps
working.
A grant can be a flow (a call flow — for the call-trigger capability) or a
number (a phone number — for the SMS endpoints). flowId can only ever name a
single flow: never a second flow, and never a number. resources can express all of
it, so if you send both, resources wins. Use resources for anything new:
Old way — one flow only:
{ "flowId": "7f3a" }
New way — any mix of flows and numbers:
{
"resources": {
"mode": "selected",
"grants": [
{ "type": "flow", "id": "7f3a" },
{ "type": "number", "id": "12125550188" }
]
}
}
A key will run as its owner, so the owner's admin-center permissions will limit it too, on top of its scopes — the same rule OAuth already follows: what the key can do will be its scopes, limited by the owner's role. Today a key's owner role is ignored; after this change, a key whose owner lacks a permission loses that access too. Nothing has changed yet; everything above is how the API works today.
One thing no key can unlock: if the account has sensitive-content re-verification turned on, call recordings cannot be reached through the API at all. That check needs a real person at a browser, and a key has nothing to show it. See Recordings.
Rotate a key
Reissuing keeps everything about the key the same — name, scopes, allowed addresses, owners — and just creates a new secret with a fresh expiry date.
When you reissue a key, its old secret stops working the instant the new one
is created — the two secrets never overlap. So put the new secret in place first,
or expect a short gap — a brief window where your requests fail (401) because
your software is still sending the old, now-dead secret.
If you need an overlap, do not reissue — instead create a second, separate key. Both keys are valid at the same time, so you can move your systems over to the new one at your own pace, and only then turn off the old one.
A key you have turned off (revoked) cannot be brought back — that is final. Create a new key instead.
Revoke a key
Turning off a key takes effect on its next request. Do it right away if a key is exposed — in a log, a commit, a screenshot, or a ticket. Because a key reaches the whole account within its scopes, a leak is account-wide: turn it off first, investigate after.
Keeping a key safe
- Keep it on your own server. Never inside a web page, a mobile app, or anything you hand to a customer.
- One key per system. If several systems share a key, you cannot turn it off without breaking them all at once — and the history cannot tell you which system did what.
- Give it as few scopes as possible. A report job — a program that only reads your
data to build a report — does not need
sms:send. See Scopes. - Limit it to the objects it needs. A key that texts from one number should be granted just that number, so a leak cannot spread past it — see Resource grants.
- Use the allowed-addresses list. Set it when your system sends from a fixed internet address — it is the one protection that still helps even if the key itself leaks.
- Never write the key into your logs. If you need to tell which key a log entry belongs
to, record only its first few characters (
tbk_live_ab…), never the whole value.
Audit trail
Every change made with a key is recorded with source: api and the id of the
key that made it — the key's non-secret label, not the tbk_live_… secret. So in
the account's change history, an owner can tell an integration's change from a
person's and trace it to the exact key. For example, a number blocked by a key is
tagged source: api with the key's id, not attributed to a person.
An owner reviews this history in the admin center; there is no API to read it back. Reads are not recorded — logging every list refresh would bury the changes that matter.
Rate limits
None yet — the per-key call-limit fields are not applied to this API. See Rate limits before relying on that.