← Back to Blog
Security14 min readFebruary 27, 2026

The agent authentication stack: API keys, OAuth, JWTs, and mTLS compared

A
Anon Team

39 million reasons to care about agent auth

In 2024, GitHub detected over 39 million leaked secrets across its platform — API keys, tokens, credentials sitting in public repositories. That number has been climbing year over year.

Now add AI agents to the picture. Agents need credentials to authenticate with your API. They store those credentials in config files, environment variables, and memory. They pass them in HTTP headers thousands of times per day. Every credential you issue to an agent is a credential that could leak, get replayed, or get stolen.

The authentication mechanism you choose determines your blast radius when (not if) something goes wrong.

This isn't an abstract security discussion. It's the practical difference between "we revoked a scoped token that expired in 30 minutes" and "we need to rotate every API key we've ever issued because one agent's config got committed to a public repo."

The four contenders

There are four serious options for authenticating AI agents making machine-to-machine (M2M) calls to your API:

  1. API keys — Static, opaque secrets
  2. OAuth 2.0 Client Credentials — Token exchange with scoped, short-lived access
  3. JWTs (signed assertions) — Self-contained, verifiable tokens with embedded claims
  4. mTLS — Mutual certificate-based authentication at the transport layer

Each has real trade-offs. Let's break them down with code, threat analysis, and the situations where each one actually makes sense.


1. API keys: the default everyone starts with

An API key is an opaque string — a shared secret between the client and the server. The client sends it with every request. The server looks it up in a database and decides whether to grant access.

How it works

Agent                        Your API
  |                                |
  |-- GET /api/data -------------->|
  |   x-api-key: sk_live_abc123   |
  |                                |
  |<-- 200 OK --------------------|
  |   { data }                     |

Implementation (Node.js)

// API key verification middleware
function authenticateApiKey(req, res, next) {
  const apiKey = req.headers['x-api-key'];
  
  if (!apiKey) {
    return res.status(401).json({ error: 'Missing API key' });
  }

  // Hash the key before lookup (never store raw keys)
  const keyHash = crypto
    .createHash('sha256')
    .update(apiKey)
    .digest('hex');

  const client = await db.clients.findOne({ 
    apiKeyHash: keyHash,
    status: 'active' 
  });

  if (!client) {
    return res.status(403).json({ error: 'Invalid API key' });
  }

  // Rate limit check
  const usage = await rateLimiter.check(client.id);
  if (usage.exceeded) {
    return res.status(429).json({ 
      error: 'Rate limit exceeded',
      retryAfter: usage.resetAt 
    });
  }

  req.client = client;
  next();
}

Implementation (Python / FastAPI)

from fastapi import FastAPI, Header, HTTPException
import hashlib

app = FastAPI()

async def verify_api_key(x_api_key: str = Header(...)):
    key_hash = hashlib.sha256(x_api_key.encode()).hexdigest()
    
    client = await db.clients.find_one({
        "api_key_hash": key_hash,
        "status": "active"
    })
    
    if not client:
        raise HTTPException(status_code=403, detail="Invalid API key")
    
    return client

@app.get("/api/data")
async def get_data(client=Depends(verify_api_key)):
    return {"data": "...", "client_id": client["id"]}

Threat model

Threat Risk Level Mitigation
Key leaked in source code 🔴 Critical Prefix keys (sk_live_, sk_test_) so scanners detect them
Key stolen from config/env 🔴 Critical No built-in expiry — compromised key works until manually revoked
Key replayed by attacker 🟡 Medium Rate limiting, IP allowlisting
Privilege escalation 🟡 Medium Most API keys have flat permissions — no scopes
Key shared between agents 🟡 Medium No way to distinguish which agent used a shared key

The real problem

API keys are long-lived, unscoped, and bearer tokens — whoever holds the key has access, period. There's no expiry, no built-in scope mechanism, and no standard way to distinguish between a legitimate agent and an attacker who found the key in a .env file.

Stripe does API keys well: they prefix keys (sk_live_, pk_test_), support restricted keys with granular permissions, and maintain separate test/live environments. But that level of API key engineering is rare. Most implementations are just a random string in a database.

