@agentrails/sdk
v0.0.1
Published
AgentRail SDK — detect AI agent traffic, gate it by purpose, and charge via HTTP 402 (AP2 / x402 / Bedrock Payments). Express middleware today; framework-agnostic adapters incoming.
Readme
@agentrails/sdk
Drop-in Express middleware that detects AI agent traffic, gates it by purpose, and charges per call over HTTP 402 — with pluggable validators for AP2, x402, AWS Bedrock Payments, and your own custom rails.
Install
npm install @agentrails/sdkQuick start
const express = require('express');
const {
agentrail,
makeAp2Validator,
makeX402Validator,
makeBedrockValidator,
} = require('@agentrails/sdk');
const app = express();
app.use(agentrail({
policies: {
'/api/products': { priceCents: 1, purposeAllow: ['browsing','answer'] },
'/api/products/:sku': { priceCents: 1, purposeAllow: ['browsing','answer'] },
'/api/reports/*': { priceCents: 25, purposeAllow: ['browsing','answer'] },
},
validators: {
ap2: makeAp2Validator({ merchantId: 'merchant_acme_inc' }),
x402: makeX402Validator({ recipientAddress: '0xYourTreasury...' }),
bedrock: makeBedrockValidator({ kmsKeyId: 'arn:aws:kms:...' }),
},
onEvent: (e) => analytics.record(e),
}));
app.get('/api/products', (req, res) => res.json({ products: [...] }));
app.listen(3000);A ChatGPT-User request to /api/products now gets:
HTTP/1.1 402 Payment Required
X-Price-Cents: 1
X-Payment-Methods: ap2,x402,bedrockAfter paying through the agent's preferred rail and resending with
X-Payment-Receipt: ap2:<base64-jws> (or x402:<tx-hash>:<sig>, or
bedrock:<base64-blob>), the request gets 200 OK. Human requests pass
through unchanged.
Multi-rail routing
The validators map dispatches by the receipt's prefix:
| Receipt prefix | Goes to |
|-------------------|--------------------------|
| demo:1:abc | makeDemoValidator() |
| ap2:<jws> | makeAp2Validator(...) |
| x402:<tx>:<sig> | makeX402Validator(...) |
| bedrock:<blob> | makeBedrockValidator(...) |
Mount only the rails you accept. An agent presenting a receipt for an
unmounted rail gets 402 with reason: unsupported_rail.
Every validator returns { ok, amountCents, rail } on success and
{ ok: false, reason } on failure (missing / invalid / replay / underpaid).
Validator factories
makeAp2Validator({
publicKeyUrl: 'https://ap2.google.com/.well-known/keys', // for JWS verification
merchantId: 'merchant_acme_inc', // your AP2 merchant ID
clockSkewSec: 60, // mandate expiry tolerance
})makeX402Validator({
facilitatorUrl: 'https://facilitator.coinbase.com/x402/verify',
recipientAddress: '0xYourTreasury...',
chain: 'base',
acceptedTokens: ['USDC'],
})makeBedrockValidator({
kmsKeyId: 'arn:aws:kms:us-east-1:.../demo-signing-key',
region: 'us-east-1',
})All three accept the same { req, requiredCents } and return the standard
result shape. Implementations are stubbed in the prototype; the production
verification work is documented inline in src/rails.js.
Bring your own validator
If you have a custom payment system, implement the same shape:
function myCustomValidator({ req, requiredCents }) {
const proof = req.get('x-payment-receipt');
// ... verify against your service ...
return { ok: true, amountCents: 1, rail: 'custom' };
}
app.use(agentrail({
policies: { ... },
validators: { custom: myCustomValidator },
}));What you get out of the box
- Detection — known-bot UA fingerprinting (OpenAI, Anthropic, Google,
Perplexity, Meta, ByteDance, CommonCrawl, …), self-declared
X-Agent-Identity/X-Agent-Purposeheaders, MCP protocol signals, and behavioural heuristics. Setsreq.agentrail = { isAgent, identity, signals, confidence }. - Control — per-resource policies with purpose gating
(allow
browsing/answer, denytraining, etc.). - Monetize — HTTP 402 with
X-Price-Cents/X-Payment-Methods. Multi-rail validator router supports AP2, x402, Bedrock, and custom rails side-by-side. - Analyze —
onEventfires on everyvisit/payment/blockedevent so you can pipe into your analytics (Postgres, Segment, etc.). Each payment event includes the rail used.
Configuration
| Option | Default | Description |
|---------------------|------------------------|-------------|
| policies | {} | Map of route patterns → policy. |
| validators | { demo: makeDemoValidator() } | Rail-prefix → validator function. |
| validatePayment | — | Legacy single-validator override. Use validators instead. |
| paymentMethods | derived from validators | Override of X-Payment-Methods header. |
| onEvent | () => {} | Callback for every analytics event. |
| treatPathAsAgent | () => false | Predicate: if true for req.path, any caller is treated as an agent. Use for explicit /api/agent/* surfaces. |
Route patterns
- Exact:
/api/data - Wildcard prefix:
/api/reports/* - Parameterized:
/api/products/:sku
Serving agents
Once the middleware is installed, your route handlers decide what to return to an agent vs a human. The SDK identifies the caller and gates payment, but never transforms your response body — the agent-facing JSON shape is your call. See docs/serving-agents.md for the canonical pattern, three good response shapes, anti-patterns, and a cheatsheet.
Verifying agent identity
Out of the box, the SDK upgrades a claimed identity (User-Agent,
X-Agent-Identity) into a verified verdict by checking the source IP
against the operator's published ranges and confirming PTR + forward-DNS.
The result lands on req.agentrail.verified:
| verified | Meaning |
|--------------------|----------------------------------------------------------------------------------------------------|
| 'rdns' | IP is in the operator's published range and PTR resolves to an operator-owned host and forward-DNS includes the source IP. Strongest tier without HTTP signatures. |
| 'ip-allowlist' | IP is in the operator's published range; PTR was missing or didn't match. Still useful — TCP source IPs are hard to spoof — but weaker. |
| null | Not in any range, or operator isn't known to the verifier. Identity is claimed only. |
Verification is fail-open: any network or DNS error returns null
rather than throwing. The request flow is never broken by the verifier.
Use it in policy:
app.get('/api/agent/articles/:slug', (req, res) => {
// Optional: charge unverified callers more than verified ones.
if (req.agentrail.verified === 'rdns') return serveLowTier(req, res);
return serveHighTier(req, res);
});The verifier supports four operators by default (OpenAI, Anthropic, Google, Perplexity). Override or inject:
const { agentrail, createVerifier } = require('@agentrails/sdk');
const verifier = createVerifier({
operatorConfig: { /* your override; see DEFAULT_OPERATORS export */ },
refreshIntervalMs: 6 * 60 * 60 * 1000, // 6h list refresh, 24h per-IP cache
fetchImpl: globalThis.fetch,
resolver: require('node:dns/promises'),
});
app.use(agentrail({ /* ... */ verifier }));
// or: agentrail({ verifier: false }) to disable verification entirely.The SDK also sets a X-Agent-Verified: rdns | ip-allowlist response
header alongside the existing X-Agent-Recognized, so agents can see
the merchant's verdict on their own identity.
Status
This is the prototype that ships with the AgentRail pitch site. The
detection, policy, and event layers are real. The four rail validators are
stubs that match production shape with replay protection and rail-prefixed
receipts; each carries an inline PRODUCTION comment block describing the
real cryptographic / on-chain verification work that replaces the stub.
