# AgentBouncer Documentation
> Verify signed AI-agent requests and apply project-specific access policies.
Documentation version: 0.1.1
Canonical portal: https://agentbouncer.io/en/docs
Canonical Markdown: https://agentbouncer.io/en/docs.md
## Documentation index
- [AgentBouncer overview](#overview): Verify signed AI-agent requests and apply project-specific access policies.
- [Quickstart](#quickstart): Create a project, copy an API key, and protect an endpoint.
- [Core concepts](#core-concepts): Understand projects, API keys, signing keys, providers, and policies.
- [Create and manage projects](#projects): Configure the trust boundary for an application or MCP server.
- [Project API keys](#api-keys): Authenticate your server when calling the AgentBouncer API.
- [REST API integration](#rest-api): Call AgentBouncer from any language or backend framework.
- [JavaScript and TypeScript SDK](#sdk): Verify signed requests and apply the final AgentBouncer policy decision.
- [Next.js App Router](#nextjs): Protect a Next.js route while preserving the external request URL.
- [Next.js without the SDK](#manual-nextjs): Send a minimal verification payload directly to the REST API.
- [Express integration](#express): Protect Express routes using the REST verification endpoint.
- [Protect MCP tools](#mcp): Apply AgentBouncer decisions before executing MCP operations.
- [Project signing keys](#project-signing-keys): Create identities for agents owned directly by your project.
- [Register an agent provider](#providers): Publish a provider identity and public-key directory.
- [Policy modes](#policies): Choose how verified identity and trust data affect access.
- [Custom policies](#custom-policies): Create fine-grained allow and deny rules.
- [Verification response](#response): Understand the fields returned by the verification API.
- [Reasons and errors](#errors): Common verification and policy decision reasons.
- [Verification events](#events): Monitor real traffic and investigate authorization decisions.
- [Security guidance](#security): Deploy AgentBouncer without leaking secrets or weakening verification.
- [Troubleshooting](#troubleshooting): Diagnose signature, URL, key-directory, and policy problems.
- [Production checklist](#production-checklist): Review the integration before enabling blocking policies.
## AgentBouncer overview
Verify signed AI-agent requests and apply project-specific access policies.
AgentBouncer verifies HTTP Message Signatures sent by AI agents, resolves the signer public key, evaluates your project policy, and returns a final access decision.
> **The most important integration rule**
>
> Use allowed as the final authorization decision. verified reports the cryptographic result, while allowed reports the effective project-policy decision.
| verified | allowed | Meaning |
| --- | --- | --- |
| true | true | The signature is valid and the request is allowed. |
| true | false | The signature is valid, but the project policy denied access. |
| false | true | The request was not verified but was allowed by MONITOR_ONLY or another permissive setting. |
| false | false | The request failed verification and access was denied. |
- Verify RFC 9421 HTTP Message Signatures.
- Resolve provider and project-owned public keys.
- Apply trust tiers, trust scores, actions, tools, and custom rules.
- Protect MCP tools, APIs, checkout operations, and autonomous workflows.
- Record verification events for analytics and incident investigation.
## Quickstart
Create a project, copy an API key, and protect an endpoint.
1. **Create a project** — Open the AgentBouncer dashboard, create a project, and enter the origin or host of the service you want to protect.
2. **Start in Monitor only** — Use MONITOR_ONLY during the first integration phase. Requests continue to run while AgentBouncer records what would have been blocked.
3. **Copy the project API key** — The API key authenticates your backend when it calls the AgentBouncer verification API. It is not an agent signing key.
4. **Send the original request URL and signature headers** — The URL must match the URL that the agent signed. Preserve Signature, Signature-Input, and Signature-Agent.
5. **Authorize with allowed** — Reject the request only when allowed is false.
### Verify a request with REST
```bash
curl -X POST https://agentbouncer.io/api/v1/verify \
-H "Authorization: Bearer ab_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://merchant.example/api/mcp/weather",
"method": "POST",
"headers": {
"signature": "sig1=:BASE64_SIGNATURE:",
"signature-input": "sig1=(\"@authority\" \"signature-agent\");created=1760000000;expires=1760000300;keyid=\"KEY_ID\";tag=\"web-bot-auth\"",
"signature-agent": "\"https://agent.example\"",
"content-type": "application/json"
},
"expectedTag": "web-bot-auth",
"action": "tools:call",
"tool": "weather"
}'
```
### Apply the final decision
```typescript
const verification = await verifyWithAgentBouncer(request);
if (!verification.allowed) {
return Response.json(
{
ok: false,
error: "agent_access_denied",
verification,
},
{
status: verification.verified ? 403 : 401,
}
);
}
// Continue to the protected operation.
return runProtectedTool();
```
## Core concepts
Understand projects, API keys, signing keys, providers, and policies.
| Concept | Purpose |
| --- | --- |
| Project | A protected application, API, MCP server, origin, or trust boundary. |
| Project API key | Authenticates your backend when calling /api/v1/verify. |
| Project signing key | Identifies and signs requests from one of your own agents. |
| Provider | A registered external AI-agent provider with a signature identity and public-key directory. |
| Provider key | A public key discovered in the provider HTTP Message Signatures directory. |
| Verification event | An immutable record of the cryptographic result, policy decision, signer, risk, and request metadata. |
| Policy | Rules that convert verification and trust information into allowed=true or allowed=false. |
> **API keys and signing keys are different**
>
> The project API key authorizes calls from your server to AgentBouncer. A signing key is held by an agent and signs the original HTTP request.
## Create and manage projects
Configure the trust boundary for an application or MCP server.
1. **Create the project** — Choose a descriptive name such as Production MCP, Checkout API, or Internal automation.
2. **Set the origin** — Use the public origin that agents call. Reverse-proxy and internal deployment URLs should not replace the externally signed URL.
3. **Choose a policy** — Start with MONITOR_ONLY, review verification events, and then move to BLOCK_UNKNOWN or a stricter policy.
4. **Create project signing keys if needed** — Use project keys for internal agents, development clients, and integrations that are owned by the project.
5. **Review events** — Inspect verified, allowed, reason, signer type, provider, project key, action, tool, risk, and policy details.
> **Recommended project boundaries**
>
> Use separate projects for production and development. Consider separate projects when applications have materially different trust or authorization requirements.
## Project API keys
Authenticate your server when calling the AgentBouncer API.
Project API keys use the ab_live_ prefix and must only be stored on your backend. Never expose them in browser JavaScript, public repositories, or client-side environment variables.
### .env.local
```bash
AGENTBOUNCER_API_KEY=ab_live_YOUR_API_KEY
AGENTBOUNCER_PUBLIC_ORIGIN=https://merchant.example
AGENTBOUNCER_VERIFY_URL=https://agentbouncer.io/api/v1/verify
```
- Store production keys in your hosting provider secret manager.
- Rotate a key if it was logged or committed accidentally.
- Do not send the API key to the incoming AI agent.
- Do not confuse the AgentBouncer API key with a private signing JWK.
## REST API integration
Call AgentBouncer from any language or backend framework.
### POST /api/v1/verify
```http
POST /api/v1/verify HTTP/1.1
Host: agentbouncer.io
Authorization: Bearer ab_live_YOUR_API_KEY
Content-Type: application/json
{
"url": "https://merchant.example/api/mcp/weather",
"method": "POST",
"headers": {
"signature": "sig1=:...:",
"signature-input": "sig1=(...);created=...;expires=...;keyid=\"...\";tag=\"web-bot-auth\"",
"signature-agent": "\"https://agent.example\""
},
"expectedTag": "web-bot-auth",
"action": "tools:call",
"tool": "weather",
"userAgent": "ExampleAgent/1.0"
}
```
| Field | Required | Description |
| --- | --- | --- |
| url | Yes | The exact external URL whose request was signed. |
| method | Recommended | The original HTTP method. Defaults to GET. |
| headers | Recommended | Original request headers used for signature verification. Signature fields may alternatively be supplied through signature, signatureInput, and signatureAgent. |
| signature | Conditional | The Signature header value. Required if it is not included in headers. |
| signatureInput | Conditional | The Signature-Input header value. Required if it is not included in headers. |
| signatureAgent | No | The Signature-Agent structured-field value. It may alternatively be included in headers. |
| expectedTag | No | The intent tag expected by the protected endpoint. |
| action | No | A semantic operation such as tools:call, products:read, checkout, or orders:write. |
| tool | No | The MCP tool or application capability being accessed. |
| userAgent | No | The original requester User-Agent for analytics. |
> **Preserve the signed URL**
>
> Do not replace the public target URL with the AgentBouncer verification endpoint URL, an internal container URL, or a reverse-proxy URL.
## JavaScript and TypeScript SDK
Verify signed requests and apply the final AgentBouncer policy decision.
### Install
```bash
npm install @agentbouncer/sdk
```
### Create a client
```typescript
import {
createAgentBouncer,
} from "@agentbouncer/sdk";
export const agentBouncer =
createAgentBouncer({
apiKey:
process.env.AGENTBOUNCER_API_KEY!,
publicOrigin:
process.env.AGENTBOUNCER_PUBLIC_ORIGIN,
verifyUrl:
process.env.AGENTBOUNCER_VERIFY_URL,
});
```
> **Class constructor**
>
> The SDK also exports AgentBouncer. new AgentBouncer(options) and createAgentBouncer(options) create the same client.
> **Configure the public origin behind a proxy**
>
> If request.url contains an internal deployment or reverse-proxy URL, configure publicOrigin with the externally visible origin that agents sign. You can alternatively pass targetUrl to each verify() call.
### verify()
```typescript
const verification =
await agentBouncer.verify({
request,
expectedTag: "web-bot-auth",
action: "tools:call",
tool: "weather",
});
if (!verification.allowed) {
return new Response(
"Agent access denied",
{
status: verification.verified
? 403
: 401,
}
);
}
// Continue to the protected operation.
```
### require()
```typescript
import {
AgentBouncerDeniedError,
AgentBouncerError,
} from "@agentbouncer/sdk";
try {
const verification =
await agentBouncer.require({
request,
expectedTag: "web-bot-auth",
action: "tools:call",
tool: "weather",
});
return runWeatherTool({
agent: verification.agent,
});
} catch (error) {
if (
error instanceof
AgentBouncerDeniedError
) {
return new Response(
"Agent access denied",
{
status:
error.verification.verified
? 403
: 401,
}
);
}
if (
error instanceof AgentBouncerError
) {
return new Response(
"Agent verification unavailable",
{
status: 503,
}
);
}
throw error;
}
```
> **SDK method semantics**
>
> verify() returns the complete verification result. require() throws AgentBouncerDeniedError when allowed is false. Network, timeout, API authentication, and malformed-response failures throw AgentBouncerError.
## Next.js App Router
Protect a Next.js route while preserving the external request URL.
### src/lib/agentbouncer-client.ts
```typescript
import {
createAgentBouncer,
} from "@agentbouncer/sdk";
const apiKey =
process.env.AGENTBOUNCER_API_KEY;
const publicOrigin =
process.env.AGENTBOUNCER_PUBLIC_ORIGIN;
if (!apiKey) {
throw new Error(
"Missing AGENTBOUNCER_API_KEY"
);
}
if (!publicOrigin) {
throw new Error(
"Missing AGENTBOUNCER_PUBLIC_ORIGIN"
);
}
export const agentBouncer =
createAgentBouncer({
apiKey,
publicOrigin,
verifyUrl:
process.env.AGENTBOUNCER_VERIFY_URL,
});
```
### src/app/api/mcp/weather/route.ts
```typescript
import {
AgentBouncerDeniedError,
AgentBouncerError,
} from "@agentbouncer/sdk";
import {
NextRequest,
NextResponse,
} from "next/server";
import {
agentBouncer,
} from "@/lib/agentbouncer-client";
export const runtime = "nodejs";
export const dynamic = "force-dynamic";
export async function POST(
req: NextRequest
) {
const requestId = crypto.randomUUID();
try {
const verification =
await agentBouncer.require({
request: req,
expectedTag: "web-bot-auth",
action: "tools:call",
tool: "weather",
});
const body = await req
.json()
.catch(() => ({}));
const city =
typeof body.city === "string"
? body.city
: "Berlin";
return NextResponse.json({
ok: true,
city,
weather: {
temperatureC: 21,
conditions: "Clear",
},
agent: verification.agent,
requestId,
});
} catch (error) {
if (
error instanceof
AgentBouncerDeniedError
) {
return NextResponse.json(
{
ok: false,
error: "mcp_access_denied",
reason:
error.verification.reason,
requestId,
},
{
status:
error.verification.verified
? 403
: 401,
}
);
}
console.error(
"AgentBouncer verification failed",
{
requestId,
error,
}
);
return NextResponse.json(
{
ok: false,
error:
"agentbouncer_unavailable",
message:
error instanceof
AgentBouncerError
? error.message
: "Unable to verify the request.",
requestId,
},
{
status: 503,
}
);
}
}
```
> **Do not use the old gate**
>
> Do not use if (!verification.verified || !verification.allowed). That incorrectly blocks requests allowed by MONITOR_ONLY or allowUnsigned.
## Next.js without the SDK
Send a minimal verification payload directly to the REST API.
### Minimal header collection
```typescript
import type {
NextRequest,
} from "next/server";
import {
pickVerificationHeaders,
} from "./pick-verification-headers";
function resolvePublicTargetUrl(
req: NextRequest
) {
const publicOrigin =
process.env
.AGENTBOUNCER_PUBLIC_ORIGIN;
if (!publicOrigin) {
return req.nextUrl.toString();
}
return new URL(
`${req.nextUrl.pathname}${req.nextUrl.search}`,
publicOrigin
).toString();
}
export async function verifyAgentRequest(
req: NextRequest,
options?: {
expectedTag?: string;
action?: string;
tool?: string;
}
) {
const targetUrl =
resolvePublicTargetUrl(req);
const response = await fetch(
process.env.AGENTBOUNCER_VERIFY_URL ??
"https://agentbouncer.io/api/v1/verify",
{
method: "POST",
headers: {
Authorization:
`Bearer ${process.env.AGENTBOUNCER_API_KEY}`,
"Content-Type": "application/json",
Accept: "application/json",
},
body: JSON.stringify({
url: targetUrl,
method: req.method,
headers:
pickVerificationHeaders(req),
expectedTag:
options?.expectedTag ?? null,
action:
options?.action ?? null,
tool:
options?.tool ?? null,
userAgent:
req.headers.get("user-agent"),
}),
cache: "no-store",
}
);
const result = await response
.json()
.catch(() => null);
if (!response.ok) {
throw new Error(
result?.detail ||
result?.reason ||
`AgentBouncer returned HTTP ${response.status}.`
);
}
if (
!result ||
typeof result.verified !== "boolean" ||
typeof result.allowed !== "boolean"
) {
throw new Error(
"AgentBouncer returned an invalid response."
);
}
return result;
}
```
### Call the verification endpoint
```typescript
import type { NextRequest } from "next/server";
import { pickVerificationHeaders } from "./pick-verification-headers";
export async function verifyAgentRequest(
req: NextRequest,
options?: {
expectedTag?: string;
action?: string;
tool?: string;
}
) {
const targetUrl = req.nextUrl.toString();
const response = await fetch(
process.env.AGENTBOUNCER_VERIFY_URL ??
"https://agentbouncer.io/api/v1/verify",
{
method: "POST",
headers: {
Authorization:
`Bearer ${process.env.AGENTBOUNCER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: targetUrl,
method: req.method,
headers: pickVerificationHeaders(req),
expectedTag: options?.expectedTag ?? null,
action: options?.action ?? null,
tool: options?.tool ?? null,
userAgent: req.headers.get("user-agent"),
}),
cache: "no-store",
}
);
const result = await response.json();
if (!response.ok && response.status === 401) {
throw new Error(
"AgentBouncer API authentication failed."
);
}
return result;
}
```
## Express integration
Protect Express routes using the REST verification endpoint.
```typescript
import type {
NextFunction,
Request,
Response,
} from "express";
export async function requireVerifiedAgent(
req: Request,
res: Response,
next: NextFunction
) {
try {
const protocol =
req.headers["x-forwarded-proto"] || req.protocol;
const host =
req.headers["x-forwarded-host"] ||
req.headers.host;
const targetUrl =
`${protocol}://${host}${req.originalUrl}`;
const verifyResponse = await fetch(
"https://agentbouncer.io/api/v1/verify",
{
method: "POST",
headers: {
Authorization:
`Bearer ${process.env.AGENTBOUNCER_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
url: targetUrl,
method: req.method,
headers: {
signature: req.headers.signature,
"signature-input":
req.headers["signature-input"],
"signature-agent":
req.headers["signature-agent"],
"content-type":
req.headers["content-type"],
"content-digest":
req.headers["content-digest"],
},
expectedTag: "web-bot-auth",
action: "api:call",
}),
}
);
const verification = await verifyResponse.json();
if (!verification.allowed) {
res.status(verification.verified ? 403 : 401).json({
ok: false,
error: "agent_access_denied",
verification,
});
return;
}
res.locals.agentVerification = verification;
next();
} catch (error) {
next(error);
}
}
```
## Protect MCP tools
Apply AgentBouncer decisions before executing MCP operations.
Set action to tools:call and tool to the MCP tool name. This makes verification events and custom policy rules specific to the operation being requested.
### MCP authorization pattern
```typescript
const verification = await agentBouncer.verify({
request,
expectedTag: "web-bot-auth",
action: "tools:call",
tool: toolName,
});
if (!verification.allowed) {
return {
isError: true,
content: [
{
type: "text",
text: "This agent is not allowed to call the tool.",
},
],
};
}
switch (toolName) {
case "weather":
return getWeather(toolArguments);
case "catalog":
return searchCatalog(toolArguments);
case "checkout":
return createCheckout(toolArguments);
default:
throw new Error("Unknown MCP tool");
}
```
| Operation | Suggested action | Suggested tool |
| --- | --- | --- |
| Read an MCP resource | resources:read | resource name |
| List tools | tools:list | * |
| Call a tool | tools:call | tool name |
| Read products | products:read | catalog |
| Create checkout | checkout:create | checkout |
| Submit an order | orders:create | orders |
## Project signing keys
Create identities for agents owned directly by your project.
1. **Issue a project key** — Open the project Agent keys tab and create a descriptive key such as Production MCP agent or Internal CRM.
2. **Save the private JWK** — The private key is shown once. Store it in the agent secret manager.
3. **Keep the public identity** — AgentBouncer stores the public JWK, key ID, signature-agent identity, scopes, and policy configuration.
4. **Sign requests** — Use the private JWK to produce Signature and Signature-Input headers for the exact target URL.
5. **Revoke compromised keys** — Delete or revoke a project key immediately if its private JWK is exposed.
### Example environment variables
```bash
AGENT_KEY_ID=YOUR_KEY_ID
AGENT_SIGNATURE_AGENT=https://agentbouncer.io/project-agents/PROJECT_ID/KEY_ID
AGENT_PRIVATE_JWK='{"kty":"OKP","crv":"Ed25519","x":"...","d":"..."}'
```
## Register an agent provider
Publish a provider identity and public-key directory.
1. **Create the provider** — Choose a stable slug, provider name, website, signature-agent origin, and directory URL.
2. **Publish the public JWKS** — Expose public keys at the configured HTTP Message Signatures directory.
3. **Refresh keys** — Ask AgentBouncer to load and validate the directory.
4. **Verify the provider domain** — Publish the AgentBouncer DNS TXT record or verification file.
5. **Sign production requests** — Use a private key corresponding to an active public key in the directory.
### Example JWKS document
```json
{
"keys": [
{
"kty": "OKP",
"crv": "Ed25519",
"kid": "provider-key-2026-01",
"alg": "EdDSA",
"use": "sig",
"x": "PUBLIC_KEY_MATERIAL"
}
]
}
```
### Recommended public location
```text
https://agent.example/.well-known/http-message-signatures-directory/jwks.json
```
> **Never publish the private JWK**
>
> Only public JWK properties belong in the provider directory. The private d property must remain secret.
## Policy modes
Choose how verified identity and trust data affect access.
| Mode | Behavior |
| --- | --- |
| MONITOR_ONLY | Records verification and policy failures but allows the request. |
| BLOCK_UNKNOWN | Blocks unsigned requests and unknown identities. |
| ALLOW_TIER_1_ONLY | Allows only the highest-trust provider tier. |
| ALLOW_TIER_1_AND_2 | Allows trusted Tier 1 and Tier 2 providers. |
| CUSTOM | Evaluates provider, tier, project-key, action, and tool rules. Matching DENY rules take precedence over matching ALLOW rules; otherwise defaultEffect is applied. |
> **Recommended rollout**
>
> Begin with MONITOR_ONLY, inspect real traffic, define actions and tools, test project keys, and only then enable blocking.
## Custom policies
Create fine-grained allow and deny rules.
### Example custom policy
```json
{
"version": 1,
"defaultEffect": "DENY",
"rules": [
{
"id": "rule_allow_tier_1_read",
"name": "Allow Tier 1 catalog reads",
"enabled": true,
"effect": "ALLOW",
"subject": {
"type": "PROVIDER_TIER",
"tiers": ["TIER_1"]
},
"actions": ["products:read"],
"tools": ["catalog"]
},
{
"id": "rule_deny_external_checkout",
"name": "Deny external checkout",
"enabled": true,
"effect": "DENY",
"subject": {
"type": "ANY_PROVIDER"
},
"actions": ["checkout:create"],
"tools": ["checkout"]
}
]
}
```
- DENY rules take precedence over ALLOW rules.
- An empty actions array matches any action.
- An empty tools array matches any tool.
- The wildcard * matches any action or tool.
- If no rule matches, defaultEffect is applied.
- Use PROJECT_KEY to target selected internal agent keys.
## Verification response
Understand the fields returned by the verification API.
```json
{
"verified": true,
"allowed": true,
"reason": null,
"checks": {
"signature": "valid",
"timestamp": "valid",
"replay": "unknown",
"provider": "known",
"policy": "matched"
},
"agent": {
"type": "PROJECT_KEY",
"provider": null,
"providerSlug": null,
"projectKey": {
"id": "key_id",
"name": "Production MCP agent",
"scopes": []
},
"signatureAgent": "https://agentbouncer.io/project-agents/...",
"keyid": "..."
},
"request": {
"method": "POST",
"path": "/api/mcp/weather",
"tag": "web-bot-auth",
"expectedTag": "web-bot-auth",
"action": "tools:call",
"tool": "weather"
},
"risk": {
"score": 5,
"level": "low"
},
"policy": {
"mode": "CUSTOM",
"wouldBlockReason": null,
"matchedRule": {
"id": "rule_allow_weather",
"name": "Allow weather agent",
"effect": "ALLOW"
},
"defaultEffectApplied": false
}
}
```
> **Replay detection**
>
> A valid signature does not by itself prove that the request has not been replayed. Until replay storage or nonce validation is enabled, checks.replay is reported as unknown.
| Field | Meaning |
| --- | --- |
| verified | Whether the cryptographic identity verification succeeded. |
| allowed | The final authorization decision after applying project policy. |
| reason | The effective denial reason, or null when access is allowed. |
| checks | Individual signature, timestamp, replay, provider, project-key, and policy results. unknown means that a check was not performed or sufficient information was not available. |
| agent | Resolved provider or project-key identity. |
| risk | Calculated risk score and level. |
| policy | Policy mode, matching rule, and hypothetical block reason. |
## Reasons and errors
Common verification and policy decision reasons.
| Reason | Description |
| --- | --- |
| unauthorized | Missing or invalid AgentBouncer project API key. |
| invalid_request | The verification payload is missing a required field. |
| invalid_url | url is not a valid absolute target URL. |
| no_signature | Signature or Signature-Input is missing. |
| no_keyid | Signature-Input does not contain keyid. |
| unknown_keyid | No active project or provider key was found. |
| not_yet_valid | The signature creation time is in the future. |
| expired | The signature has expired. |
| window_too_long | The created-to-expires signature window exceeds the permitted maximum. |
| bad_signature | Cryptographic verification failed. |
| unknown_provider | The key provider is not recognized. |
| provider_blocked | The provider is suspended, banned, fraudulent, or revoked. |
| provider_not_trusted | The project requires a trusted provider. |
| unsupported_tag | The signature intent tag is unsupported. |
| tag_mismatch | The actual signature tag does not match expectedTag. |
| tier_too_low | The provider does not satisfy the required tier. |
| trust_score_too_low | The provider trust score is below the project threshold. |
| project_keys_disabled | Project-owned signing keys are disabled. |
| custom_policy_denied | A matching custom DENY rule rejected the request. |
| custom_policy_no_match | No custom rule matched and the default effect is DENY. |
## Verification events
Monitor real traffic and investigate authorization decisions.
- Filter events by day, week, month, all time, or custom dates.
- Compare verified requests with allowed requests.
- Use allowed, not verified, when labeling an event as accepted or rejected.
- Inspect provider tier, status, trust score, abuse score, risk, action, and tool.
- Review wouldBlockReason for requests allowed by MONITOR_ONLY.
- Export event data as CSV for offline analysis.
| verified | allowed | Recommended UI label |
| --- | --- | --- |
| true | true | Allowed |
| true | false | Denied by policy |
| false | true | Allowed in monitor mode |
| false | false | Verification failed |
## Security guidance
Deploy AgentBouncer without leaking secrets or weakening verification.
- Keep AgentBouncer API keys on the server.
- Keep private signing JWKs in a secret manager.
- Publish only public JWK properties.
- Use HTTPS for target URLs, signature-agent identities, and key directories.
- Preserve the original externally visible URL and HTTP method.
- Do not forward Authorization, Cookie, Set-Cookie, x-api-key, or proxy credentials in verification payloads.
- Exclude reverse-proxy and observability headers unless they are intentionally signed.
- Use short signature validity windows.
- Revoke compromised project and provider keys.
- Start policy changes in MONITOR_ONLY before enforcing them.
> **Request body integrity**
>
> AgentBouncer verifies the HTTP Message Signature over the covered request components supplied to the verification API. Applications that rely on a signed Content-Digest must also ensure that the digest matches the body received by the protected endpoint.
## Troubleshooting
Diagnose signature, URL, key-directory, and policy problems.
| Problem | What to check |
| --- | --- |
| Invalid Signature header | Ensure Signature, Signature-Input, and Signature-Agent are each included exactly once. |
| bad_signature | Compare the signed URL, method, authority, covered components, public key, and content digest. |
| unknown_keyid | Confirm the project key is active or refresh the provider public-key directory. |
| tag_mismatch | Compare expectedTag with the tag in Signature-Input. |
| custom_policy_no_match | Check subject type, project key ID, provider tier, action, tool, and defaultEffect. |
| MONITOR_ONLY still blocks | Make sure your application checks only verification.allowed. |
| Wrong @authority | Use the external host that the agent signed, not an internal deployment host. |
### Correct authorization gate
```typescript
if (!verification.allowed) {
return Response.json(
{
ok: false,
error: "agent_access_denied",
verification,
},
{
status: verification.verified ? 403 : 401,
}
);
}
```
### Incorrect authorization gate
```typescript
// Incorrect: blocks MONITOR_ONLY requests.
if (!verification.verified || !verification.allowed) {
return new Response("Denied", {
status: 403,
});
}
```
## Production checklist
Review the integration before enabling blocking policies.
- The AgentBouncer API key is stored only on the server.
- The production project uses the correct public origin.
- The application sends the exact signed target URL.
- Signature headers are forwarded exactly once.
- Sensitive and infrastructure headers are excluded.
- The authorization gate checks allowed.
- Actions and MCP tool names are populated.
- MONITOR_ONLY traffic has been reviewed.
- Known providers and project keys resolve correctly.
- Custom policies have an intentional default effect.
- Private signing keys are stored in a secret manager.
- Key revocation and rotation procedures are documented.
- Verification event exports have been tested.