Connected Apps (OAuth scoped access)
A Connected App is an OAuth 2.0 third-party client that you publish and a customer installs into their Orbit org. Instead of asking the customer to paste a full-access API key into your product, your app holds a token limited to exactly the scopes it needs — and the customer can revoke it at any time. Connected Apps are the integration path when you’re building something another Orbit customer installs. To integrate with your own org, use an API key and skip this page. Scope strings, the install grant, and the tokens your app runs with are per-org and per-installation — a customer can disconnect your app from their own settings, revoking only the access that relationship created (Outreach-style scoped controls stay tenant-owned throughout).Why a scoped app instead of an API key
An API key minted in a customer’s dashboard, then pasted into your product, typically grants everything that customer can do — send messages, read contacts, launch campaigns. The authorization-code grant here narrows that down:- The customer reviews a consent screen listing each capability before they install.
- The installed token is capped at the
developerrole even with write scopes —admin, the*wildcard, and anything billing-related are never grantable to a third-party app. - Read scopes resolve to
viewer; write scopes resolve todeveloper. Both the consent preview and the tokens themselves enforce that cap. - A closed catalog of scopes means your
scopeparameter is validated against a fixed list, and anything outside it is rejected up front rather than partially granting.
The scope catalog is a subset of the same taxonomy the API-key middleware enforces. An installed app can never exceed
developer; it is not an org admin with an OAuth wrapper.Get the manifest and the scope catalog
Open Developer → Connected Apps in the dashboard. That page hosts three things:- Download app manifest — generates
orbit-<your-org>.oauth-app.json, a deterministic client-registration document pre-wired to your org’s API host. It declares the authorization-code + PKCE flow metadata (authorisation, token, and revocation endpoints;S256code-challenge method; token-endpoint auth viaclient_secret_basicorclient_secret_post) plus a placeholderclientName,redirectUris,logoUri, andhomepageUrifor you to replace, and embeds the full scope catalog. The manifest embeds no credential — it is a definition document you submit to list your app, not a token. - Grantable scopes — the closed catalog below the download button. Tick scopes to build the consent request; sensitive scopes (account settings) are flagged with a warning icon.
- Consent screen preview — renders exactly what an installing customer will see for the selected scopes, and flags any scope outside the catalog that would be rejected.
owner, admin, or developer role on the calling key or session):
consent-preview splits the scope string into granted and rejected lists, so you can fix a bad request before shipping it. Default requests in the manifest start read-only (messages:read contacts:read conversations:read) — widen them only with scopes your app genuinely calls.
The grantable catalog
messages,contacts,conversations,campaigns,flows,agents,numbers,voice,video,inbox,templates,webhooks,knowledge—:readand:writepairs.analytics—:readonly (usage and delivery analytics; there is no write scope).settings—:readonly, and flagged sensitive because it touches account and organization settings.
resources[] (the resource families) alongside the per-scope metadata, which is useful when you build your own picker.
Initiate the authorization-code flow from your product
Your registered app receives aclient_id and client_secret (shown once) when it’s listed. To install into a customer’s org, send them through Orbit’s authorization endpoint from the manifest:
redirect_uri with code and state. Always validate state — a mismatch means the callback didn’t come from your own flow.
Proof-key for code exchange is required on authorization (PKCE,
S256). A public client that cannot hold a client_secret does not need one — the challenge pair is the binding.Exchange the code for tokens
POST the code to the token endpoint the manifest declares, with the matchingcode_verifier:
client_secret_basic (HTTP Basic, shown above) and client_secret_post (credentials in the body) are accepted — pick one and keep your client consistent. The response is a standard OAuth token payload with access_token, refresh_token, expires_in, and token_type: Bearer. Treat the returned scope as authoritative and store it alongside the tokens; it should match what you requested.
Refresh and revoke tokens
Access tokens are short-lived. Exchange therefresh_token at the same token endpoint before expiry:
401 mid-call, refresh once and retry once rather than re-installing.
Revocation is two-sided:
- Your side (server-to-server): POST the token to the revocation endpoint the manifest declares. Revoking the refresh token ends the session cleanly; also revoke the orphaned access token if you hold both.
- Customer side (tenant-controlled): disconnecting your app in the customer’s dashboard revokes the installation’s tokens themselves — the next API call with a revoked token returns
401. That’s the customer’s kill switch; don’t treat it as an error on your side, treat it as an uninstall.
token_type_hint is a hint, not a hard classification — the endpoint revokes any token regardless of whether the hint matched.
Pick scopes the least-privilege way
Publishers are tempted to request*:write (or every family at once) to avoid re-consent prompts later. Don’t — the catalog has no wildcard, and an install that requests more than the app needs trips the sensitive-scope review in the customer’s mind even when every row is routine. Work from the actual calls your app makes:
- List the endpoints your app calls in production, grouped by resource family and verb.
- Map each family to the narrowest access class — prefer
:readeverywhere a:writeisn’t strictly required. If you send campaigns through Orbit but never construct them in your product,campaigns:readis enough. - Mark data that’s read and joined back in (e.g. analytics you mirror to your warehouse) as
:read— not because you lack write access to the platform’s mutations, but because the customer’s review step is cheaper when the sensitive surface is smaller. - If your app later needs an extra scope, bump the request string and re-prompt — newer installs just see an updated consent screen; existing installs retain what they approved.
If your app sends campaigns, your homepage copy (in the manifest’s
homepageUri) should say what the customer gets from installing — “Sync SMS replies to Slack” reads better than “Access Orbit APIs” even before the customer reaches the consent screen.
Frequently asked questions
Does my app need its own Orbit account to be listed?
Yes — the manifest download (and the dashboard surface) is per-org. Download the manifest from your own org’s dashboard, replace the placeholders, and submit it. The customer who installs your app sees your app definition (name, logo, scopes), not your org’s credentials.Can a customer install the same app in two orgs?
Each org-install is an independent grant. Tokens are scoped to the org that installed the app, not to the publisher.What happens to in-flight tokens when a customer revokes my app?
Revocation only affects future calls — already-issued access tokens are invalidated (a401 on the next request), and the refresh token stops minting new ones. Calls in flight at the moment of revocation may complete, but nothing after.
We use the MCP server instead of raw REST. Do scopes matter there?
Yes — the MCP scope catalog is a re-projection of this same grantable set; a connected app token is how a connected MCP client holds access. The dashboard’s Connected Apps surface is the same starting point.See also
- Developer Portal — the console, try-it sandbox, key governance, webhook debug, and OAuth manifest downloads this page plugs into
- Authentication — API-key formats, role ranges (
viewer→developerhere), and rotation - API Integration — base URL, sandbox test mode, error codes, and rate limits if you’re calling the API itself
- Security — org-level session, key, and webhook security hygiene
- Rate limits — what to design around when your installed app fans out