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.
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. The identity-provider side of the same question is in what the Enterprise-Managed Authorization extension changes. The connector catalog lists which authorization shapes each connector actually supports, which is usually the first thing you need to know.