Handling auth in integrations: OAuth, API keys, and scopes

When to use API keys vs OAuth for integrations, how scopes and token refresh work, and the security pitfalls that turn a partner launch into an incident.

An API reference card with an authorization key and a partner engineer node, illustrating how integrations authenticate against an API.

Authentication is the first code a partner engineer writes against your API, and the first place they decide whether building on you will be pleasant or painful. Everything before this point was a conversation. Now someone has a terminal open, a set of credentials they are not sure how to use, and a manager waiting on an estimate. If they get to an authenticated call in a few minutes, your integration moves forward. If they spend an afternoon guessing at a token flow that your docs describe only in prose, your integration drops a tier in their roadmap.

Auth decisions also outlive the launch. The choice between API keys and OAuth, the way you scope access, how you handle token refresh, and where you store secrets all shape what happens when something goes wrong two years later. A weak auth model does not announce itself. It sits quietly until a leaked key, an over-broad token, or a refresh bug turns into a support thread, a security review, or a disclosure email.

This guide is about getting those decisions right. When to reach for API keys and when OAuth earns its complexity, how to design scopes that pass a partner's own security review, how token lifetimes and refresh should actually work, and the pitfalls that quietly accumulate risk. It pairs with our guide to making your API partner-ready, which covers the wider surface of docs, sandbox, and webhooks, and goes deeper on the one section partners stall on most.

The 60-second version

  • Auth is your first impression in code. A partner judges your API by how fast they reach an authenticated call, not by your feature list.
  • API keys are for server-to-server, one customer's own data, and fast prototyping. OAuth is for apps acting on behalf of many users, and most marketplaces require it.
  • If you can ship only one, ship scoped API keys and put OAuth on a public roadmap. If you serve marketplace apps, you will need OAuth.
  • Scopes should match real use cases. read:contacts and write:contacts beat one api.access scope, because partners are asked to justify every permission they request.
  • Document token lifetimes and refresh with numbers. Partners should refresh on a schedule, not discover expiry through 401s in production.
  • Most auth incidents are predictable: keys in source control, tokens with no expiry, secrets in URLs, missing revocation, and no scope checks on the server.
  • Treat auth as a product surface, with a worked example, self-serve credentials, and an error catalog that explains every failure.

API keys versus OAuth: the decision that shapes everything

Almost every integration auth conversation starts here, and teams often overthink it. The two models solve different problems, and the right answer depends on who the credential represents and what it is allowed to touch.

An API key is a long-lived secret that identifies a single account or service. The caller sends it on every request, usually in an Authorization header, and the server checks it against a stored value. There is no user in the loop, no consent screen, no handshake. That simplicity is the whole point. A partner can paste a key into an environment variable and make a call in two minutes.

OAuth 2.0 is an authorization framework, not a single flow, defined in RFC 6749. It exists to solve a harder problem: letting one application act on behalf of a user without that user handing over their password. The user approves a specific set of permissions, the application receives a token scoped to those permissions, and the token can be revoked or expired without touching anything else the user owns. That power costs implementation effort on both sides.

Here is the decision laid out the way most teams need to see it:

API keys OAuth 2.0
Represents A single account or service A user who granted consent
Best for Server-to-server, one customer's own data Apps acting on behalf of many users
Partner effort to integrate Minutes Days, even with good docs
Granularity Often all-or-nothing Scopes per permission
Consent None, the account holder issues the key Explicit user approval screen
Revocation Manual, per key Per user, standard flows
Token lifetime Often indefinite until rotated Short-lived access plus refresh
Marketplace listing Frequently not accepted Usually required

The honest recommendation for most B2B SaaS startups is to support both, in that order. API keys let a partner prototype on day one and cover the large class of integrations where a customer is simply connecting their own account to their own systems. OAuth is what a multi-tenant app, and almost every marketplace review, will eventually require. If you can only build one for now, build API keys with scoping and put OAuth on a public roadmap, so partners can plan around it. The exception: if your near-term partners are marketplace apps acting on behalf of end users, you will need OAuth sooner than later, and a key-only launch will stall their review. We walk through how that affects partner conversations in the partner-ready API guide.

One distinction worth keeping straight, because it causes real confusion: API keys handle authentication (who is calling) and, if you scope them, a coarse form of authorization (what they may do). OAuth is fundamentally about delegated authorization, a user granting an app permission to act for them. Conflating the two leads to designs where teams reach for OAuth when a scoped API key would have been simpler, or ship a raw API key when they actually needed user consent.

How to pass a key without leaking it

If you support API keys, how the partner sends them matters as much as the key itself. The settled convention is the Authorization header, documented in the MDN reference for the Authorization header. A typical request looks like sending Authorization: Bearer sk_live_... over TLS. That is the entire mechanism, and the simplicity is a feature.

The pitfall most teams ship at least once is accepting the key as a query parameter, for example a ?api_key=... on the URL. It feels convenient, and a curl example with the key in the URL is easy to copy. It is also one of the most common ways secrets leak. URLs end up in server access logs, proxy logs, browser history, analytics pipelines, and the Referer header sent to third parties. A secret that travels in a URL has effectively been written to a dozen places you do not control. Put the key in a header, document only the header form, and if you must accept a query parameter for some legacy reason, treat any key that arrives that way as compromised and force a rotation.