When API keys actually make sense

  • Internal services behind a VPN or service mesh where the network is the trust boundary
  • Developer onboarding — let people try your API in 30 seconds before graduating to OAuth
  • Low-value read-only endpoints where a leaked key has minimal blast radius
  • Webhook verification using HMAC signatures (technically a derived use of shared secrets)

2. OAuth 2.0 Client Credentials: the production standard

The OAuth 2.0 Client Credentials Grant (RFC 6749 §4.4) was literally designed for machine-to-machine authentication. No browser. No human. The client exchanges its credentials at a token endpoint for a short-lived, scoped access token.

How it works

Agent                     Auth Server                   Your API
  |                            |                            |
  |-- POST /oauth/token ------>|                            |
  |   grant_type=client_creds  |                            |
  |   client_id=xxx            |                            |
  |   client_secret=yyy        |                            |
  |   scope=read:data          |                            |
  |                            |                            |
  |<-- { access_token,  -------|                            |
  |      expires_in: 3600 }    |                            |
  |                            |                            |
  |-- GET /api/data ------------------------------>|
  |   Authorization: Bearer <token>                |
  |                            |                            |
  |<-- 200 OK ------------------------------------|

Implementation: token endpoint (Node.js)

app.post('/oauth/token', async (req, res) => {
  const { grant_type, client_id, client_secret, scope } = req.body;

  if (grant_type !== 'client_credentials') {
    return res.status(400).json({ error: 'unsupported_grant_type' });
  }

  // Validate client credentials
  const client = await db.clients.findOne({ clientId: client_id });
  if (!client) {
    return res.status(401).json({ error: 'invalid_client' });
  }

  const secretValid = await bcrypt.compare(client_secret, client.secretHash);
  if (!secretValid) {
    return res.status(401).json({ error: 'invalid_client' });
  }

  // Validate requested scopes against client's allowed scopes
  const requestedScopes = (scope || '').split(' ').filter(Boolean);
  const allowedScopes = client.allowedScopes || [];
  const grantedScopes = requestedScopes.filter(s => allowedScopes.includes(s));

  if (requestedScopes.length > 0 && grantedScopes.length === 0) {
    return res.status(400).json({ error: 'invalid_scope' });
  }

  // Issue a short-lived JWT access token
  const accessToken = jwt.sign(
    {
      sub: client.clientId,
      client_id: client.clientId,
      scope: grantedScopes.join(' '),
      type: 'access_token',
    },
    process.env.JWT_SIGNING_KEY,
    {
      algorithm: 'RS256',
      expiresIn: '1h',
      issuer: 'https://auth.yourapp.com',
      audience: 'https://api.yourapp.com',
    }
  );

  res.json({
    access_token: accessToken,
    token_type: 'Bearer',
    expires_in: 3600,
    scope: grantedScopes.join(' '),
  });
});

Implementation: token verification middleware

const jwksClient = require('jwks-rsa');

const client = jwksClient({
  jwksUri: 'https://auth.yourapp.com/.well-known/jwks.json',
  cache: true,
  rateLimit: true,
});

function getSigningKey(header, callback) {
  client.getSigningKey(header.kid, (err, key) => {
    if (err) return callback(err);
    callback(null, key.getPublicKey());
  });
}

function requireScope(...requiredScopes) {
  return (req, res, next) => {
    const authHeader = req.headers.authorization;
    if (!authHeader?.startsWith('Bearer ')) {
      return res.status(401).json({ error: 'Missing bearer token' });
    }

    const token = authHeader.slice(7);

    jwt.verify(token, getSigningKey, {
      algorithms: ['RS256'],
      issuer: 'https://auth.yourapp.com',
      audience: 'https://api.yourapp.com',
    }, (err, decoded) => {
      if (err) {
        return res.status(401).json({ error: 'Invalid token' });
      }

      // Check scopes
      const tokenScopes = (decoded.scope || '').split(' ');
      const hasScope = requiredScopes.some(s => tokenScopes.includes(s));
      
      if (!hasScope) {
        return res.status(403).json({ error: 'Insufficient scope' });
      }

      req.client = decoded;
      next();
    });
  };
}

// Usage
app.get('/api/data', requireScope('read:data'), (req, res) => {
  // req.client contains decoded token claims
  res.json({ data: '...' });
});

Threat model

