Back to all integrations
API

OAuth and App Authorization

Authorizing an app against an AppBO account: the handshake, token refresh, scope management and handling revocation.

On this page
What it connectsHow to connect itWhat data movesWorked exampleLimits and gotchas
Need this wired up?

Bring your stack to a call and we will connect it, test every field in both directions, and hand it over documented.

Book a call
01

What it connects

OAuth is the handshake that lets an application act on an AppBO account without ever holding the account owner's password. On one side is your application: registered once, holding a client identifier and a client secret. On the other is an account owner or agency administrator who clicks through a consent screen and grants your app a named set of permissions against a named account. What comes back is a short-lived access token, a longer-lived refresh token, and a record of exactly which account and which scopes the grant covers.

You wire this up whenever more than one account will ever use your integration, or whenever the thing being connected is a product rather than a one-off script. A single private key hard-coded into a cron job is fine for your own account; it is unworkable the moment a second client needs it, because you would be asking that client to hand over credentials that cannot be scoped or revoked independently.

There are two shapes of grant worth distinguishing. A sub-account (location) grant authorizes your app against one account's data. An agency grant authorizes your app at the parent level, after which it can obtain tokens for the accounts beneath it. Agency grants are the right model for a product that serves an agency's whole client roster; they are also the one most likely to be mis-scoped, because the permissions are broader and the blast radius of a leaked token is every client under that agency.

02

How to connect it

  1. Register the application in the platform's developer area. Record the client identifier and client secret, and store the secret in a secret manager from the start, not in a repository, a frontend bundle, or a mobile app.
  2. Decide whether the app is authorized per sub-account or at agency level, and configure the application accordingly. This choice drives which scopes are available and what the consent screen says.
  3. Choose the scopes your app actually calls. Take the exact scope identifiers from the API reference; do not infer them from endpoint names. Separate read scopes from write scopes and request write only where you write.
  4. Register your redirect URI exactly as your server will present it, including scheme, host, port and path. A trailing-slash mismatch is a rejected handshake.
  5. Build the authorize step. Your app sends the user to the platform's authorize URL carrying the client id, the requested scopes, the redirect URI, the response type, and a state value you generated and stored against the user's session.
  6. Handle the redirect. Verify the returned state matches what you stored, then discard it. Skipping this check is what makes a flow vulnerable to request forgery.
  7. Exchange the authorization code for tokens, server-side only, posting the code together with your client id, client secret and redirect URI. The code is single-use and expires quickly.
  8. Persist what you receive: access token, refresh token, expiry, the granted scope list, the account or location identifier, and the agency identifier where one applies. Encrypt the tokens at rest.
  9. For an agency-level grant, perform the additional step the reference describes to obtain a token for a specific sub-account, and store those per-account tokens separately from the parent grant.
  10. Call one read endpoint to confirm the token resolves and that the account it resolves to is the one the user intended to connect. Show the account name back to the user as confirmation. It catches the case where someone authorized the wrong account.
  11. Implement proactive refresh. Refresh on a schedule with a safety margin before expiry, inside a per-installation lock, and persist the new refresh token immediately. Treat refresh as a write to your database, not a cache update.
  12. Implement a disconnect path. When a user removes your app, or you detect the grant is gone, delete the stored tokens, mark the installation inactive, and stop the jobs that were using it.
  13. Handle scope expansion deliberately. If a new feature needs a new scope, you need a fresh authorization from each existing installation: existing tokens do not silently gain permissions. Build a re-consent prompt rather than letting calls fail.
  14. Verify the whole flow against a test account from a clean browser session: authorize, confirm the account, force a refresh, revoke from the platform side, and confirm your app degrades gracefully rather than looping.
  15. Monitor in production: alert on failed token exchanges, on refresh failures per installation, and on any 403 that indicates a scope you believed you had.
03

What data moves

Browser to platform

  • The authorization request: client identifier, requested scopes, redirect URI, response type, and your opaque state value.
  • The account owner's consent decision and their choice of which account to grant.

Platform to your server

  • The single-use authorization code, on your registered redirect URI, together with your state value.
  • On exchange: an access token, a refresh token, the token lifetime, the granted scopes, the token type, and the account or location identifier the grant covers. Agency grants also identify the parent.
  • On subsequent refresh: a new access token and, commonly, a rotated refresh token that replaces the old one.

