How it works
- An agent requests a purchase or action from your service
- Your service calls AgentScore to verify the operator’s identity
- AgentScore returns
allowordenybased on your compliance policy - If denied, the operator can self-serve verify via a URL you provide
Define your compliance policy
A policy specifies what identity checks the operator must pass:| Field | Type | Description |
|---|---|---|
require_kyc | boolean | Operator must have completed identity verification |
require_sanctions_clear | boolean | Operator must pass NAME-based sanctions screening on their KYC identity (separate from wallet-address OFAC screening, which fires automatically when a signer is in the request — see POST /v1/assess for the signer_sanctions block) |
min_age | number | Minimum age (18 or 21) |
blocked_jurisdictions | string[] | ISO country codes to block |
allowed_jurisdictions | string[] | ISO country codes to allow (denies all others) |
Check identity at transaction time
When an agent sends a request to your service, extract the identity from the headers and callPOST /v1/assess:
Per-product policy + soft mode (commerce SDK)
The single-gate setup above attaches one policy to a route. Multi-product merchants where each item has different compliance needs (regulated wine ⨉ free-to-everyone merch ⨉ high-value print that wants KYC as a fraud signal but won’t block the sale) want the policy per product, not per route. Bothagentscore-commerce (Python) and @agent-score/commerce (Node) ship
parallel helpers for this. Field names follow each language’s convention
(snake_case in Python, camelCase in Node) but the shape is identical.
Python (agentscore_commerce.identity.policy):
PolicyBlock: typed shape carryingenforcement(hard|soft| absent),require_kyc,require_sanctions_clear,min_age,allowed_jurisdictions,allowed_shipping_countries,allowed_shipping_states. Vendors usually source these from a database row (one column per field).build_gate_from_policy(policy, *, api_key); translates a block into anAgentScoreGate. ReturnsNonewhen the block has noenforcementset, signalling “no gate; identity_status=‘anonymous’”.run_gate_with_enforcement(request, gate, *, enforcement); runs the gate. On hard denial it returnsstatus="denied"with adenial_statusanddenial_bodyfor the caller to propagate. On soft denial it swallows the 403 and returnsstatus="unverified"so the order completes with a degraded identity stamp. On success:status="verified". No gate:status="anonymous".
@agent-score/commerce/identity/policy):
PolicyBlock: same fields in camelCase (enforcement,requireKyc,requireSanctionsClear,minAge,allowedJurisdictions,allowedShippingCountries,allowedShippingStates).buildGateFromPolicy(policy, { apiKey }): translates a block into the options object the per-frameworkagentscoreGate(...)accepts (the Node SDK builds gates per framework; Hono, Express, Fastify, Next.js, Web; so the policy module emits options rather than a constructed gate, mirroring python’sbuild_gate_from_policyverb pattern). Returnsnullwhen noenforcement.runGateWithEnforcement(enforcement, runGate): wraps the per-framework middleware in the hard/soft enforcement runner. The vendor passes arunGateadapter that resolves to{ ok: true }on accept or{ ok: false, status, body }on deny; the runner returns a structuredGateResult.
shipping{Country,State}Allowed(...): per-product shipping allowlists. Country list is hard-enforced regardless of identity strictness; state list only fires for US shipments (e.g. wine).
enforcement | Identity required? | On gate denial | Use case |
|---|---|---|---|
hard | yes | propagate the gate’s 403 (today’s regulated path) | wine, cannabis, anything regulated |
soft | offered, not required | swallow + stamp identity_status="unverified" on order | request KYC for fraud signal but accept anonymous sales |
absent / null | no | gate never fires; identity_status="anonymous" | unregulated merch, ship anywhere |
identity_status on each order row (verified | unverified | anonymous) so ops/analytics can distinguish soft passes from hard passes from anonymous sales.
A typical multi-product setup mixes enforcement modes; e.g. a regulated wine SKU with enforcement="hard" (KYC + 21 + US-only state allowlist) alongside unregulated merch (tee, sticker pack) with no policy. Single-file runnable examples: per_product_policy_merchant.py (Python) and per-product-policy-merchant.ts (Node).
Handle unverified operators
When an operator isn’t verified, the assess response includes averify_url. Return it to the agent so the operator can self-serve:
Session-based verification (recommended)
For a smoother flow, create a verification session before returning the deny. This lets the agent poll for the result instead of requiring the operator to copy-paste credentials:operator_token. It retries the request; this time assess returns allow. The user closes the AgentScore tab; the agent finishes the transaction in the background.
What’s checked
| Check | What it verifies |
|---|---|
require_kyc | Government-issued photo ID via Stripe Identity |
require_sanctions_clear | Operator name not on OFAC/sanctions lists (OpenSanctions @ KYC), AND payment-signer wallet address not on the OFAC SDN crypto list (refreshed hourly from sdn_advanced.xml). A hit on either axis flips decision to deny. |
min_age | Age bracket derived from ID (18+ or 21+) |
blocked_jurisdictions | Country from ID is not in blocked list |
allowed_jurisdictions | Country from ID is in allowed list |
AgentScore Gate denial codes
When AgentScore Gate rejects a request before hitting/v1/assess, the 403 body uses one of the codes below. Every code carries a structured agent_instructions payload (JSON-encoded {action, steps, user_message}) so agents can recover deterministically from the response alone; no discovery-doc round trip required.
| Code | When | agent_instructions.action + key fields |
|---|---|---|
missing_identity | Neither X-Wallet-Address nor X-Operator-Token present, and no auto-session was created | probe_identity_then_session: try wallet on signing rails → stored opc_... → session flow; agent_memory cross-merchant bootstrap hint included |
identity_verification_required | No identity header, but the merchant’s gate auto-minted a verification session: body includes verify_url, session_id, poll_secret, poll_url | deliver_verify_url_and_poll: share verify_url with the user, poll poll_url with X-Poll-Secret until status=verified, retry with the returned opc_... |
token_expired | X-Operator-Token was valid but is no longer (covers both TTL-expired and explicitly revoked: the API deliberately doesn’t disclose which). 401 body carries an auto-minted session so the agent recovers without an API key. | deliver_verify_url_and_poll: same flow as identity_verification_required; poll returns a fresh opc_... |
invalid_credential | X-Operator-Token doesn’t match any credential: typo, fabricated, or never minted. Permanent: retrying the same token will keep failing, no auto-session issued. | switch_token_or_restart_session: try a different stored opc_..., or drop the header and re-bootstrap via the merchant’s createSessionOnMissing flow |
wallet_signer_mismatch | X-Wallet-Address was sent but the payment signer resolves to a different operator | resign_or_switch_to_operator_token; body also carries claimed_operator, actual_signer_operator, expected_signer, actual_signer, linked_wallets: re-sign from expected_signer (or any linked_wallets entry), or switch to X-Operator-Token |
wallet_auth_requires_wallet_signing | X-Wallet-Address was sent with a rail that has no wallet signature (Stripe SPT, card) | switch_to_operator_token: drop X-Wallet-Address, retry with X-Operator-Token, or use a wallet-signing rail (Tempo MPP, x402 EIP-3009 on Base, or x402 SPL Token on Solana) |
wallet_not_trusted | /v1/assess returned deny with an UNFIXABLE policy reason (sanctions_flagged / age_insufficient / jurisdiction_restricted). Body includes reasons[]. | contact_support: re-verification won’t fix this; surface the merchant’s support contact. Fixable reasons (kyc_required, kyc_pending, kyc_failed) never reach this code: the gate auto-mints a verification session and re-routes to identity_verification_required with poll fields, identical UX to missing_identity. Note: jurisdiction_restricted is unfixable because the API only emits it after KYC is verified (the user’s KYC’d country is in the merchant’s blocked list: re-doing KYC won’t change the country). |
payment_required | The merchant’s AgentScore plan doesn’t include /v1/assess. Merchant-side misconfig: not recoverable agent-side. | contact_merchant: surface to the merchant via their support channel; no agent-side fix available |
api_error | AgentScore API failure. Body’s agent_instructions.action discriminates: retry_with_backoff for transient 5xx / network timeout, contact_merchant for a 429 (merchant-side issue). | Read agent_instructions.action. For retry_with_backoff: exponential backoff over 5–30s, surface to user after ~5min sustained. For contact_merchant: DO NOT retry; surface the merchant’s support contact to the user. Identity headers remain valid in both cases; this is NOT a compliance denial. See errors › Retryable infra errors |
Wallet-mode response fields
Whenidentity_mode: "wallet", 402 and 403 bodies also include these fields so agents know exactly which address must sign:
| Field | Type | Description |
|---|---|---|
identity_mode | "wallet" | "operator_token" | Which identity path was used |
required_signer | string | Wallet address normalized per network: EVM lowercased, Solana base58 preserved verbatim. The payment MUST be signed by this address or a linked_wallets entry. |
linked_wallets | string[] | Same-operator sibling wallets, normalized per network. May mix EVM (0x...) and Solana (base58) for multi-chain operators; any may sign in place of required_signer. |
signer_constraint | string | Human-readable explanation of the same-operator rule |
Fail-open behavior (opt-in)
By default AgentScore Gate fails closed: any AgentScore-side infrastructure failure (HTTP 429, 5xx, network timeout) returns 503 to the buyer. This is the correct posture for regulated commerce; better to outage than to ship to a sanctioned wallet because our API blipped. Some merchants (low-stakes commerce, high-uptime SLAs) prefer graceful degradation. SetfailOpen: true (Node) / fail_open=True (Python) to opt in. When opted in AND the failure is infra-shape, the buyer passes through and the gate state carries a degraded flag merchants can log/alert on:
infraReason / infra_reason is one of:
| Value | When |
|---|---|
quota_exceeded | AgentScore returned 429 |
api_error | AgentScore returned 5xx (transient infra issue) |
network_timeout | Request to /v1/assess timed out or failed at the network layer |
failOpen does NOT bypass compliance denials. sanctions_flagged, age_insufficient, jurisdiction_restricted, wallet_signer_mismatch, kyc_required and other real policy outcomes still return 403 regardless of the flag. failOpen only covers “we couldn’t reach AgentScore to ask,” never “AgentScore said no but we’ll allow anyway.”
For Web Fetch / Next.js (createAgentScoreGate / withAgentScoreGate), the degraded + infraReason fields land directly on the GuardResult.allowed variant / handler’s gate parameter; no separate getter needed.
Recommendations by vertical
| Vertical | Recommended posture |
|---|---|
| Regulated commerce (alcohol, cannabis, age-gated, sanctioned-jurisdiction) | failOpen: false (default): better to outage than to bypass compliance |
| Low-stakes commerce (cheap API calls, content access) | failOpen: true is reasonable: accept the trade for uptime |
| Enterprise customers with tight SLAs | failOpen: true with degraded logging as the audit trail |
What sellers see
Sellers receive binary decisions;allow or deny. You never see the operator’s name, address, date of birth, or ID documents. The only data exposed:
- Verification level (none / claimed / verified)
- Whether each policy check passed or failed
Privacy
- AgentScore does not store ID documents; they are processed by Stripe Identity and never leave Stripe
- We store derived facts only: verification status, jurisdiction (country code), age bracket, sanctions status
- If our database is breached, attackers see “operator X is verified, US, individual”; no identity data
Sandbox testing
Usetest: true with reserved test addresses to simulate compliance scenarios:
Pricing
See pricing for plans, quotas, and which tier includes compliance gating.Next steps
POST /v1/assess
Full assess endpoint reference.
Sessions
Create verification sessions for agent polling.
Credentials
Create and manage operator credentials.
AgentScore Passport
How operators verify their identity.