OAuth 2.1
For an application that acts on behalf of a user. They approve the exact permissions you ask for, you receive a token, and your app never sees their password — or more access than that user already has.
Standard authorization code + PKCE for public clients. No client secret is issued.
| Base URL | https://api.account.telebroad.com |
| Discovery | /.well-known/oauth-authorization-server |
| PKCE | Required, S256 |
| Access token | ~1 hour |
| Refresh token | Rotates on every use |
The flow
- Add your app: your company's admin does this once.
- Discover the endpoints, once.
- Authorize: send the user to consent.
- Exchange the code for tokens.
- Refresh when the access token expires.
1. Add your app
Your company's admin adds the app once. They open Add your app, then enter:
- the app's name,
- the exact https callback URL(s), and
- who can use it: everyone, or chosen roles.
When they save, copy the client_id. There is no client secret and none is ever
issued: this is a public client, and PKCE is what proves the request came from you.
Rules that catch people out:
httpsonly, on a real domain. Nohttp, nolocalhost, no IP address, no wildcard, no#fragment and no desktop-app scheme. See why a callback URL is rejected.- The match is exact, on the full URL. At authorize time your
redirect_urimust be character-for-character one of the saved callback URLs. A trailing slash or anhttp/httpsswap is a callback URL that isn't one of the app's URLs. - One app per company. Two companies can use the same callback URL, each with
its own
client_id. If you serve several customers, each customer adds your app and gives you theirclient_id. - A new app starts with owners only. The admin chooses who else can use it, see Let your staff use an app.
If someone signs in before the app is added, they see This app isn't set up for your company.
2. Discover
curl https://api.account.telebroad.com/.well-known/oauth-authorization-server
Returns the live authorization_endpoint, token_endpoint,
registration_endpoint and scopes_supported.
:::note Read the endpoints, don't copy them
The URLs below are what discovery returns today, and they are the one part of the
platform not under /api/public/v1. Take them from the discovery document at
runtime and a future move costs you nothing.
:::
3. Authorize
Generate a PKCE pair per request:
code_verifier = base64url(32 random bytes) # secret, keep server-side
code_challenge = base64url( SHA-256(code_verifier) ) # goes in the URL
Send the user's browser to the discovered authorization_endpoint:
{authorization_endpoint}
?response_type=code
&client_id=tbapp_…
&redirect_uri=https://app.acme.example/oauth/callback
&scope=webhooks:read%20webhooks:write
&state=RANDOM_PER_SESSION
&code_challenge=CHALLENGE
&code_challenge_method=S256
They return to redirect_uri?code=…&state=…, or ?error=access_denied if they
declined. Verify state matches the value you sent.
4. Exchange the code
Form-encoded, not JSON:
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=tbapp_… \
-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"
}
Check scope — it is what was actually granted, which may be less than you
asked for.
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=tbapp_…
Returns a new access token and a new refresh token. Refresh tokens rotate — persist the newest one every time. Reusing an old one fails.
AI assistants and tools that register themselves
Assistants such as Claude, ChatGPT and the Telebroad Playground cannot ask an admin to add them first, so they register themselves with dynamic client registration. This is only for them. If you are building an app for a company, use Add your app.
curl -X POST https://api.account.telebroad.com/api/v1/oauth/register \
-H 'Content-Type: application/json' \
-d '{
"client_name": "Example Assistant",
"redirect_uris": ["https://assistant.example/oauth/callback"]
}'
{
"client_id": "tbmcp_…",
"redirect_uris": ["https://assistant.example/oauth/callback"]
}
Their redirect_uris must be HTTPS on a host Telebroad has approved, with one
standing exception: loopback callbacks work with no approval and no HTTPS.
A registration that is refused returns
this error.
Loopback callbacks
http://localhost:3000/callback
http://127.0.0.1:53682/callback
http://[::1]:8080/callback
localhost, 127.0.0.1 and [::1] are accepted on any port, over http, with
no approval. This is how desktop and command-line assistants connect. It does
not apply to your company's apps, which take saved https callbacks only.
This is RFC 8252 §7.3, loopback interface redirection. Any port is accepted because a native app binds whatever the OS gives it and cannot register the number in advance.
:::note Why this is safe even though anyone can register a client
The authorization code is delivered to the user's own machine. An attacker who
registered a client pointing at 127.0.0.1 would be sending the code to the
victim's loopback interface, which they cannot reach — to read it they would
already need code running on that machine, and at that point the browser session is
theirs anyway.
That is not true of an internet host, which is exactly why those are approved. :::
The exception matches the host, not a substring of it:
https://localhost.evil.example/cb is an ordinary internet host and still needs
approval.
Call the API
curl https://api.account.telebroad.com/api/public/v1/webhooks \
-H "Authorization: Bearer ACCESS_TOKEN"
Errors
| Status | Meaning | Action |
|---|---|---|
401 | Missing or expired token | Refresh, then re-authorize if that fails |
403 insufficient_scope | Token lacks the scope; the scope field names it | Re-authorize with that scope |
403 permission_denied | Scope is fine — the user's role lacks the permission | Their admin must change their role |
400 on /token | Bad or expired code, PKCE mismatch, or wrong redirect_uri | Restart the flow |
400 invalid_grant on /token | The connection, the app or the user's role was removed, or the refresh token expired | Never retry. See Handling revoked access |
| Sign-in page error | The app isn't set up, the user isn't allowed yet, or the callback URL doesn't match | See OAuth errors |
That third row is the one people lose time on. See Scopes.
Security requirements
- PKCE (S256) is mandatory. No client secret exists to fall back on.
- Redirect URIs must be
httpsand match one of your app's callback URLs exactly. - Rotate refresh tokens — store the newest after every refresh.
- Least privilege — request the minimum scopes; a user who sees a smaller consent screen is a user who says yes.