Express
How to verify AI agent tokens and authenticate incoming requests in Express.js using @agentonboard/sdk/express.
The @agentonboard/sdk/express subpath exports requireAgentAccount tailored for Express applications.
Because Express relies on explicit res object mutations rather than returning standard Response instances, the Express adapter accepts both req and res, and streams rejection headers and bodies directly when a check fails.
1. Install the SDK
npm install @agentonboard/sdk2. Guard your Express route
requireAgentAccount(req, res, options) handles verification and rejection writes automatically:
import express from "express";
import { requireAgentAccount } from "@agentonboard/sdk/express";
const app = express();
app.use(express.json());
// Simulated database lookup
const accounts = new Map([["ada@example.com", "user_123"]]);
app.post("/api/notes", async (req, res) => {
const session = await requireAgentAccount(req, res, {
audience: "notes.com",
resolveAccount: async ({ email }) => {
return accounts.get(email.toLowerCase()) ?? null;
},
});
// If verification failed or user was not found, the response status
// and body were already written to `res`. Simply return.
if (!session.ok) return;
// Use session.user directly in your database operations
res.json({ success: true, created_by: session.user });
});
app.listen(3000);Why Express requires both req and res
An Express route handler's return value is ignored by Express. If requireAgentAccount only returned an error object, handlers would need boilerplate checks and could easily return a 200 OK by mistake. Passing res guarantees that rejections are written reliably before the promise resolves.
3. Rejection codes and status mapping
When a check fails, the Express adapter writes the standard AgentOnboard failure body:
{
"code": "ACCOUNT_REQUIRED",
"error": "No account found for this email address"
}401 Unauthorized: Missing Bearer header, expired token, invalid signature, or wrong audience.403 Forbidden: Valid token, but email is not found in your database (ACCOUNT_REQUIRED).500 Internal Server Error:resolveAccountthrew an error.
4. Test with curl
# Mint a 5-minute test token
TOKEN=$(curl -s -X POST https://agentonboard.xyz/api/v1/token/exchange \
-H "Authorization: Bearer aon_live_your_key_here" \
-H "Content-Type: application/json" \
-d '{"audience": "notes.com"}' | jq -r .token)
# Test your Express endpoint
curl -i -H "Authorization: Bearer $TOKEN" http://localhost:3000/api/notes