The two identity headers
| Header | When to send | Why |
|---|---|---|
X-Wallet-Address: 0x… (EVM) or … (Solana base58) | When the agent is paying via a crypto wallet it controls on a rail that carries a wallet signature: Tempo MPP, x402 EIP-3009 on Base, or Solana MPP solana/charge. EVM addresses normalize lowercased; Solana base58 is preserved verbatim (case-sensitive). | The wallet is the identity. Zero credential lifecycle: no TTL, no rotation. The gate enforces that the payment signer resolves to the same operator as the claimed wallet. Cross-chain linked wallets are fungible: an EVM wallet and a Solana wallet captured under the same operator can both authenticate, and either can sign on behalf of the other. |
X-Operator-Token: opc_… | For any payment rail: Stripe SPT, card, Link, Tempo, x402, whichever | Token is reusable across AgentScore merchants until it expires. Required for SPT/card rails because those don’t carry a wallet signer. |
| Neither | First-ever encounter with AgentScore, agent has no stored identity | Gate returns 403 with verify_url, session_id, poll_secret. User completes KYC in browser, agent polls, receives a fresh operator_token, retries. |
Request pattern for every gated endpoint
This is the pattern to bake into every agent talking to AgentScore-gated APIs:First-encounter flow (session bootstrap)
If neither header works, the gate returns a 403 like this:- Present
verify_urlto the user verbatim. It’s a complete, ready-to-open URL with the session token embedded. Don’t construct it yourself; don’t modify it. - Begin polling
poll_urlevery 5 seconds withX-Poll-Secret: <poll_secret>. User is verifying in the browser while you poll. - When the poll returns
status: "verified", extractoperator_token. This is a one-time value; save it immediately; subsequent polls returnstatus: "consumed"without it. - Retry the original request with
X-Operator-Token: <operator_token>.
Handling specific denial codes
Every denial from the gate carries a typederror.code and usually a next_steps.action. Map each to a distinct remediation:
error.code | What it means | What the agent should do |
|---|---|---|
identity_verification_required | No identity header on request; the merchant auto-created a session (body includes verify_url + session_id + poll_secret + poll_url + agent_instructions) | Run the session flow (above) |
missing_identity | No identity header AND no auto-session; body includes agent_instructions.action: "probe_identity_then_session" + agent_memory | Follow the probe strategy in agent_instructions: try a wallet on signing rails → stored opc_... → retry with no header (auto-session merchants will then return a full session body) |
compliance_denied | Merchant-emitted (not gate-emitted): identity verified but policy failed with an UNFIXABLE reason (sanctions_flagged, age_insufficient, jurisdiction_restricted). Fixable reasons (kyc_required, kyc_pending, kyc_failed) get re-routed by the gate to identity_verification_required upstream and never reach this code in practice. | Surface next_steps.action: contact_support to the user: re-verification won’t change the outcome |
wallet_not_trusted | Wallet linked to an operator but failed UNFIXABLE compliance (sanctions_flagged, age_insufficient, jurisdiction_restricted). Body includes reasons[] + agent_instructions.action: "contact_support". 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. jurisdiction_restricted is unfixable: the API only emits it after KYC is verified (the user’s KYC’d country is in the blocked list: re-doing KYC won’t change the country). | Surface contact_support to the user: re-verification won’t change the outcome |
kyc_required / kyc_pending / kyc_failed | Operator exists but KYC isn’t complete | Re-run verify flow (fixable denial) |
jurisdiction_restricted | Operator’s KYC’d country is in the merchant’s blocked list (or absent from the allowed list). Only emitted after KYC is verified. | Unfixable: surface contact_support to the user. Re-doing KYC won’t change the country. |
sanctions_flagged / age_insufficient | Compliance policy failure | Stop. These are unfixable: surface contact_support action to the user |
token_expired | Your operator_token is no longer valid (covers both TTL-expired AND revoked: the API deliberately doesn’t disclose which, to avoid leaking the user’s revoke intent) | The 403/401 body carries an auto-minted session (verify_url + session_id + poll_secret): share verify_url with the user, poll until verified, receive a fresh opc_.... Account KYC persists so the user just confirms. |
wallet_signer_mismatch | Sent X-Wallet-Address but paid from a different wallet not linked to the same operator | Regenerate the payment from the claimed wallet (or any wallet in linked_wallets on the denial body) |
wallet_auth_requires_wallet_signing | Sent X-Wallet-Address but your rail (SPT/card) has no wallet signature | Switch to X-Operator-Token |
payment_required | Merchant’s AgentScore tier doesn’t support /v1/assess. Body includes agent_instructions.action: "contact_merchant". | Not actionable agent-side: surface the merchant’s support channel to the user |
api_error / 503 | AgentScore API failure. Body’s agent_instructions.action discriminates: retry_with_backoff for transient 5xx / network timeout, or contact_merchant for a 429: merchant-side issue, don’t retry, surface to user. | Read agent_instructions.action and act accordingly. Distinct from wallet_not_trusted: don’t conflate |
Multi-wallet flows
Your agent has two wallets; maybe a Tempo wallet for L2 payments and a Base wallet for x402. You don’t need a separate identity per wallet.Memory contract
On bootstrap denials (identity_verification_required, missing_identity) and on the POST /v1/sessions success body, every AgentScore response includes an agent_memory block:
What to do with it
LLM-hosted agents (Claude, GPT, Cursor, etc.) with persistent context: savepattern_summary, identity_paths, and identity_check_endpoint to long-term memory on first encounter. On every subsequent AgentScore-gated request; any merchant; recognize the denial pattern and skip straight to the right identity header. You won’t need to hit a session flow again.
Scripted agents that don’t have an LLM memory layer: ignore the field. The per-request agent_instructions block has everything you need to complete the current flow. agent_memory is purely optimization for stateful agents.
What NOT to save to memory
Thedo_not_persist_in_memory array names bearer credentials. Respect it. operator_token and poll_secret are secrets; LLM memory is often leak-prone (context logs, quoted tool outputs, etc.). Put tokens in a credential store (OS keychain, Vercel KV, HashiCorp Vault; whatever your host provides). Tokens are designed with 24h default TTLs so an accidentally-leaked one self-expires quickly.
URL safety
identity_check_endpoint is hardcoded to https://api.agentscore.sh in the memory payload; not derived from the merchant’s gate configuration. A malicious merchant can’t set a custom baseUrl to phish your agent into sending credentials to their server. Safe to trust cross-merchant.
The tempo request flow (worked example)
For agents using Tempo USDC via the Tempo CLI; Martin Estate’s canonical path:
Key endpoints
| Endpoint | What it’s for |
|---|---|
POST /v1/sessions | Create a verification session when you have no identity. Returns verify_url + poll_secret |
GET /v1/sessions/:token (X-Poll-Secret required) | Poll for the one-time operator_token after the user finishes KYC |
POST /v1/credentials | Mint a fresh operator_token (use after token_expired, or on first-time setup from an authenticated account) |
GET /v1/credentials | List your active tokens and their TTLs |
POST /v1/credentials/wallets | Merchants call this fire-and-forget after a successful wallet-signed payment to capture the signer wallet under the paying operator_token. Agents don’t call it directly. |
POST /v1/assess | Merchants call this. Agents usually don’t. |
Next steps
- Agent commerce quickstart; merchant-side integration pattern
- Compliance gating reference; policy fields + denial code details
- API reference: /v1/sessions; session creation + poll semantics
- API reference: credentials; credential lifecycle endpoints
- AgentScore Pay MCP server; if your agent is MCP-hosted, register
agentscore-pay --mcpand use thecredentials_create/credentials_list/assesstools instead of raw HTTP