A few more practices make key-based auth implementable and safe:

  • Prefix and label your keys. A key that starts with sk_live_ or sk_test_ tells the holder, and any secret-scanning tool, what it is and which environment it touches. This single convention catches a meaningful share of accidental commits before they become incidents.
  • Distinguish test from live. A partner evaluating you at 9 PM should be able to generate a test key against a sandbox without touching production data. Mixing the two is how a prototype writes to a real customer's account.
  • Store hashes, not keys. Keep a hash of each key in your database, not the key itself, the same way you would store passwords. If your database is exposed, the keys are not usable. Show the full key to the user exactly once, at creation.
  • Make rotation a button, not a ticket. Partners will need to rotate keys, after a laptop is lost or an employee leaves. If rotation requires emailing your support team, it will not happen on the schedule security demands.

Scopes: least privilege that a partner can actually request

Scopes are where coarse auth becomes good auth. A scope is a named permission attached to a credential, and a well-designed scope list lets a partner request exactly the access their integration needs and nothing more. This is not bureaucracy. When a partner's own security team reviews the integration, the first question is "why does this app have write access to everything," and a tight scope list is the answer that lets the review pass.

The failure pattern is a single god scope, something like api.access or full, that grants everything. It is easy to build and impossible to defend. A partner who only reads contacts is forced to request, and hold, permission to delete invoices. When that credential leaks, the blast radius is your entire API surface instead of one read endpoint.

Design scopes around real use cases, typically split by resource and action:

Scope Grants Typical use
read:contacts Read contact records A CRM sync that imports your data
write:contacts Create and update contacts A two-way sync or a form integration
read:events Read event or activity data An analytics or reporting integration
write:webhooks Register and manage webhooks Any integration that needs push updates
read:billing Read invoices and subscriptions A finance or reconciliation tool

A few principles keep a scope system honest. Name scopes for what a human would request, not for internal table names, so the consent screen a user sees is legible. Separate read from write, so the common read-only integration never has to ask for write. Default to the narrowest scope that completes the job, and make it obvious in your docs which scopes each use case needs, so a partner does not over-request out of uncertainty. And critically, enforce scopes on the server for every request, not just at the consent screen. A scope that is displayed but never checked is theater. The token may say read:contacts, but if your endpoint does not verify it before deleting a contact, the scope protected no one.

When a caller hits an endpoint they lack the scope for, return a clear 403 that names the missing scope, not a generic error. The difference between "403 Forbidden" and "403: this endpoint requires the write:contacts scope" is the difference between a debugging afternoon and a thirty-second fix. We cover this style of self-serve error in detail in our guide to API error design.

Token lifetimes and refresh, documented with numbers

Once you support OAuth, or any token-based auth, you inherit a lifecycle: tokens are issued, they expire, and they get refreshed. How you design and document that lifecycle decides whether partners build something stable or something that breaks at 2 AM.

The standard OAuth pattern uses two tokens. A short-lived access token is sent on each API call, following the bearer token usage defined in RFC 6750. A longer-lived refresh token is used only to obtain new access tokens when the old one expires. Splitting them limits damage: if an access token leaks, it is useless within an hour or so, and the refresh token, which is more sensitive, travels far less often and lives only on the server.

The rule that prevents most token bugs is simple: partners should refresh on a schedule, not discover expiry through 401s in production. That only works if you give them the numbers. Document, with concrete values, how long an access token lives, how long a refresh token lives, whether refresh tokens rotate on use, and exactly what an expired-token response looks like so they can handle it in code.

Question a partner has What your docs must state
How long does an access token live? A concrete number, for example one hour
How long does a refresh token live? A concrete number, or "until revoked," stated plainly
Do refresh tokens rotate on use? Yes or no, and what happens to the old one
What does an expired access token return? The exact status and error body, for example 401 with a code
Can a partner refresh proactively? Whether early refresh is allowed and any rate on it
What happens when a user revokes access? The error the partner sees and how to re-consent

Two design choices deserve a deliberate decision rather than a default. First, refresh token rotation: issuing a new refresh token each time one is used, and invalidating the old one, lets you detect token theft, because a stolen-and-replayed refresh token shows up as a reuse of an already-spent token. It adds complexity for the partner, who must always persist the newest refresh token, so document it loudly if you do it. Second, what a partner does on a 401: the correct behavior is to refresh once and retry, then fail loudly if the refresh also fails. Partners who instead retry the same expired token in a loop will hammer your auth endpoint and call it an outage. Spell out the expected flow so they build it correctly the first time.

Common mistakes, and the fix

Keys committed to source control. The single most common way secrets leak. The fix: prefix keys so scanners recognize them, store only hashes, support one-click rotation, and tell partners in your docs to use environment variables or a secrets manager, never a checked-in config file. Assume any key that has touched a repository is burned.

Tokens that never expire. An access token with no expiry is a permanent liability the moment it leaks, because nothing reduces its value over time. The fix: short-lived access tokens with refresh, with the lifetimes documented. If you must issue long-lived API keys, make revocation and rotation effortless to compensate.

