How We Normalize OAuth Across 280+ APIs at Unified.to
June 20, 2025
OAuth 2.0 is a standard, but it rarely feels that way when you're building integrations across 280+ SaaS platforms. Each one introduces a new edge case: different permission scopes, redirect flow rules, content types, token result formats, and error responses. At Unified.to, our goal is to remove that complexity entirely.
This post walks through how Unified.to normalizes OAuth across hundreds of APIs, how we expose that to developers through our SDK, and what patterns we've adopted to abstract away common OAuth pitfalls.
Why OAuth Fragmentation Slows Teams Down
If you've ever built integrations with multiple third-party APIs, you've probably encountered:
- One provider using
client_credentials
, anotherauthorization_code
, a third relying on legacy tokens - Scope naming collisions:
read_contacts
vs.contacts.read
vs.read_people
- Redirect URI mismatch errors due to minor differences in path formatting
- HTML errors instead of JSON on expired tokens
- Varying token formats, expiration policies, and re-consent behaviors
- So many refresh strategies; refreshing access token, refreshing the refresh token, time-based and activity-based expiries, re-authentication expiries, …
Each new integration adds more code paths, more exception handling, and more time spent maintaining brittle OAuth logic. In fact, at Unified.to, we've found (and support) more than 70 variations of the OAuth2 "standard".
Our Design Goals
Unified.to was built to abstract this away. We focused on:
- Supporting all major OAuth grant types & flows
- Standardizing redirect URIs across every integration
- Mapping provider scopes to a unified set
- Handling token exchange, refresh, and revocation uniformly
- Surfacing OAuth errors in a consistent way
- Giving developers a single method to initiate and monitor authorization flows
Here's how we've implemented those.
1. Grant Type Normalization
Unified.to supports authorization_code
, client_credentials
, password
and refresh_token
flows. We also support API key/token-based authorization where applicable. For each integration, we define the required grant type and credentials internally. Developers only need to call unified.connect()
in our SDK.
Under the hood, we initiate the appropriate flow, handle redirects, and capture tokens. We also expose these tokens to your infrastructure via secure storage, or store them encrypted and scoped per connection.
2. Unified Redirect URIs
We use a single redirect URI across all integrations: https://api.unified.to/oauth/code
(plus EU and AU versions). Providers are configured to use that during app registration.
That means no environment-specific redirects, no per-provider callback config, and fewer deployment issues. This one endpoint handles all OAuth code exchanges.
3. Unified Scope Abstraction
Instead of asking developers to memorize each provider's scope strings, we maintain a library of unified scopes (e.g. crm_contact_read
, storage_file_write
, calendar_event_read
).
In the Unified.to SDK or dashboard, you select these abstract scopes. We map them to provider-specific scopes under the hood.
Need a custom scope? You can pass provider-native scope strings too. Unified.to will merge and handle them.
4. Token Management and Refresh
Once tokens are obtained, Unified.to:
- Stores them encrypted or in your own AWS Secrets Manager
- Tracks expiration
- Refreshes access tokens and, if needed, refresh tokens automatically
- Handles provider-specific quirks (e.g. rolling refresh, re-consent windows)
This means no cron jobs, no manual expiry tracking, and no customer disruption. If a refresh fails, Unified.to flags the connection and surfaces a consistent 401
or needs_reconnect
error and sends your server an event notification.
5. Standardized Error Surfacing
Every provider returns errors differently. Unified.to standardizes them:
401 Unauthorized
: Token expired or revoked403 Forbidden
: Missing scope or permission400 Bad Request
: Invalid config or payload
The authorization also will test the newly created connection to ensure that it has access to the data that your permissions requested. If it doesn't then a 403
error is returned and the connection is not created.
In every OAuth failure (auth rejection, scope issue, redirect error), we surface an error object in a consistent format. Developers only have to handle a known set of cases.
6. One SDK, One Interface
Our SDK exposes a single connect()
method for initiating OAuth, and a getConnection()
method for retrieving status and metadata.
Each connection object includes:
- Connection ID
- Status (
healthy
,unhealthy
,needs_reconnect
) - Scopes granted
- Refresh history
- Error state if present
This simplifies frontend UI and backend sync logic. No per-integration code required.
7. Pre-build Authorization UI
To make it dead-simple to get your customers to authorize access to their accounts, we've built UI components that you can use directly inside your application with just 1 line of code.
- We have pre-build components for React, Angular, VueJS, Svelte and Javascript.
- Plus you can fork our publicly available source code and redesign it however you like.
- If that isn't enough customization, then just use our API to get a list of activated integrations which will include a logo URL, name, and authorization URL, which you can then style and display however you like (even alongside your existing internally-built integrations).
Why It Matters
OAuth fragmentation isn't just a developer inconvenience. It slows down feature delivery, introduces hard-to-debug issues, and increases risk. Unified.to makes OAuth something you can configure once and scale across all integrations.
You don't have to manage per-API edge cases. You get a unified abstraction for scopes, redirects, errors, token refresh, and storage.
OAuth is still OAuth under the hood. But Unified.to makes it feel like one integration.