# 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.