Secrets in URLs. Keys or tokens in query strings end up in logs, history, and the Referer header. The fix: accept credentials only in the Authorization header, document only that form, and treat any credential that arrives in a URL as compromised.

Scopes that are displayed but never enforced. A consent screen that lists read:contacts while the server lets the token write anything is worse than no scopes, because it implies a protection that does not exist. The fix: check the scope on the server for every request, and return a 403 that names the missing scope.

Documenting only the happy path. Auth docs that show one successful call and stop force the partner to reverse-engineer expiry, refresh, scope errors, and revocation. The fix: document the full lifecycle with real response bodies, including every failure. This is the section partner engineers stall on, so it earns the most detail.

No path to revoke. When a partner's key or a user's token is compromised, they need to kill it now. The fix: per-key revocation for API keys, standard per-user revocation flows for OAuth, and a documented "what to do if a credential leaks" runbook. The OWASP API Security Project catalogs how broken authentication and authorization show up in real APIs, and it is worth reading against your own design.

How auth choices change the integration conversation

Auth is not only an engineering detail. Like the rest of API readiness, it changes what a partner can verify versus what they have to take on faith. Every gap forces them to trust you. Every documented, working piece lets them check for themselves, and partnerships move at the speed of verification.

What the partner asks What answers it
"How do we authenticate?" A worked example from credentials to a successful call
"Can our engineer try it tonight?" Self-serve test credentials, no sales gate
"What can this token actually do?" A scope list mapped to use cases
"How do we keep tokens fresh?" Documented lifetimes and a refresh flow
"What happens when something leaks?" Per-credential revocation and a rotation path
"Will this pass our security review?" Least-privilege scopes, hashed storage, header-only secrets

Read that table from the partner's side and the pattern matches the rest of partner readiness: the goal is to let an engineer answer every question from your docs and a terminal, without booking a call. When auth is solid, the conversation skips past "is this safe to build on" and goes straight to scoping the integration, which is the conversation you want to be having. For how versioning interacts with all of this over the life of an integration, see our guide to integration versioning.

FAQ

Should we use API keys or OAuth for our integrations? Both, if you can. API keys are right for server-to-server integrations and for a single customer connecting their own account, and they let a partner prototype in minutes. OAuth is right for apps acting on behalf of many users and is usually required for marketplace listings. If you ship one first, ship scoped API keys and put OAuth on a public roadmap, unless your near-term partners are marketplace apps, in which case prioritize OAuth.

What are scopes and why do they matter? A scope is a named permission attached to a credential, like read:contacts or write:webhooks. They matter because they let a partner request the minimum access their integration needs, which is exactly what their own security review will demand. A single broad scope means any leaked credential exposes your whole API, while tight scopes contain the blast radius to one resource and action.

How long should access tokens live? Short, so a leaked token is useless quickly, paired with a refresh token for renewal. The exact number is yours to choose, but whatever you pick, document it with a concrete value and document the refresh flow alongside it. The goal is that partners refresh on a schedule rather than discovering expiry through 401 errors in production.

Where should partners send API keys? In the Authorization header over TLS, and only there. Never accept keys in a query string, because URLs leak into logs, browser history, and the Referer header sent to third parties. Document only the header form, and treat any key that arrives in a URL as compromised.

What is refresh token rotation and do we need it? Rotation issues a new refresh token every time one is used and invalidates the old one, which lets you detect theft when a stolen token is replayed. It is a strong control but adds complexity for partners, who must always store the newest refresh token. If you adopt it, document it prominently so partners build for it from the start.

How do we let partners recover from a leaked credential? Make revocation and rotation self-serve and fast: per-key revocation for API keys, standard per-user revocation for OAuth, and a short runbook in your docs describing what to do when a credential leaks. If recovery requires emailing your support team, it will not happen on the timeline security incidents demand.

Do API keys need scopes too? Yes, ideally. Scoped API keys give you least privilege without the full weight of OAuth, so a server-to-server integration that only reads data holds a key that cannot write or delete. This is the most practical way to ship a safe key-based system before you build OAuth.

Further reading

The short version

Authentication is the first code a partner writes against your API, so it is your first impression in code and the place a security review will look hardest. Choose API keys for server-to-server and single-account integrations, OAuth for apps acting on behalf of users, and support both when you can, leading with scoped API keys if you have to pick one. Design scopes around real use cases, enforce them on the server, and never settle for one god scope.

Document token lifetimes and refresh with concrete numbers so partners refresh on a schedule instead of discovering expiry in production. Then avoid the predictable incidents: keys in source control, tokens that never expire, secrets in URLs, scopes that are shown but not checked, and credentials with no path to revoke. Get those right and auth stops being the place your integrations stall.

If you want an outside pair of eyes on exactly that, a Partner Audit reviews your API, your auth model, and your partner readiness, then hands you a concrete plan: what to fix, what to document, and which partners to approach once an engineer can authenticate against you in minutes.

Ready to turn partnerships into shipped product?

Start with a Partner Audit. We review your product, API, customer workflows, and partner potential.

Book a Partner Audit