Threat Risk Level Mitigation
Client secret leaked 🟡 Medium Secret alone isn't enough — attacker still needs to exchange it. Rotate the secret and all issued tokens expire naturally.
Access token stolen 🟢 Low Token expires in minutes/hours. Blast radius is time-bounded.
Token replayed 🟢 Low Short expiry + scope limits = limited damage window
Privilege escalation 🟢 Low Scopes are baked into the token, validated on every request
Scope misconfiguration 🟡 Medium Overly broad scopes undo most security benefits

Why this is the default for production

OAuth Client Credentials gives you everything API keys don't:

  • Short-lived tokens: If a token leaks, it's dead in an hour (or less — you control expiry)
  • Scoped access: Agents only get the permissions they request and are allowed to have
  • Standard protocol: Every auth library, every language, every auth provider supports it
  • Auditability: Token issuance is logged. You know which client authenticated when.
  • Rotation without downtime: Rotate the client secret → old tokens still work until they expire → new tokens use the new secret

OAuth 2.1 (draft spec) tightens this further by removing the implicit and password grant types entirely, mandating PKCE for public clients, and requiring refresh token rotation. Anthropic has already adopted OAuth 2.1 as the authentication standard for the Model Context Protocol (MCP).


3. JWTs as bearer assertions: beyond access tokens

JWTs appear inside the OAuth flow (as access tokens), but they can also serve as the authentication credential itself — replacing client_id + client_secret with a signed assertion. This is the JWT Bearer Grant (RFC 7523).

How it works

Instead of sending a client secret, the agent signs a JWT with its private key and sends that as proof of identity:

Agent (holds private key)      Auth Server (holds public key)
  |                                  |
  |-- POST /oauth/token ------------>|
  |   grant_type=urn:ietf:params:    |
  |     oauth:grant-type:jwt-bearer  |
  |   assertion=<signed-jwt>         |
  |   scope=read:data                |
  |                                  |
  |<-- { access_token } -------------|

Creating the assertion (Node.js)

const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const fs = require('fs');

function createClientAssertion(clientId, tokenEndpoint) {
  // Agent's private key — never leaves the agent's environment
  const privateKey = fs.readFileSync('./agent-private-key.pem');

  const now = Math.floor(Date.now() / 1000);

  return jwt.sign(
    {
      iss: clientId,           // The agent's client ID
      sub: clientId,           // Same — the agent is authenticating itself
      aud: tokenEndpoint,      // The auth server's token endpoint
      iat: now,
      exp: now + 300,          // 5 minute validity
      jti: crypto.randomUUID() // Unique ID to prevent replay
    },
    privateKey,
    { algorithm: 'RS256' }
  );
}

// Token request using JWT assertion
async function getAccessToken(clientId) {
  const assertion = createClientAssertion(
    clientId,
    'https://auth.yourapp.com/oauth/token'
  );

  const response = await fetch('https://auth.yourapp.com/oauth/token', {
    method: 'POST',
    headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
    body: new URLSearchParams({
      grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
      assertion: assertion,
      scope: 'read:data write:data',
    }),
  });

  return response.json();
}

Verifying assertions on the server

app.post('/oauth/token', async (req, res) => {
  const { grant_type, assertion, scope } = req.body;

  if (grant_type !== 'urn:ietf:params:oauth:grant-type:jwt-bearer') {
    return res.status(400).json({ error: 'unsupported_grant_type' });
  }

  // Decode without verifying first to extract the issuer
  const unverified = jwt.decode(assertion, { complete: true });
  if (!unverified) {
    return res.status(400).json({ error: 'invalid_assertion' });
  }

  // Look up the client's registered public key
  const client = await db.clients.findOne({ 
    clientId: unverified.payload.iss 
  });

  if (!client?.publicKey) {
    return res.status(401).json({ error: 'invalid_client' });
  }

  try {
    const decoded = jwt.verify(assertion, client.publicKey, {
      algorithms: ['RS256'],       // CRITICAL: restrict algorithms
      audience: 'https://auth.yourapp.com/oauth/token',
      clockTolerance: 30,
    });

    // Check jti hasn't been used before (replay protection)
    const used = await db.usedJtis.findOne({ jti: decoded.jti });
    if (used) {
      return res.status(400).json({ error: 'assertion_replay' });
    }
    await db.usedJtis.insertOne({ 
      jti: decoded.jti, 
      exp: new Date(decoded.exp * 1000) 
    });

    // Issue access token (same as Client Credentials flow)
    const accessToken = issueAccessToken(client, scope);
    res.json({ access_token: accessToken, token_type: 'Bearer' });

  } catch (err) {
    return res.status(401).json({ error: 'invalid_assertion' });
  }
});

