# OAuth vs API keys for AI agents

> OAuth vs API keys is a settled argument until the caller is a model that can be talked into using the credential, and cannot be trusted to know which of its fields are secret.

**TL;DR** For agent access, prefer OAuth wherever the vendor offers it, because it carries an identity, expires on its own and can be revoked without a rotation. Where only an API key exists, the questions that matter are who holds it, whether anything can read it back, and whether the system knows which of its own fields are secret. That last one has to be a stored list, not a naming convention.

OAuth vs API keys is a well worn comparison. Tokens expire and keys do not. Tokens carry scopes and identity, keys are bearer strings. Revoking a token is one action, rotating a key breaks everything that held it.

All of that still applies when the caller is an agent. Three things change, and they change the emphasis enough to be worth writing down.

## What OAuth vs API keys means once an agent is the caller

**The holder can be persuaded.** A script with a key does what it was written to do. A model with access does what the conversation leads it to do, and the conversation includes content from outside your organization: a support ticket, a web page, an email. The credential has not become weaker. The judgment in front of it has.

**Credentials outlive the reason for them.** A person connects a system for one project. The project finishes. The connection remains, because nothing prompts anyone to remove it, and it is still valid two years later.

**The blast radius is wide by construction.** The whole point of connecting an agent is breadth. A key that reaches one API in a script reaches, in this setting, one system among many that the same assistant can combine.

None of that makes API keys unusable. It moves the important question from *which credential type* to *who holds it, what can read it back, and what happens when it expires*.

## Prefer OAuth where it exists

Three properties, in order of how much they matter here.

**It carries an identity.** A token was issued to somebody, for something. When a call is made, there is a subject. With a shared key there is no subject, and every audit answer collapses to the name of the key.

**It expires on its own.** The connection that outlived its project stops working without anyone having to notice it. Expiry is the only control that operates without human attention, which makes it the only one that operates reliably.

**Revocation is surgical.** Revoke one grant and one grant stops. Rotate a shared key and you find out, by outage, everything that was holding it.

There is a fourth reason specific to this setting. OAuth scopes give the vendor's own consent screen a chance to state what is being granted, in the vendor's words, at the moment of granting. That is a better boundary than anything written on our side, because the person approving it is reading the vendor's description of the vendor's own data.

## Where a key is genuinely better

Two cases, worth stating because the answer is not always the same one.

**The key is scoped more finely.** Several vendors offer read-only or resource-scoped keys while their OAuth scopes are coarse. Where that is true, a narrow key can be the smaller grant, and smaller wins.

**The OAuth grant is tied to one person's account.** Some implementations bind the installation to the human who authorized it. That is fine until they leave, at which point the connection dies with their account and takes the team's tooling with it. A key issued to a service identity avoids a failure mode that is otherwise guaranteed rather than possible.

If you pick a key for either reason, write down which reason. The next person to look at it will otherwise assume nobody thought about it.

## Who holds it, and who can read it back

This is where agent access differs most from ordinary application integration, and where the design decisions are load-bearing.

**The model never receives a credential.** Credentials are resolved inside the call, after the authorization checks have passed, and the model receives the result. There is no step at which a token is in the conversation. A design where the model is handed a credential and asked to use it has no recoverable position once the model is talked into something, because the credential is already out.

**Storage is separate from policy.** The system that decides whether a call is allowed and the system that holds the secret are not the same system. That separation is not theater; it means a bug in the policy layer cannot read secrets, because the policy layer has no path to them.

**Read-back is structurally impossible, not merely forbidden.** Reading a connection's configuration returns the public values plus **a list of the paths that were encrypted**, carrying none of their values. You can see that there is a token at a particular place in the configuration. You cannot see it.

That list of paths does one more job, and it is the subtle one.

## The system has to know what is secret

An obvious way to decide whether a configuration field is a secret is to look at its name. Anything called `token`, `secret`, `key` or `password` is sensitive; the rest is not.

This works until it does not, and it fails silently. Connector configurations come from many vendors with many conventions. A field called `signature_base` is a secret. A field called `api_key_name` is not. A naming convention is a guess about somebody else's schema, and the cost of guessing wrong is asymmetric: guess conservatively and something readable is hidden, guess permissively and a secret is returned over an API.

So the list of encrypted paths is stored at the time of encryption and is **the only thing that decides whether a variable is secret**. It is a fact recorded by the code that did the encrypting, not a pattern matched afterwards.

A related detail worth copying: an attempt to edit one of those paths is refused, and the refusal text is **identical whichever layer produced it**. If the check in front and the check behind gave different messages, a caller could tell which one stopped them, and that difference is a map of where the checks are.

## Refresh belongs to the store, and failure has to be loud

Whoever holds the credential should refresh it. Splitting the two means two systems with an opinion about whether a token is current, and they will disagree.

The more important half is what happens when a refresh fails. The wrong answer is to fail the call and move on. That produces a connection which looks fine, works some of the time, and returns nothing the rest of the time.

Nothing is the dangerous result. An agent that gets an empty list does not raise an alarm. It tells the user there are no matching records, which is a confident, wrong, plausible answer. The user acts on it.