Your server to platform

  • The code exchange, carrying client id, client secret, code and redirect URI.
  • Refresh requests carrying the current refresh token.
  • Every subsequent API call, carrying the access token as a bearer credential.

What does not move

  • The account owner's password. Your app never sees it, which is the entire point.
  • Any data outside the granted scopes: the token is a key to specific doors, not the building.
  • Any account the grant did not cover. A sub-account token does not reach its siblings.
  • Permissions added to your app registration after a grant was issued. Existing tokens keep the scopes they were issued with.
  • Billing or plan information about the authorizing account.
04

Worked example

Scenario: an agency installs your reporting product once at the parent level, and your product then reads opportunity data for five client sub-accounts.

  1. The agency administrator clicks Connect in your product. Your server generates a random state, stores it against their session, and redirects them to the platform's authorize URL with your client id, your agency-level scopes and your redirect URI.
  2. The administrator is already signed in to the platform, so they land straight on the consent screen. It lists the permissions your app asked for. They approve.
  3. The platform redirects their browser to your registered callback with a single-use code and your state value.
  4. Your server compares the returned state to the stored one, confirms the match, and deletes it.
  5. Server-side, you exchange the code for an access token, a refresh token and an expiry, along with the agency identifier. You encrypt and store all of it as one installation row.
  6. You list the sub-accounts the grant covers and present them to the administrator, who ticks the five clients they want reported on.
  7. For each selected sub-account, you perform the documented step to obtain an account-scoped token, and store those five token sets as child rows under the installation.
  8. Your nightly job wakes up, finds each stored token within its refresh margin, refreshes them under a per-row lock, and persists the rotated refresh tokens before doing anything else.
  9. The job reads opportunities for each of the five accounts and builds the report. One account returns 403 on a pipeline endpoint, which tells you that scope was never granted; you flag that account in the UI with a Reconnect to enable prompt rather than silently publishing an incomplete report.
  10. Two months later the agency removes your app. The next refresh fails with an invalid-grant error. Your handler marks the installation inactive, stops the nightly job for it, deletes the stored tokens, and emails the administrator that reporting has stopped: instead of retrying forever.
05

Limits and gotchas

Rate limiting

Token endpoints are rate limited like any other, and a bug that refreshes on every API call rather than on a timer will hit that limit and take your whole integration down. Refresh once per installation per window, behind a lock. API calls made with the resulting token count against the account's normal API budget, so an agency-level app serving many accounts should spread work across the window rather than firing every account's sync at midnight.

Retries and idempotency

Authorization codes are single-use. Retrying a failed exchange with the same code fails, and if the first attempt actually succeeded you have just thrown away a valid token pair, so persist tokens before returning a response to the browser. Refresh tokens commonly rotate on use: if you retry a refresh with the old token after a successful rotation you will be told the grant is invalid, which looks exactly like revocation. Make refresh atomic and persist before reuse.

Field mapping traps

Scope identifiers are literal strings and are not interchangeable with endpoint names. Read and write are distinct scopes for the same resource. Store the granted scope list and check against it in code, so a missing permission surfaces as a clear error in your own logs rather than an opaque 403 in production.

Duplicate installations

Nothing stops the same account being authorized twice: a user clicks Connect again, or two team members each connect. Key your installation records on the account identifier and treat a second grant as a replacement, or you will run two syncs against one account and double-create records.

Sandbox versus live

Use a separate app registration per environment, with its own client secret and its own redirect URI. Pointing a staging app at a production account is how test data ends up in a client's pipeline, and sharing one registration across environments means you cannot revoke one without breaking the other.

Auth expiry and revocation

Access tokens expire in minutes to hours; refresh tokens last longer but are not permanent. A grant dies when the user disconnects the app, when an administrator removes the installation, when the account is closed, or when the platform invalidates it. Distinguish 401 (token expired, refresh it) from an invalid-grant error on refresh (the grant is gone, ask a human to reconnect). Looping on the second case is the most common failure mode in production.

What commonly breaks

  • Redirect URI mismatches after a domain or path change.
  • state not validated, or validated against a cookie that was lost in a cross-site redirect.
  • Rotated refresh tokens not persisted, so the next refresh uses a dead token.
  • Client secret leaked into a frontend bundle or a public repository.
  • A new feature shipped with a new scope, with no re-consent path for existing installations.
  • Agency-level tokens used where an account-scoped token was required, or vice versa.

Connect it with us.

We wire the integration, map the fields, test both directions and hand it over with the SOPs your team needs to run it.

Book a call