Why JWT assertions matter for agents

The key advantage: the secret never travels over the network. With Client Credentials, you send client_secret to the token endpoint on every token request. With JWT assertions, you send a signed proof that you hold the private key — but the key itself stays on the agent's machine.

This is the same principle behind SSH keys: you prove identity with a signature, not by transmitting the secret.

JWT security pitfalls (real attacks, not theoretical)

JWTs have a well-documented history of implementation vulnerabilities:

Algorithm confusion attack: If your verification code accepts the algorithm from the JWT header without restriction, an attacker can:

  1. Take a JWT signed with RS256 (asymmetric)
  2. Change the header to HS256 (symmetric)
  3. Sign it using the server's public key as the HMAC secret
  4. The server verifies using the public key as an HMAC secret — and it passes
// ❌ VULNERABLE — accepts algorithm from token header
jwt.verify(token, publicKey);

// ✅ SECURE — explicitly restricts allowed algorithms
jwt.verify(token, publicKey, { algorithms: ['RS256'] });

none algorithm attack: Some libraries accept alg: "none", which means the token isn't signed at all:

// ❌ An unsigned JWT that some libraries will accept
eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJzdWIiOiIxMjM0NTY3ODkwIn0.

Missing kid validation: If you use a Key ID (kid) to select the verification key, make sure it's validated. Attackers can inject SQL or path traversal via the kid field if it's used in a database query or file path unsanitized.

These aren't hypothetical. CVE-2025-9312, disclosed in November 2025, showed a real mTLS authentication bypass in WSO2 where the system accepted requests without valid client certificates when mTLS was enabled. The point: protocol-level security is only as good as the implementation.

Threat model

Threat Risk Level Mitigation
Private key leaked 🟡 Medium Key only exists on agent's machine. No network transmission.
Algorithm confusion 🔴 Critical (if vulnerable) ALWAYS restrict algorithms in verification
Assertion replay 🟡 Medium jti claim + server-side tracking
Token forgery 🟢 Low (with RS256) Asymmetric crypto — forging requires the private key

4. mTLS: transport-layer identity

Mutual TLS (mTLS) pushes authentication down to the transport layer. Both the client and server present X.509 certificates during the TLS handshake. The server verifies the client's certificate against a trusted Certificate Authority (CA), and the client verifies the server's. Authentication happens before any HTTP request is even sent.

How it works

Agent (client cert)             Your API (server cert)
  |                                   |
  |-- TLS ClientHello -------------->|
  |<-- TLS ServerHello + CertReq ----|
  |-- Client Certificate ----------->|
  |-- TLS Finished ----------------->|
  |<-- TLS Finished -----------------|
  |                                   |
  |   (Encrypted channel established) |
  |                                   |
  |-- GET /api/data ----------------->|
  |<-- 200 OK ------------------------|

Server setup (Node.js)

const https = require('https');
const fs = require('fs');
const express = require('express');

const app = express();

// mTLS server configuration
const server = https.createServer(
  {
    // Server's own certificate and key
    cert: fs.readFileSync('./server-cert.pem'),
    key: fs.readFileSync('./server-key.pem'),

    // CA certificate(s) that issued client certs
    // ONLY clients with certs from this CA are accepted
    ca: fs.readFileSync('./client-ca-cert.pem'),

    // Require client certificates
    requestCert: true,
    rejectUnauthorized: true,
  },
  app
);

// Extract client identity from the certificate
app.use((req, res, next) => {
  const cert = req.socket.getPeerCertificate();
  
  if (!cert || !cert.subject) {
    return res.status(403).json({ error: 'No client certificate' });
  }

  // The Common Name (CN) or Subject Alternative Name (SAN)
  // identifies the agent
  req.clientIdentity = {
    commonName: cert.subject.CN,
    organization: cert.subject.O,
    fingerprint: cert.fingerprint256,
    validFrom: cert.valid_from,
    validTo: cert.valid_to,
  };

  next();
});

