Agent^Rider

Integrating the gate

There are two credentials in play: your merchant key (proves your subscription is active, used to mint riders) and a rider (a signed JWT you hand to an agent, and that any gate can verify locally — no call back to us required).

1. Subscribe and get a merchant key

Start a Merchant Gate subscription from the pricing section. After checkout, the success page calls GET /api/provision?session_id=... and shows your merchant key once (format: merchant_live_...). Store it somewhere safe — it isn't shown again, and it's what authorizes rider issuance below.

2. Issue a rider for an agent

Mint a signed rider for an agent you've cleared — issuance itself is free. Requires your merchant key in the X-Merchant-Key header, gated on your subscription being active; local rider verification (next section) needs no key at all.

POST /api/rider/issue
X-Merchant-Key: merchant_live_...
Content-Type: application/json

{
  "agent_id": "a7f2-rider-9c14",
  "operator_id": "network.acme-fleet",
  "level": "L2",
  "scopes": ["read:catalog", "purchase:<100"]
}

Response:

{
  "rider": "eyJhbGciOiJFUzI1NiIs...",
  "jti": "3f9c...",
  "expires_in": 900,
  "header_to_send": "X-Agent-Rider"
}

Riders expire in 15 minutes by default. Hand the rider token to the agent; it presents it as X-Agent-Rider at every gate it crosses.

3. Verify a rider at your gate

Two ways to do this. Only the first one is actually free of a round trip to us — use it if you're gating real traffic.

Option A — local verification (recommended)

Fetch our public key once from a standard JWKS endpoint, cache it, and verify the ES256 signature yourself. No call to us per request:

import { jwtVerify, createRemoteJWKSet } from "jose";

const JWKS = createRemoteJWKSet(
  new URL("https://agentrider.vercel.app/.well-known/jwks.json")
);

const { payload: rider } = await jwtVerify(riderToken, JWKS, {
  issuer: "agentrider.dev",
});
// rider.agent_id, rider.operator_id, rider.level, rider.scopes, rider.jti

jwtVerify throws on an expired, tampered, or malformed token — createRemoteJWKSet handles fetching and caching the key set for you, in any JWT library that supports JWKS, not just jose.

Option B — our verify endpoint

Same signature check, run on our server instead of yours — simpler to call, but it's one HTTP request to us per check, not local:

POST /api/rider/verify
Content-Type: application/json

{
  "rider": "eyJhbGciOiJFUzI1NiIs..."
}

Response:

{
  "valid": true,
  "rider": {
    "agent_id": "a7f2-rider-9c14",
    "operator_id": "network.acme-fleet",
    "level": "L2",
    "scopes": ["read:catalog", "purchase:<100"],
    "jti": "3f9c..."
  }
}

An expired, tampered, or malformed rider returns {"valid": false, "reason": "..."}. You can also send the token as an X-Agent-Rider header instead of a body field.

Clearance levels

Riders carry a level, L0L4, low to high stakes. Check it against the minimum your endpoint requires:

Pair a level with a scope check (e.g. purchase:*) for finer-grained gates. See /api/demo/catalog, /api/demo/checkout, and /api/demo/account-action for worked examples at L1, L2, and L3.

Letting agents discover a rider is required

An agent shouldn't need out-of-band knowledge to know it needs a rider — the 401 it gets back should tell it. When our own gated demo routes (/api/demo/*) reject a request for a missing or invalid rider, the response is self-describing, the same way OAuth's WWW-Authenticate: Bearer challenge works:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Rider realm="agentrider.dev",
  issue_uri="https://agentrider.vercel.app/api/rider/issue",
  docs_uri="https://agentrider.vercel.app/docs"
Content-Type: application/json

{
  "error": "missing_rider",
  "issue_url": "https://agentrider.vercel.app/api/rider/issue",
  "docs_url": "https://agentrider.vercel.app/docs"
}

Both the header (for clients that parse challenge headers) and the body (for anything that just reads JSON) carry the same information. If you're building your own gate on top of checkGate-equivalent logic, return the same shape — it's what turns "access denied" into "here's how to fix that," without the agent's operator needing to have read these docs in advance.

Check your merchant key's status

Separate from rider verification — this checks whether a merchant key itself is currently backed by an active subscription (useful for your own dashboard, not for gating agent requests). Rider issuance is free; this is what's metered — the first VERIFY_FREE_CALLS_PER_MONTH (69 by default) calls each calendar month are included in Merchant Gate, calls above that keep succeeding but report as billable overage:

curl -X POST https://agentrider.vercel.app/api/verify \
  -H "Content-Type: application/json" \
  -d '{"merchantKey": "merchant_live_..."}'
# => {
#   "valid": true,
#   "status": "active",
#   "usage": { "callsThisMonth": 41, "freeLimit": 69, "overage": false }
# }

Errors and edge cases