Back to writing
Authentication · 8 min read

Why API Keys Are Not an Identity Strategy

API keys prove that a caller knows a secret. AI agents need scoped, expiring, and auditable identity instead.

Every team building AI agents right now is basically doing the same thing: they generate a long random string, put it in an environment variable, call it “authentication,” and move on. I am seeing this pattern in almost every AI infrastructure discussion I am part of, and I want to say clearly: an API key is a secret, not an identity. Treating it as the same thing is a disaster waiting to happen.

The confusion, explained simply

A secret answers one question: do you know this string or not? An identity answers a completely different set of questions. Who are you, actually? What are you allowed to do? Who granted you that permission, and for how long? When an API key is your whole security model, you have answered the first question and quietly skipped the other three.

This was tolerable when the caller was a predictable backend service that your own team wrote and deployed. It is not tolerable when the caller is an AI agent that is making autonomous decisions, chaining tool calls, and sometimes acting on behalf of a human user it has never directly authenticated. The agent is not “your service” anymore in the old sense. It is closer to a semi-independent actor that needs its own verifiable identity, its own scoped permissions, and its own audit trail.

Where the API key model actually breaks

Let me list where I am seeing the cracks, without pretending this is some neat universal law:

  • No delegation. An API key cannot say “I am acting on behalf of user X, with X’s consent, for the next 15 minutes.” It is a flat, all-or-nothing credential.
  • No expiry that means anything. Most API keys live forever until someone remembers to rotate them, which basically means never.
  • No audience or scope binding. The same key that reads a customer record can usually also delete it. There is no built-in concept of “this token is only valid for this one resource server, for this one action.”
  • Terrible audit story. When three different AI agents share the same key—and in practice, they often do—your logs cannot tell you which agent actually did what. You get a name in a log line, not a proof.
  • Revocation is a blunt instrument. You cannot revoke “this one agent’s access to this one tool” without breaking everything else that shares the key.

None of this is a new problem, actually. Enterprise software went through this exact argument fifteen years ago when everyone moved off shared static credentials toward OAuth-based delegated access. What is different now is that the caller on the other end is not a human clicking “allow” on a consent screen; it is an autonomous agent, and the whole flow needs to happen without a human sitting there approving each step.

What an identity-based model looks like instead

API key model versus identity model

In the API key model above, there is nothing to reason about. The key either works or it does not, and once it is compromised, everything behind it is compromised also. Now compare this to what a proper identity flow looks like for an agent.

Identity based token flow for an AI agent

The important shift here is that the agent is not holding a permanent secret that grants everything. It is holding a token, and that token is a claim about identity, scope, and time. The resource server does not trust the token because it is hard to guess; it trusts the token because it is signed by an identity provider it already trusts, and it can inspect exactly what the token is allowed to do before it does anything.

This is where OAuth 2.0 client credentials grant, or better, OAuth 2.0 token exchange (RFC 8693), becomes relevant for agent-to-agent and agent-to-tool calls. Token exchange in particular is built for exactly this situation: an agent that holds one token needs to call a downstream service with a narrower, more specific token, possibly acting on behalf of the original user. That is delegation done properly, not a shared secret pretending to be delegation.

A quick code comparison

Here is roughly what the API key version looks like. Simple, and also the whole problem in one snippet:

# The "identity strategy" most agents ship with today
import requests
 
response = requests.get(
    "https://api.internal-service.com/v1/customer-records",
    headers={"X-API-Key": "sk_live_9f8a7...never_rotated"}
)
# No subject, no scope, no expiry, no way to tell which agent called this.

Now compare with a token acquired through client credentials and passed as a bearer token, where the token itself carries claims:

import requests
 
token_response = requests.post(
    "https://idp.example.com/oauth/token",
    data={
        "grant_type": "client_credentials",
        "client_id": "agent-invoice-reader",
        "client_secret": AGENT_SECRET,  # short lived, rotated, scoped to this one agent
        "scope": "invoices:read",
        "audience": "https://api.internal-service.com",
    },
)
access_token = token_response.json()["access_token"]
 
response = requests.get(
    "https://api.internal-service.com/v1/customer-records",
    headers={"Authorization": f"Bearer {access_token}"},
)
# The token carries sub, scope, aud, and exp claims.
# The resource server can verify all four before doing anything.

The second version has more moving parts; I am not denying that. But those moving parts are exactly what let you answer “which agent did this, was it allowed to, and for how long” without guessing. If you decode that access token (it is typically a JWT, RFC 7519), you get a sub claim identifying the client, a scope claim limiting what it can do, an aud claim binding it to one resource server, and an exp claim that makes the whole thing self-expiring. An API key gives you none of these.

Standards worth actually reading

If you are designing this for real, three documents are worth your time and not just a skim:

  • RFC 6749, The OAuth 2.0 Authorization Framework, for the base grant types including client credentials.
  • RFC 8693, OAuth 2.0 Token Exchange, which is basically the spec that AI agent architectures keep re-inventing badly when they do not use it.
  • RFC 7519, JSON Web Token (JWT), for how claims-based tokens are structured and verified.

OpenID Connect is also worth knowing here, though it solves a slightly different problem: authenticating a human end user. Most agent-to-service calls are closer to pure OAuth client credentials or token exchange territory.

The takeaway

An API key tells you that a caller knows a string. It does not tell you who the caller is, what it is allowed to do, or when that permission runs out. AI agents are autonomous enough now that this gap is not theoretical anymore; it is showing up as real incidents. If your agent’s entire security model is one static header value, you do not have an identity strategy—you have a shared password with extra steps.

Treat your agents like the semi-independent actors they are: give them real, scoped, expiring identity, not a key copied into a config file three deployments ago.