So a failed refresh marks the connection as needing reauthorization and surfaces that to the person who owns it. The connection is visibly broken rather than invisibly empty. Loud failure is worth a lot when the consumer of the result is a model that will narrate whatever it receives.

## One more thing that is not a credential

Worth stating because it looks like one.

The URL that starts a connection flow, the one you follow to authorize an account, **carries no token**. It is a one-time session identifier that begins an authorization the person then completes themselves. It is safe to return over an API and safe to hand to an agent, which matters because "connect my Notion account" is a thing people ask an assistant to help with.

Getting this wrong in either direction costs something. Treat it as a secret and you cannot let an assistant help anyone connect anything. Treat a real credential this way and you have a much worse problem. The distinction is worth checking in any product you evaluate, in both directions.

## Bring your own OAuth application

A short note, because it is the enterprise question that comes up third.

Larger organizations often want the OAuth application to be theirs, so consent screens carry their name and the vendor relationship is direct. That is reasonable and we support it, with one deliberate narrowing: the accepted configuration is a client id, a client secret and scopes. Everything endpoint-shaped is **unrepresentable**, not merely validated.

The reason is that a configurable token endpoint is a redirect of the whole organization's authorization traffic to an address of somebody's choosing. Validation would be a check that could be bypassed. A type that has no field for it cannot be.

The same thinking puts this behind the permission to manage connectors rather than the permission to manage connections. Those sound adjacent and are not. Everyone who can delete a connection would otherwise silently gain the ability to repoint the organization's OAuth application, which is a much larger thing than deleting a connection.

## What to check per connector before you commit

Authorization shapes vary more between vendors than the two-word summary
suggests, and the differences decide how the connection behaves a year in. Five
questions, answerable from a vendor's own documentation in a few minutes.

**Does the token expire, and what happens when it does?** Some vendors issue
refresh tokens that themselves expire after a period of inactivity. A connection
used weekly is fine; one used at quarter end may be dead every time you need it.
This is the single most common cause of a connection that "randomly stops
working".

**Is the grant bound to a human account or to an installation?** This determines
whether the connection survives the person who made it. It is the question that
decides whether you are setting yourself up for the offboarding problem, and it
is rarely stated plainly in the docs; look at what the consent screen says it is
authorizing.

**Are the scopes finer than the roles?** Where a vendor's OAuth scopes are coarse
but their API keys can be restricted per resource, a key may genuinely be the
smaller grant. Worth checking rather than assuming the protocol determines the
answer.

**Does revocation cascade?** Some vendors revoke every token issued to an
application when one is revoked. If two teams connected the same app, that turns
a targeted revocation into an outage for both, which is something to know before
the day you need to revoke.

**Is there a rate limit tied to the credential rather than the account?** Shared
credentials share limits. A team of twenty running searches through one
connection can exhaust a limit that the same twenty people never touched using
the web interface.

None of these change the general preference for OAuth. They change what you
should expect from a specific connection, which is the thing that determines
whether somebody files a bug in three months.

## The short version

- Prefer OAuth. Identity, expiry and surgical revocation are worth more here than anywhere else.
- Use a key when it is genuinely narrower, or when the OAuth grant would die with an employee. Write down which.
- Never let the model hold the credential. It should hold results.
- Store the list of secret paths. Do not infer secrecy from field names.
- Make refresh failure loud. An empty result is the answer an agent will confidently repeat.
- A connect URL is not a credential. Know which of your strings are which.

What happens after the credential resolves, and what gets written down about it, is in [what an AI audit log has to capture](/blog/what-an-ai-audit-log-must-capture/). The identity-provider side of the same question is in [what the Enterprise-Managed Authorization extension changes](/blog/enterprise-managed-authorization/). The [connector catalog](/connectors/) lists which authorization shapes each connector actually supports, which is usually the first thing you need to know.

## FAQ

### If a vendor offers both, which should I pick?

OAuth, in almost every case. It ties the grant to a person or an installation rather than to a string, it expires without anyone remembering to expire it, and revoking it is one action that does not break anything else. An API key is a bearer credential with none of those three properties.

### Is an API key ever the better choice?

Sometimes, and it is worth being honest about it. Some vendors scope keys more finely than their OAuth scopes allow, so a key can be the narrower credential. Some OAuth implementations bind the grant to a specific human's account, which means the connection breaks when that person leaves. Where either applies, a key held in the right place can be the better answer.

### Does the agent ever see the credential?

No, and that is the property worth checking in any product you evaluate. The credential is resolved inside the call, after the authorization checks, and the model receives the result of the call and never the token. A design in which the model is handed a credential to use has no recoverable position once that model is talked into something.

### What happens when a refresh fails?

The connection is marked as needing reauthorization and the person who owns it is told. The failure mode being avoided is a connection that looks healthy and quietly returns nothing, because an agent that gets an empty result does not report an outage, it reports that there is nothing to find.

### Can somebody read a stored secret back out?

No. A read of a connection's configuration returns the public values plus a list of the paths that were encrypted, carrying none of their values. That list is also what decides whether a field is a secret, rather than the field's name, because a naming convention is a guess about a schema somebody else controls.
