AI agents are calling your API: 5 auth mistakes that create backdoors
A 2026 survey of 900+ practitioners found that 80.9% of engineering teams run AI agents in testing or production. The same survey found that only 21.9% treat those agents as independent identity-bearing entities. The rest share credentials, skip audit trails, and grant blanket API access to code that runs unsupervised.
That gap is a security problem. OWASP released its Top 10 for Agentic Applications in 2026, and Tool Misuse and Privilege Escalation tops the list at 520 reported incidents. Meta documented a rogue AI agent that passed every identity check because it held valid credentials from a confused deputy scenario. 48.9% of organizations can't even see what their agents are doing with API access.
This post covers the five auth mistakes that turn your API into an open door for AI agents, and how to close each one with working code.
Mistake 1: sharing a single API key across all agents
44% of teams use static API keys for AI agents. When multiple agents share one key, you lose three things: audit trails (which agent made this request?), granular revocation (revoking one agent kills them all), and per-agent rate limiting (one runaway agent exhausts the quota for every agent).
# Three different agents share one API key
# Agent A: summarizes support tickets
# Agent B: processes refunds
# Agent C: exports customer PII
curl -X POST https://api.acme.com/v1/customers/export \
-H "Authorization: Bearer sk_live_shared_key_for_all_agents" \
-H "Content-Type: application/json" \
-d '{"format": "csv", "include_pii": true}'
# Which agent made this request? You can't tell.
# Need to revoke Agent C? You break A and B too. The fix: issue a unique credential per agent. Each key maps to a specific agent identity, a defined scope of endpoints, and its own rate limit. When you need to revoke Agent C, agents A and B keep running.
# Each agent gets its own scoped API key
curl -X POST https://api.acme.com/v1/tickets/summary \
-H "Authorization: Bearer sk_live_agent_a_tickets_readonly" \
-H "X-Agent-ID: agent-ticket-summarizer" \
-H "Content-Type: application/json" \
-d '{"ticket_id": "TKT-4829"}'
Use the X-Agent-ID header alongside the API key so your logs always attribute requests to a
specific agent. Decode the token to verify its claims match the agent making the call.
# Inspect what permissions an agent token carries
curl -s -X POST https://api.botoi.com/v1/jwt/decode \
-H "Content-Type: application/json" \
-d '{"token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhZ2VudC1yZWZ1bmQtcHJvY2Vzc29yIiwic2NvcGVzIjpbInJlZnVuZHM6d3JpdGUiLCJvcmRlcnM6cmVhZCJdLCJleHAiOjE3NDk2MDAsMDAwLCJpc3MiOiJhY21lLWF1dGgifQ.signature"}' {
"success": true,
"data": {
"header": {
"alg": "RS256",
"typ": "JWT"
},
"payload": {
"sub": "agent-refund-processor",
"scopes": ["refunds:write", "orders:read"],
"exp": 1749600000,
"iss": "acme-auth"
}
}
} The confused deputy problem: Meta's rogue agent incident happened because the agent held valid credentials that granted access beyond its intended scope. The agent passed every identity check. The credentials were legitimate. The problem was that no one scoped them to the agent's purpose.
Mistake 2: granting full API access when an agent needs one endpoint
35% of organizations rely on shared service accounts for AI agents. A service account with full API access means your ticket-summarization agent can also process refunds, export customer PII, and delete records. That's a principle of least privilege violation.
Scope each agent token to the minimum endpoints and actions it needs. A refund-processing agent gets
refunds:write and orders:read. Nothing more.
// Mint a scoped token for each agent at deploy time
interface AgentTokenClaims {
sub: string; // unique agent identity
scopes: string[]; // minimum required permissions
exp: number; // short TTL: 1-24 hours
iss: string; // your auth service
rate_limit: number; // requests per minute for this agent
}
const agentClaims: AgentTokenClaims = {
sub: "agent-refund-processor",
scopes: ["refunds:write", "orders:read"],
exp: Math.floor(Date.now() / 1000) + 3600, // 1 hour
iss: "acme-auth",
rate_limit: 30,
};
Before deploying an agent, check that its token carries the correct scopes and hasn't expired. Decode the
token with the Botoi JWT endpoint to inspect the claims and the is_expired flag.
# Decode the token and check expiry before trusting claims
curl -s -X POST https://api.botoi.com/v1/jwt/decode \
-H "Content-Type: application/json" \
-d '{
"token": "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJhZ2VudC10aWNrZXQtc3VtbWFyaXplciIsInNjb3BlcyI6WyJ0aWNrZXRzOnJlYWQiXSwiZXhwIjoxNzQ5Njg2NDAwfQ.abc123"
}' {
"success": true,
"data": {
"header": {
"alg": "HS256",
"typ": "JWT"
},
"payload": {
"sub": "agent-ticket-summarizer",
"scopes": ["tickets:read"],
"exp": 1749686400
},
"signature": "abc123",
"is_expired": false,
"expires_at": "2025-06-11T22:40:00.000Z",
"expires_in_seconds": 3600
}
} | Approach | Scope control | Revocation | Audit trail | Risk |
|---|---|---|---|---|
| Shared service account | None | Breaks all agents | No attribution | High |
| Static API key per agent | Key-level only | Per-agent | By key | Medium |
| Scoped JWT per agent | Endpoint + action | Per-agent + expiry | Full claims | Low |
| OBO token exchange | Inherited + narrowed | Per-session | Full chain | Lowest |
New IAM solutions for AI agents support On-Behalf-Of (OBO) token exchange. The agent receives a token derived from the original user's session, automatically scoped to the permissions that user granted. This approach creates a full authorization chain from user to agent to API.
Mistake 3: no expiration on agent credentials
43% of teams use username/password authentication for AI agents. Those credentials don't expire. Static API keys don't expire either, unless you build expiration into your system. An agent credential that lives forever is a credential that can be stolen, leaked in logs, or committed to a repository and exploited months later.
Set a maximum TTL of 1 to 24 hours on agent tokens. Generate a fresh credential on every deploy or on a schedule. Use the Botoi password generator to create high-entropy secrets for HMAC signing.
# Generate a high-entropy secret for HMAC signing
curl -s -X POST https://api.botoi.com/v1/password/generate \
-H "Content-Type: application/json" \
-d '{"length": 64, "uppercase": true, "lowercase": true, "numbers": true, "symbols": false}' {
"success": true,
"data": {
"password": "kX9mT2vR8nL4wQ6jY3bA5cF7hD1gP0sE9uI2oK4lM6nB8xZ3vC5tR7yW0qJ1aG"
}
} Add HMAC request signing
Beyond token-based auth, sign each request payload with HMAC-SHA256. Your API verifies the signature before processing the request. This proves the payload wasn't tampered with in transit and ties each request to a specific agent secret.
# Sign a request payload so your API can verify the sender
PAYLOAD='{"agent_id":"agent-refund-processor","action":"refund","order_id":"ORD-7291"}'
curl -s -X POST https://api.botoi.com/v1/hash/hmac \
-H "Content-Type: application/json" \
-d "{
\"text\": \"$PAYLOAD\",
\"key\": \"kX9mT2vR8nL4wQ6jY3bA5cF7hD1gP0sE9uI2oK4lM6nB8xZ3vC5tR7yW0qJ1aG\",
\"algorithm\": \"sha256\"
}" {
"success": true,
"data": {
"hmac": "a3f8c2d1e5b7094f6d2c8a1e3b5f7d9e4c6a8b0d2f4e6c8a0b2d4f6e8c0a2b4",
"algorithm": "sha256"
}
} Your API server recalculates the HMAC using its stored copy of the agent's secret. If the hashes match, the request is authentic. If they don't, reject it.
Automate secret rotation
Manual rotation doesn't scale when you run dozens of agents. Build a rotation script that generates a new secret, deploys it, verifies connectivity, then revokes the old one.
import Botoi from "@botoi/sdk";
const botoi = new Botoi({ apiKey: process.env.BOTOI_API_KEY });
async function rotateAgentSecret() {
// Generate a new 64-character secret
const { data } = await botoi.password.generate({
length: 64,
uppercase: true,
lowercase: true,
numbers: true,
symbols: false,
});
console.log("New secret generated. Deploy to agent, then revoke the old one.");
// Hash the new secret for storage (never store plaintext)
const hashed = await botoi.hash.sha256({ input: data.password });
console.log("Hashed for DB:", hashed.data.hash);
return { secret: data.password, hash: hashed.data.hash };
}
rotateAgentSecret(); Mistake 4: skipping request logging for machine-to-machine calls
48.9% of organizations are blind to machine-to-machine traffic. They log human API calls but exempt internal service-to-service communication from their observability stack. AI agents fall into that blind spot.
When an agent misbehaves, sends a flood of requests, accesses endpoints outside its scope, or gets compromised, you need to answer three questions: which agent, when, and what did it touch? Without request logging, you can't answer any of them.
// Express middleware: log every agent request
app.use("/v1/*", (req, res, next) => {
const agentId = req.headers["x-agent-id"] || "unknown";
const start = Date.now();
res.on("finish", () => {
const log = {
timestamp: new Date().toISOString(),
agent_id: agentId,
method: req.method,
path: req.path,
status: res.statusCode,
duration_ms: Date.now() - start,
ip: req.ip,
user_agent: req.headers["user-agent"],
};
console.log(JSON.stringify(log));
});
next();
}); Capture the agent identity, endpoint, HTTP method, status code, and duration on every request. Send structured logs to your observability platform. Set alerts for anomalies: sudden spikes in request volume, requests to endpoints outside the agent's scope, or elevated error rates.
Track what the agent sends, not what it says it sends. Log the request payload hash
(using /v1/hash) alongside the metadata. This gives you tamper evidence without
storing sensitive payloads in your log system.
Mistake 5: treating agent auth the same as human auth
Only 18% of security leaders are confident their IAM systems can manage agent identities. The other 82% bolt agent access onto human auth flows that weren't designed for autonomous callers.
Human auth assumes a person reads an OAuth consent screen, enters a password, and responds to an MFA prompt. Agents don't do any of that. They need a different auth model with three properties.
1. Scoped tokens with explicit permissions
Each agent token declares which endpoints it can call and which actions it can perform. The API enforces those scopes on every request. No scope, no access.
2. Per-identity rate limits
A human user sends a few requests per minute. An agent sends hundreds or thousands. Rate limits per agent identity prevent one agent from starving others and cap the blast radius if an agent goes rogue.
3. Request attribution at every layer
Every request carries the agent identity from the edge proxy through the application layer to the database query log. When something goes wrong, you trace the full path from agent to action.
Audit an agent token with the Botoi SDK
Combine the JWT and password endpoints into a single audit function. Decode the token, check its expiration, verify the signature, and flag overly broad scopes.
import Botoi from "@botoi/sdk";
const botoi = new Botoi({ apiKey: process.env.BOTOI_API_KEY });
async function auditAgentToken(token: string, secret: string) {
// Step 1: Decode to inspect claims
const decoded = await botoi.jwt.decode({ token });
const claims = decoded.data.payload;
console.log("Agent:", claims.sub);
console.log("Scopes:", claims.scopes);
// Step 2: Check expiration
const now = Math.floor(Date.now() / 1000);
const hoursLeft = (claims.exp - now) / 3600;
if (hoursLeft < 0) {
console.error("Token expired", Math.abs(hoursLeft).toFixed(1), "hours ago");
return { valid: false, reason: "expired" };
}
if (hoursLeft > 24) {
console.warn("Token lives longer than 24 hours; rotate sooner");
}
// Step 3: Verify signature
const verified = await botoi.jwt.verify({ token, secret, algorithms: ["HS256"] });
if (!verified.data.valid) {
console.error("Signature verification failed");
return { valid: false, reason: "invalid_signature" };
}
// Step 4: Check scope breadth
const dangerousScopes = claims.scopes.filter(
(s: string) => s === "*" || s === "admin" || s.endsWith(":*")
);
if (dangerousScopes.length > 0) {
console.warn("Overly broad scopes:", dangerousScopes);
}
return { valid: true, agent: claims.sub, scopes: claims.scopes, hoursLeft };
}
const result = await auditAgentToken(process.env.AGENT_TOKEN!, process.env.SIGNING_SECRET!);
console.log(result); Run this check as a pre-deploy step. If the token is expired, has a TTL longer than 24 hours, fails signature verification, or carries wildcard scopes, block the deployment.
A checklist for AI agent API auth
| Check | What to verify | Botoi endpoint |
|---|---|---|
| Unique identity per agent | No two agents share a key or token sub claim | /v1/jwt/decode |
| Minimum scopes | Token scopes match the agent's purpose; no wildcards | /v1/jwt/decode |
| Token expiration | TTL under 24 hours; refresh mechanism in place | /v1/jwt/decode |
| Request signing | Payload HMAC matches on the server side | /v1/hash/hmac |
| Secret rotation | Secrets rotate on deploy or on a schedule | /v1/password/generate |
| Request logging | Every agent request is logged with identity and payload hash | /v1/hash |
The survey data is clear: teams deploy AI agents faster than they secure them. 80.9% have agents running, but fewer than 1 in 5 give those agents their own identity. Every mistake on this list, shared keys, broad scopes, permanent credentials, silent traffic, human-shaped auth, is a backdoor waiting for the next confused deputy incident.
Pick one mistake from this list. Fix it this week. Then move to the next.
Frequently asked questions
- Should AI agents use the same API keys as human users?
- No. AI agents need dedicated credentials with scoped permissions, per-identity rate limits, and automatic expiration. A 2026 survey of 900+ practitioners found that only 21.9% of teams treat agents as independent identity-bearing entities, and 44% still share static API keys across agents.
- How do you rotate API keys for AI agents without downtime?
- Generate a new credential, deploy it to the agent, verify it works, then revoke the old key. Use short-lived tokens (1-24 hours) with automatic refresh so rotation happens without manual steps. The Botoi /v1/password/generate endpoint creates high-entropy secrets, and /v1/jwt/decode inspects the new token before cutting over.
- What is the OWASP Top 10 for Agentic Applications?
- Released in 2026, the OWASP Top 10 for Agentic Applications lists the most common security risks when AI agents interact with APIs and tools. Tool Misuse and Privilege Escalation tops the list at 520 reported incidents. Other risks include insufficient access control, missing audit trails, and confused deputy attacks.
- How do you monitor machine-to-machine API traffic from AI agents?
- Log every request with the agent identity, timestamp, endpoint, and payload hash. Use structured JSON logs and send them to a centralized system (Datadog, Grafana, or CloudWatch). A 2026 study found 48.9% of organizations cannot monitor AI agent traffic at all, leaving them blind to compromised or misbehaving agents.
- What is a confused deputy attack in the context of AI agents?
- A confused deputy attack happens when an AI agent uses its legitimate credentials to perform actions its operator did not intend. The agent passes every identity check because it holds valid tokens, but it acts on instructions from an untrusted source. Meta documented this pattern in a rogue agent incident where the agent accessed resources beyond its intended scope.
Try this API
JWT Decoder API — interactive playground and code examples
More guide posts
Start building with botoi
150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.