Skip to main content

Authentication — OAuth 2.1

Let your application call the Telebroad API on a user's behalf. The user approves the exact permissions you request; you receive a token — and never more access than that user already has. Your app never sees their password.

It's a standard OAuth 2.1 authorization-code + PKCE flow for public clients (no client secret). Base URL: https://api.account.telebroad.com.

Scopes

Request only what you need — space-delimited in the authorize request.

ScopeGrants
webhooks:readList and view webhooks
webhooks:writeCreate, update, delete, enable/disable webhooks
reports:readRead call history and analytics
users:readList the account's users — names, emails, extensions. Personal data.
sms:readList which of the account's numbers can text. Costs nothing and sends nothing.
sms:writeResolve a conversation, or attach an internal note to one. Cannot send anything to anyone.
sms:sendSend SMS from the account's numbers — this spends money. Every message is billed to the account. Request it only if your app sends texts.
recordings:readFetch links to call-recording audio. The most sensitive scope: it is the contents of conversations, not metadata.

The three sms:* scopes are separate on purpose. Building a number picker (sms:read) should not require the ability to spend money, a help-desk integration that only closes threads (sms:write) should not be able to text customers, and a send-only integration should not be able to enumerate the account's numbers. Ask for each only if you do that thing.

The authoritative, live list is in the discovery document's scopes_supported.

A scope is a ceiling, not a key

Holding a scope does not mean you get everything under it. For an OAuth token, the portal permissions of the person who authorized your app still apply on top — a scope can only narrow what they could already do themselves, never widen it.

EndpointScopeAnd, for an OAuth token, the authorizing user's portal permission
GET /usersusers:readUsers — you see the users they can see, no more
GET /sms/linessms:readPhone numbers — only the numbers assigned to them
POST /sms/conversations/…sms:writePhone numbers — the line must be one of theirs
POST /sms/messagessms:sendPhone numbersfrom must be one of theirs
GET /calls/{id}/recordingsrecordings:readCall reports → allow recordings; without it, 403 even with the scope

So the same request can legitimately return different data for two tokens on the same account. If your app sees less than you expect, check the authorizing user's role in the portal before you assume a bug.

API keys are different. A key authenticates the account, not a person, so there is no user role to apply — a key gets the full account within its scopes. Use one for backend integrations that need account-wide reads; use OAuth when your app acts on behalf of a specific user.

One thing no credential can unlock: if the account has sensitive-content re-verification turned on, call recordings are unavailable over the API entirely. See Call recordings.

The flow

  1. Discover the endpoints (once).
  2. Register your app to get a client_id (once).
  3. Send the user to authorize with PKCE; they log in and approve.
  4. Receive the code at your redirect URI and exchange it for tokens.
  5. Call the API with the access token; refresh when it expires.

1. Discover

curl https://api.account.telebroad.com/.well-known/oauth-authorization-server

Returns the exact authorization_endpoint, token_endpoint, registration_endpoint, and scopes_supported. Read them from here rather than hardcoding.

2. Register (once)

note

The OAuth URLs shown below are what discovery returns today. They are the one part of the platform not under /api/public/v1, so take them from the discovery document at runtime rather than copying them — that way a future move costs you nothing.

curl -X POST https://api.account.telebroad.com/api/v1/oauth/register \
-H 'Content-Type: application/json' \
-d '{"client_name":"Acme Webhooks","redirect_uris":["https://app.acme.example/oauth/callback"]}'
{ "client_id": "tbmcp_…", "redirect_uris": ["https://app.acme.example/oauth/callback"] }

Your redirect_uris must be HTTPS and their host must be allow-listed by Telebroad — send us the callback host to add before going live.

3. Authorize (PKCE)

Per request, generate a PKCE pair:

code_verifier = base64url(32 random bytes) # keep secret, per-request
code_challenge = base64url( SHA-256(code_verifier) ) # sent in the URL

Send the user's browser to the discovered authorization_endpoint:

{authorization_endpoint}
?response_type=code
&client_id=tbmcp_…
&redirect_uri=https://app.acme.example/oauth/callback
&scope=webhooks:read%20webhooks:write
&state=RANDOM_PER_SESSION
&code_challenge=CHALLENGE
&code_challenge_method=S256

After they approve, the browser returns to redirect_uri?code=…&state=… (or ?error=access_denied on deny). Verify the returned state matches the one you sent.

4. Exchange the code

The token endpoint is application/x-www-form-urlencoded:

curl -X POST https://api.account.telebroad.com/api/v1/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d grant_type=authorization_code \
-d code=AUTHORIZATION_CODE \
-d code_verifier=CODE_VERIFIER \
-d client_id=tbmcp_… \
-d redirect_uri=https://app.acme.example/oauth/callback
{
"access_token": "eyJ…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "…",
"scope": "webhooks:read webhooks:write"
}

The access token lasts ~1 hour. Check scope — it's what was actually granted.

5. Refresh

curl -X POST https://api.account.telebroad.com/api/v1/oauth/token \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d grant_type=refresh_token \
-d refresh_token=REFRESH_TOKEN \
-d client_id=tbmcp_…

Returns a new access token and a new refresh token — refresh tokens rotate, so always store the latest one.

Errors

StatusMeaning → action
401Missing/expired token → refresh or re-authorize
403 insufficient scope: …Token valid but lacks the scope → re-authorize with it
400 on /tokenBad/expired code, PKCE mismatch, or wrong redirect_uri → restart

Security notes

  • PKCE (S256) is required — no client secret is issued.
  • Redirect URIs must be HTTPS, exactly match a registered URI, and their host must be allow-listed (coordinate new hosts with Telebroad).
  • Least privilege — a scope only narrows what the token can do; it can never exceed the approving user's own access. Request the minimum you need.
  • Rotate refresh tokens — persist the newest; reusing an old one fails.