app.get('/api/data', (req, res) => {
  console.log(`Request from: ${req.clientIdentity.commonName}`);
  res.json({ data: '...' });
});

server.listen(443);

Client setup (Python)

import httpx

# Agent makes requests with its client certificate
client = httpx.Client(
    cert=("./agent-cert.pem", "./agent-key.pem"),
    verify="./server-ca-cert.pem",  # Verify server's certificate
)

response = client.get("https://api.yourapp.com/data")
print(response.json())

Certificate management: the real cost

mTLS is the strongest authentication mechanism on this list. It's also the hardest to operate. Here's what you're signing up for:

Certificate lifecycle management:

  • Generate a CA (or use an intermediate CA)
  • Issue client certificates for each agent
  • Distribute certificates securely to agents
  • Track certificate expiry dates
  • Rotate certificates before they expire
  • Revoke compromised certificates via CRL or OCSP

What this looks like at scale:

# Generate a client certificate for an agent (simplified)
# In production, use a proper PKI tool like step-ca, Vault, or SPIFFE/SPIRE

# 1. Generate agent's private key
openssl genrsa -out agent-key.pem 4096

# 2. Create a Certificate Signing Request
openssl req -new -key agent-key.pem -out agent-csr.pem \
  -subj "/CN=agent-acme-corp/O=AcmeCorp/OU=agents"

# 3. Sign it with your CA
openssl x509 -req -in agent-csr.pem \
  -CA ca-cert.pem -CAkey ca-key.pem \
  -CAcreateserial -out agent-cert.pem \
  -days 90 -sha256

# 4. Distribute agent-cert.pem and agent-key.pem to the agent
# 5. Set a reminder to rotate in 60 days
# 6. Repeat for every agent

For organizations running large-scale mTLS, projects like SPIFFE/SPIRE automate workload identity issuance. SPIRE agents run on each node, automatically request short-lived certificates (SVIDs) from the SPIRE server, and rotate them without manual intervention. It's powerful infrastructure — but it's infrastructure you need to build and maintain.

Threat model

Threat Risk Level Mitigation
Private key stolen from agent 🟡 Medium Certificate has a defined validity period. Revoke via CRL/OCSP.
Certificate replay 🟢 Low TLS handshake includes fresh nonces — can't replay a handshake
CA compromise 🔴 Critical All certificates become untrustworthy. Use short-lived certs + automation.
Certificate expiry outage 🟡 Medium Automated rotation via SPIFFE/SPIRE or Vault PKI
Man-in-the-middle 🟢 Very Low Both sides authenticate each other — MITM requires compromising both CAs

The comparison matrix

Here's the full side-by-side, evaluated across dimensions that matter for AI agent access:

Free Tool

How agent-ready is your website?

Run a free scan to see how AI agents experience your signup flow, robots.txt, API docs, and LLM visibility.

Run a free scan →
Dimension API Keys OAuth Client Creds JWT Assertions mTLS
Setup complexity 5 min 2-4 hours 4-8 hours 1-3 days
Secret travels on wire Every request Only at token exchange Never (signed proof) Never (TLS handshake)
Token lifetime ♾️ (until revoked) Minutes to hours Assertion: minutes; access token: hours Cert validity: days to months
Scope/permission model Usually none Built-in scopes Built-in scopes Typically identity-only (authz is separate)
Blast radius of leak Full access, indefinitely Limited by token expiry + scope Private key leak: can mint assertions Cert leak: bounded by validity + revocation
Revocation speed Instant (delete from DB) Wait for token expiry (or use introspection) Instant (remove public key) CRL/OCSP propagation delay
Standards compliance No standard RFC 6749, RFC 6750, OAuth 2.1 RFC 7523 RFC 8705 (OAuth mTLS), X.509
Agent implementation effort Trivial Low (one HTTP call for token) Medium (crypto + signing) Medium-High (cert management)
Works through proxies/CDNs ✅ Yes ✅ Yes ✅ Yes ⚠️ Often terminated at edge
Multi-tenant support Manual Native (client per tenant) Native Possible (via cert attributes)

The decision framework

Don't start from "what's most secure?" Start from "what's appropriate for my use case?"

Start here: API keys

Use API keys when:

  • You're building an MVP and need agent access in days, not weeks
  • The agent is accessing read-only, low-sensitivity data
  • You're behind a service mesh or VPN where the network provides the trust boundary
  • It's a developer sandbox / testing environment

Graduate when: you have more than ~10 external agent consumers, handle sensitive data, or need per-agent permission scoping.

Default choice: OAuth Client Credentials

Use OAuth Client Credentials when:

  • You're running a production API with external agent consumers
  • You need scoped access (read vs. write, different resource types)
  • You want standard-compliant auth that every agent framework already supports
  • You need audit trails of which agents accessed what, when
  • You want time-bounded access without manual revocation

This is the right answer for 80% of SaaS companies. It's the reason Anthropic chose OAuth 2.1 for MCP, and it's what we recommend as the baseline in our agent-readiness benchmark.

When to add JWT assertions

Layer JWT assertions on top of OAuth when:

  • You can't afford to send client secrets over the network (regulated industries)
  • Agents run in environments where secrets in environment variables are a risk
  • You need non-repudiation — cryptographic proof of which agent authenticated
  • Your clients (agent operators) manage their own key pairs

Common in: financial services, healthcare APIs, enterprise integrations where client secret rotation is operationally expensive.

When to use mTLS

Use mTLS when:

  • You're in a zero-trust network environment
  • Compliance requirements mandate transport-layer authentication (PCI DSS, SOC 2 with enhanced controls)
  • You operate a service mesh where mTLS is already the standard (Istio, Linkerd)
  • You need the strongest possible authentication guarantee and have the infrastructure team to operate it

Be honest about operational cost. mTLS is the most secure option on this list. It's also the one most likely to cause an outage at 3 AM because a certificate expired and nobody noticed. If you don't have automated certificate management, you'll spend more time fighting mTLS than benefiting from it.


Hybrid patterns: what production actually looks like

In practice, most systems don't use just one mechanism. Here are patterns we see in companies with mature agent authentication:

Pattern 1: API keys for onboarding, OAuth for production

Developer signs up → gets test API key → starts building
                                            ↓
                            Registers OAuth client for production
                                            ↓
                   Production agent uses Client Credentials flow

Stripe, Twilio, and most developer-first platforms follow this pattern. The API key gets you started in 30 seconds. OAuth is what you ship.

Pattern 2: OAuth + JWT assertions for high-security flows

Normal agent operations → OAuth Client Credentials (client_secret)
                                      ↓
Sensitive operations → OAuth with JWT assertion (signed proof)
(billing, PII access)

Step up authentication for operations that touch sensitive data. The agent uses standard Client Credentials for read operations and switches to JWT assertions for writes or PII access.

Pattern 3: mTLS at the edge + OAuth for app-layer authz

Agent → mTLS terminates at load balancer/API gateway
                    ↓
        Certificate CN extracted, passed as header
                    ↓
        OAuth token verified for scope/permissions

This is common in Kubernetes environments with service meshes. mTLS handles identity at the transport layer. OAuth handles authorization at the application layer. You get the best of both.


What this means for agent readiness

If you're building a SaaS product that AI agents will access, here's the minimum viable authentication stack:

  1. Support OAuth 2.0 Client Credentials — this is table stakes. Publish your token endpoint, document your scopes, and make it work with standard libraries.

  2. Offer a self-service client registration flow — agents (and their operators) should be able to create credentials without emailing your sales team.

  3. Publish a discovery document/.well-known/openid-configuration or /.well-known/oauth-authorization-server so agents can find your auth endpoints programmatically.

  4. Set sane token lifetimes — 1 hour is a good default. Short enough to limit blast radius, long enough that agents aren't hammering your token endpoint.

  5. Design meaningful scopesread:contacts write:contacts is useful. admin is not.

Your agent-readiness score reflects these practices. Companies that support OAuth with proper scopes and self-service registration consistently score 15-20 points higher than those offering only API keys.

The choice of auth mechanism isn't permanent. Start with API keys, graduate to OAuth Client Credentials, and layer on JWT assertions or mTLS when your security requirements demand it. The important thing is building the right foundation — and for most SaaS companies in 2026, that foundation is OAuth.


Want to see how your authentication stack scores? Run your domain through the AgentGate benchmark and see where you stand.

Get Started

Ready to make your product agent-accessible?

Add a few lines of code and let AI agents discover, request access, and get real credentials — with human oversight built in.

Get started with Anon →