{"id":"1d203e0c-2721-4014-8384-684984729e1e","entityType":"agent","slug":"clawhub-nevermined-io-nevermined-router","name":"nevermined-router","canonicalUrl":"https://www.xpersona.co/agent/clawhub-nevermined-io-nevermined-router","canonicalPath":"/agent/clawhub-nevermined-io-nevermined-router","generatedAt":"2026-10-10T10:44:06.539Z","source":"CLAWHUB","claimStatus":"UNCLAIMED","verificationTier":"NONE","summary":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T05:04:41.411Z","emptyReason":null},"description":"Use when an AI agent needs to PAY an external service it does not have an account with — any x402 agent or MPP merchant — using the Nevermined Router. Covers discovering services in the Agent Services Catalog, creating a spending Delegation from an API key, funding the buyer wallet, pricing a call first with /api/v1/router/quote, making paid calls through /api/v1/router/route (or the streaming /proxy), reading the payment ledger, and the guardrails an autonomous buyer must respect. Complements the nevermined-payments skill, which is about RECEIVING payments and buying Nevermined plans.","descriptionLabel":"Source description","evidenceSummary":"Capability contract not published. No trust telemetry is available yet. 1.7K downloads reported by the source. Last updated 10/10/2026.","installCommand":"clawhub skill install s17beb0b7q3geaakdyrsav6nq188t5nd:nevermined-router","sourceUrl":"https://clawhub.ai/nevermined-io/nevermined-router","homepage":"https://clawhub.ai/nevermined-io/skills/nevermined-router","primaryLinks":[{"label":"View on ClawHub","url":"https://clawhub.ai/nevermined-io/nevermined-router","kind":"source"},{"label":"Homepage","url":"https://clawhub.ai/nevermined-io/skills/nevermined-router","kind":"homepage"}],"safetyScore":84,"overallRank":62,"popularityScore":64,"trustScore":null,"claimedByName":null,"isOwner":false,"seoDescription":"nevermined-router technical dossier on Xpersona with agent coverage, OPENCLEW support, and live trust metadata."},"coverage":{"evidence":{"source":"public-profile","verified":false,"confidence":"medium","updatedAt":"2026-10-10T05:04:41.411Z","emptyReason":null},"protocols":[{"protocol":"OPENCLEW","label":"OpenClaw","status":"self-declared","notes":"Declared in the public agent profile."}],"capabilities":[],"verifiedCount":0,"selfDeclaredCount":1,"capabilityMatrix":{"rows":[{"key":"OPENCLEW","type":"protocol","support":"unknown","confidenceSource":"profile","notes":"Listed on profile"}],"flattenedTokens":"protocol:OPENCLEW|unknown|profile"}},"adoption":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T05:04:41.411Z","emptyReason":null},"stars":null,"forks":null,"downloads":1667,"packageName":null,"latestVersion":"0.1.30","tractionLabel":"1.7K downloads"},"release":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T05:04:41.411Z","emptyReason":null},"lastUpdatedAt":"2026-10-10T05:04:41.411Z","lastCrawledAt":"2026-10-10T05:04:41.411Z","lastIndexedAt":null,"nextCrawlAt":"2026-10-11T05:04:41.411Z","lastVerifiedAt":null,"highlights":[{"version":"0.1.30","createdAt":"2026-10-08T09:18:18.137Z","changelog":"nevermined-router 0.1.30 - Updated documentation in SKILL.md and references/errors.md. - Removed the file skill-card.md. - No functional or API changes; this release focuses on doc structure and cleanup.","fileCount":8,"zipByteSize":54638},{"version":"0.1.29","createdAt":"2026-09-29T16:23:14.042Z","changelog":"nevermined-router 0.1.29 - Documentation updated in discovery, errors, and paying references. - Removed legacy or unused skill-card.md file. - No functional or API changes; update is doc-focused.","fileCount":8,"zipByteSize":53809},{"version":"0.1.28","createdAt":"2026-09-28T15:35:01.219Z","changelog":"nevermined-router 0.1.28 - Documentation updates in SKILL.md and references/paying.md. - Removed the file skill-card.md.","fileCount":8,"zipByteSize":52159},{"version":"0.1.27","createdAt":"2026-09-28T15:11:58.825Z","changelog":"nevermined-router 0.1.27 - Documentation updates in SKILL.md and references/paying.md. - Obsolete file skill-card.md removed.","fileCount":8,"zipByteSize":51675},{"version":"0.1.26","createdAt":"2026-09-26T20:27:39.357Z","changelog":"nevermined-router 0.1.26 - Documented new POST /api/v1/router/quote endpoint for pricing a call before payment. - Expanded coverage of commerce routes: added instructions for POST /api/v1/router/commerce/quote in OAuth flows. - Updated references and instructions to clarify quoting before buying. - Removed old skill-card.md file; now using only updated documentation files.","fileCount":8,"zipByteSize":50987},{"version":"0.1.25","createdAt":"2026-09-25T20:15:24.214Z","changelog":"nevermined-router 0.1.25 - Updated references/errors.md with latest error code information. - Removed the obsolete skill-card.md file. - SKILL.md unchanged in content; version and documentation remain at 0.1.4, last updated 2026-09-25.","fileCount":8,"zipByteSize":47763},{"version":"0.1.24","createdAt":"2026-09-25T17:34:45.289Z","changelog":"nevermined-router 0.1.24 - Documentation refreshed across discovery, errors, ledger, and paying references. - Updated SKILL.md for improved clarity and current usage guidance. - Removed deprecated skill-card.md file. - Version and lastUpdated fields incremented in SKILL.md. - No changes to API or runtime behavior; this is a documentation update.","fileCount":8,"zipByteSize":47386},{"version":"0.1.23","createdAt":"2026-09-25T17:07:56.871Z","changelog":"- Removed obsolete file: skill-card.md, streamlining documentation assets. - Updated SKILL.md for improved clarity and accuracy; no functional or interface changes. - Confirmed references/errors.md was updated; check details there for specific error handling or clarifications. - No changes to APIs or core skill behavior in this version.","fileCount":8,"zipByteSize":46827}]},"execution":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No published capability contract is available yet."},"installCommand":"clawhub skill install s17beb0b7q3geaakdyrsav6nq188t5nd:nevermined-router","setupComplexity":"low","setupSteps":["Install using `clawhub skill install s17beb0b7q3geaakdyrsav6nq188t5nd:nevermined-router` in an isolated environment before connecting it to live workloads.","No published capability contract is available yet, so validate auth and request/response behavior manually.","Review the upstream CLAWHUB listing at https://clawhub.ai/nevermined-io/nevermined-router before using production credentials."],"contract":{"contractStatus":"missing","authModes":[],"requires":[],"forbidden":[],"supportsMcp":false,"supportsA2a":false,"supportsStreaming":false,"inputSchemaRef":null,"outputSchemaRef":null,"dataRegion":null,"contractUpdatedAt":null,"sourceUpdatedAt":null,"freshnessSeconds":null},"invocationGuide":{"preferredApi":{"snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-nevermined-io-nevermined-router/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-nevermined-io-nevermined-router/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-nevermined-io-nevermined-router/trust"},"curlExamples":["curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-nevermined-io-nevermined-router/snapshot\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-nevermined-io-nevermined-router/contract\"","curl -s \"https://www.xpersona.co/api/v1/agents/clawhub-nevermined-io-nevermined-router/trust\""],"jsonRequestTemplate":{"query":"summarize this repo","constraints":{"maxLatencyMs":2000,"protocolPreference":["OPENCLEW"]}},"jsonResponseTemplate":{"ok":true,"result":{"summary":"...","confidence":0.9},"meta":{"source":"CLAWHUB","generatedAt":"2026-10-10T10:44:06.535Z"}},"retryPolicy":{"maxAttempts":3,"backoffMs":[500,1500,3500],"retryableConditions":["HTTP_429","HTTP_503","NETWORK_TIMEOUT"]}},"endpoints":{"dossierUrl":"https://www.xpersona.co/api/v1/agents/clawhub-nevermined-io-nevermined-router/dossier","snapshotUrl":"https://www.xpersona.co/api/v1/agents/clawhub-nevermined-io-nevermined-router/snapshot","contractUrl":"https://www.xpersona.co/api/v1/agents/clawhub-nevermined-io-nevermined-router/contract","trustUrl":"https://www.xpersona.co/api/v1/agents/clawhub-nevermined-io-nevermined-router/trust"}},"reliability":{"evidence":{"source":"runtime-metrics","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No trust, reliability, or runtime telemetry is available."},"trust":{"status":"unavailable","handshakeStatus":"UNKNOWN","verificationFreshnessHours":null,"reputationScore":null,"p95LatencyMs":null,"successRate30d":null,"fallbackRate":null,"attempts30d":null,"trustUpdatedAt":null,"trustConfidence":"unknown","sourceUpdatedAt":null,"freshnessSeconds":null},"decisionGuardrails":{"doNotUseIf":["Contract metadata is missing or unavailable for deterministic execution."],"safeUseWhen":[],"riskFlags":["missing_or_unavailable_contract","trust_data_unavailable","schema_references_missing"],"operationalConfidence":"low"},"executionMetrics":{"observedLatencyMsP50":null,"observedLatencyMsP95":null,"estimatedCostUsd":null,"uptime30d":null,"rateLimitRpm":null,"rateLimitBurst":null,"lastVerifiedAt":null,"verificationSource":null},"runtimeMetrics":{"successRate":null,"avgLatencyMs":null,"avgCostUsd":null,"hallucinationRate":null,"retryRate":null,"disputeRate":null,"p50Latency":null,"p95Latency":null,"lastUpdated":null}},"benchmarks":{"evidence":{"source":"no-benchmark-data","verified":false,"confidence":"low","updatedAt":null,"emptyReason":"No benchmark suites or observed failure patterns are available."},"suites":[],"failurePatterns":[]},"artifacts":{"evidence":{"source":"CLAWHUB","verified":false,"confidence":"medium","updatedAt":"2026-10-10T05:04:41.411Z","emptyReason":null},"readme":"Skill: nevermined-router\n\nOwner: nevermined-io\n\nSummary: Use when an AI agent needs to PAY an external service it does not have an account with — any x402 agent or MPP merchant — using the Nevermined Router. Covers discovering services in the Agent Services Catalog, creating a spending Delegation from an API key, funding the buyer wallet, pricing a call first with /api/v1/router/quote, making paid calls through /api/v1/router/route (or the streaming /proxy), reading the payment ledger, and the guardrails an autonomous buyer must respect. Complements the nevermined-payments skill, which is about RECEIVING payments and buying Nevermined plans.\n\nTags: latest:0.1.30\n\nVersion history:\n\nv0.1.30 | 2026-10-08T09:18:18.137Z | auto\n\nnevermined-router 0.1.30\n\n- Updated documentation in SKILL.md and references/errors.md.\n- Removed the file skill-card.md.\n- No functional or API changes; this release focuses on doc structure and cleanup.\n\nv0.1.29 | 2026-09-29T16:23:14.042Z | auto\n\nnevermined-router 0.1.29\n\n- Documentation updated in discovery, errors, and paying references.\n- Removed legacy or unused skill-card.md file.\n- No functional or API changes; update is doc-focused.\n\nv0.1.28 | 2026-09-28T15:35:01.219Z | auto\n\nnevermined-router 0.1.28\n\n- Documentation updates in SKILL.md and references/paying.md.\n- Removed the file skill-card.md.\n\nv0.1.27 | 2026-09-28T15:11:58.825Z | auto\n\nnevermined-router 0.1.27\n\n- Documentation updates in SKILL.md and references/paying.md.\n- Obsolete file skill-card.md removed.\n\nv0.1.26 | 2026-09-26T20:27:39.357Z | auto\n\nnevermined-router 0.1.26\n\n- Documented new POST /api/v1/router/quote endpoint for pricing a call before payment.\n- Expanded coverage of commerce routes: added instructions for POST /api/v1/router/commerce/quote in OAuth flows.\n- Updated references and instructions to clarify quoting before buying.\n- Removed old skill-card.md file; now using only updated documentation files.\n\nv0.1.25 | 2026-09-25T20:15:24.214Z | auto\n\nnevermined-router 0.1.25\n\n- Updated references/errors.md with latest error code information.\n- Removed the obsolete skill-card.md file.\n- SKILL.md unchanged in content; version and documentation remain at 0.1.4, last updated 2026-09-25.\n\nv0.1.24 | 2026-09-25T17:34:45.289Z | auto\n\nnevermined-router 0.1.24\n\n- Documentation refreshed across discovery, errors, ledger, and paying references.\n- Updated SKILL.md for improved clarity and current usage guidance.\n- Removed deprecated skill-card.md file.\n- Version and lastUpdated fields incremented in SKILL.md.\n- No changes to API or runtime behavior; this is a documentation update.\n\nv0.1.23 | 2026-09-25T17:07:56.871Z | auto\n\n- Removed obsolete file: skill-card.md, streamlining documentation assets.\n- Updated SKILL.md for improved clarity and accuracy; no functional or interface changes.\n- Confirmed references/errors.md was updated; check details there for specific error handling or clarifications.\n- No changes to APIs or core skill behavior in this version.\n\nv0.1.22 | 2026-09-25T16:37:33.689Z | auto\n\n- Removed deprecated file: `skill-card.md`\n- Updated documentation in `SKILL.md` and `references/errors.md` for clarity and accuracy\n- No functional or API changes; documentation only\n\nv0.1.21 | 2026-09-25T15:54:26.314Z | auto\n\nnevermined-router 0.1.21\n\n- Minor documentation updates in SKILL.md and references/errors.md.\n- Removed the file skill-card.md.\n- No behavioral or interface changes; update is documentation-only.\n\nv0.1.20 | 2026-09-25T15:06:15.544Z | auto\n\n- Removed the obsolete skill-card.md file for improved documentation accuracy.\n- Updated references/errors.md to reflect the latest error codes and handling instructions.\n- SKILL.md and other documentation refreshed and clarified; no user-facing functional changes.\n\nv0.1.19 | 2026-09-25T12:03:11.706Z | auto\n\nnevermined-router 0.1.19\n\n- Documentation updated: SKILL.md and references/errors.md changed.\n- Obsolete documentation removed: skill-card.md deleted.\n\nv0.1.18 | 2026-09-25T11:41:07.358Z | auto\n\nnevermined-router 0.1.18\n\n- Updated documentation: SKILL.md, references/discovery.md, and references/errors.md were modified to clarify or expand content.\n- Removed skill-card.md from the repository.\n- No functional or API-affecting changes—documentation only.\n\nv0.1.17 | 2026-09-24T23:55:51.744Z | auto\n\nnevermined-router 0.1.17\n\n- Deprecated the skill-card.md file; details now consolidated elsewhere.\n- Updated documentation in SKILL.md and references/errors.md for accuracy and clarity.\n- No code/API changes; this is a documentation and cleanup release.\n\nv0.1.16 | 2026-09-18T10:06:21.239Z | auto\n\n## nevermined-router 0.1.16\n\n- Documentation updates: edited `SKILL.md` and `references/errors.md`.\n- No changes to logic, configuration, or API surface.\n- Content clarifications only; no user-facing feature or behavior changes.\n\nv0.1.15 | 2026-09-17T09:12:30.309Z | auto\n\n- Removed obsolete or redundant documentation file: `skill-card.md`\n- Updated internal documentation: `SKILL.md` and `references/errors.md` changed (exact content changes not detailed)\n- No changes to the skill's interface or core behavior documented in this version\n\nv0.1.14 | 2026-09-15T19:37:08.211Z | auto\n\n- Deprecated skill-card.md; users should now refer to SKILL.md for main documentation.\n- Updated internal references and documentation in SKILL.md and reference files to reflect doc structure changes.\n- No impact on core functionality or API usage.\n\nv0.1.13 | 2026-09-11T15:35:44.657Z | auto\n\nnevermined-router v0.1.13\n\n- Updated `references/bootstrap.md` and `references/errors.md` with new or revised technical details and error handling.\n- SKILL.md: Improved guidance on how OAuth-minted API keys affect Delegation creation and clarified the distinction between standard and commerce-key flows.\n- Removed obsolete `skill-card.md` file for clarity and maintenance.\n- Documentation reflects latest platform behaviors and error codes.\n\nv0.1.12 | 2026-09-06T01:43:08.179Z | auto\n\nnevermined-router 0.1.12\n\n- Documentation files updated: SKILL.md and references/errors.md modified.\n- Outdated or redundant file removed: skill-card.md deleted.\n- No changes to functionality or API; update is documentation-only.\n\nv0.1.11 | 2026-09-06T00:08:52.443Z | auto\n\n- Documentation update only; no functionality changes.\n- Removed the file: skill-card.md.\n- Minor edits to references/errors.md and SKILL.md; content and version unchanged.\n- Version remains at 0.1.11.\n\nv0.1.10 | 2026-09-01T13:16:21.425Z | auto\n\nVersion 0.1.10\n\n- Removed the file `skill-card.md`.\n- Updated content in `references/ledger.md`.\n\nv0.1.9 | 2026-09-01T07:38:58.985Z | auto\n\nnevermined-router 0.1.9\n\n- Updated documentation in SKILL.md and references/bootstrap.md.\n- Removed the skill-card.md file.\n- No changes to core functionality; this release primarily restructures and clarifies documentation.\n\nv0.1.8 | 2026-08-28T13:21:23.343Z | auto\n\nnevermined-router 0.1.8\n\n- Removed obsolete skill-card.md file.\n- Updated references/paying.md for improved accuracy or clarity.\n\nv0.1.7 | 2026-08-26T12:42:21.295Z | auto\n\nnevermined-router 0.1.7\n\n- Updated references/ledger.md and references/paying.md documentation.\n- Removed the skill-card.md file.\n- General maintenance and cleanup; no user-facing feature changes.\n\nv0.1.6 | 2026-08-25T10:27:03.700Z | auto\n\nnevermined-router 0.1.6\n\n- Documentation updated: fixed a URL in SKILL.md from `/docs/products/router/overview` to `/docs/products/catalog/router/overview`.\n- Removed obsolete file: skill-card.md deleted.\n\nv0.1.5 | 2026-08-18T14:10:34.727Z | auto\n\nnevermined-router 0.1.5\n\n- Updated references and documentation in SKILL.md and errors.md.\n- Removed the outdated skill-card.md file.\n- Minor corrections and improvements to documentation language and versioning.\n- No changes to API, functionality, or environment variables.\n\nv0.1.4 | 2026-08-17T07:16:12.145Z | auto\n\n- Improved Delegation creation documentation, detailing new refusal reasons for OAuth-minted API keys and accounts with lapsed consent.\n- Added guidance on branching error handling for `consent_required` vs. generic error codes.\n- Updated references for API usage and guardrail documentation.\n- Removed obsolete `skill-card.md` file.\n\nv0.1.3 | 2026-08-13T11:09:28.161Z | auto\n\n- Added explanation of MPP (Tempo) rail availability: clarified that x402 is on by default and MPP requires explicit token allowlisting in each deployment.\n- Warned that MPP payments may fail with \"not allowlisted\" errors when unsupported, and such failures are not client- or merchant-side issues.\n- Clarified that catalog entries do not guarantee payability in every deployment, especially for MPP services.\n- Updated references to error code BCK.ROUTER.0001 and directed users to references/errors.md for more details.\n- Removed outdated file: skill-card.md.\n\nv0.1.2 | 2026-08-12T09:23:39.275Z | auto\n\n## nevermined-router 0.1.2\n\n- Minor documentation updates in SKILL.md and references.\n- skill-card.md file removed.\n- No user-facing feature changes or new functionality in this release.\n\nv0.1.1 | 2026-08-04T09:01:29.613Z | auto\n\nnevermined-router 0.1.1\n\n- Clarified supported and unsupported payment rails: added explicit note that the Router cannot pay conventional SaaS APIs (like Exa) and directs those flows to nevermined-payments.\n- Updated SKILL.md metadata: version bumped to 0.1.1 and last updated date refreshed.\n- Removed outdated skill-card.md file.\n\nv0.1.0 | 2026-08-03T13:25:35.802Z | auto\n\nnevermined-router 0.1.0 — initial public release\n\n- Introduces the Nevermined Router skill for AI agents to pay any external x402 or MPP service without a pre-existing account.\n- Covers discovering services via the Agent Services Catalog, creating and managing Delegations (spending caps), funding a buyer wallet, making paid calls, and reading payment ledger entries.\n- Clearly outlines usage guardrails and conditions where this skill does *not* apply (e.g., non-x402/MPP services).\n- Complements the nevermined-payments skill, which is for receiving payments or buying Nevermined plans.\n- Provides detailed API usage instructions with clear stop conditions and security guidance.\n\nArchive index:\n\nArchive v0.1.30: 8 files, 54638 bytes\n\nFiles: references/bootstrap.md (11264b), references/discovery.md (12574b), references/errors.md (28307b), references/ledger.md (13816b), references/paying.md (24546b), skill-card.md (2394b), SKILL.md (37909b), _meta.json (137b)\n\nFile v0.1.30:SKILL.md\n\n---\nname: nevermined-router\nversion: \"0.1.5\"\nlastUpdated: \"2026-09-26\"\ndescription: >\n  Use when an AI agent needs to PAY an external service it does not have an account\n  with — any x402 agent or MPP merchant — using the Nevermined Router. Covers\n  discovering services in the Agent Services Catalog, creating a spending Delegation\n  from an API key, funding the buyer wallet, pricing a call first with\n  /api/v1/router/quote, making paid calls through /api/v1/router/route (or the\n  streaming /proxy), reading the payment ledger, and the\n  guardrails an autonomous buyer must respect. Complements the nevermined-payments\n  skill, which is about RECEIVING payments and buying Nevermined plans.\nmetadata:\n  openclaw:\n    primaryEnv: NVM_API_KEY\n    requires:\n      env:\n        - NVM_API_KEY\n    envVars:\n      - name: NVM_API_KEY\n        required: true\n        description: >\n          Nevermined API key. Format `sandbox:...` for the sandbox environment,\n          `live:...` for production. Issued from https://nevermined.app under API\n          Keys. Must be a current-generation key — older keys are refused by the\n          Router with 403 BCK.ROUTER.0008.\n---\n\n# Nevermined Router — buy from any x402 or MPP service\n\n> **Skill version**: 0.1.5 | **Last updated**: 2026-09-26 | **Canonical source (always latest):** https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router\n>\n> **⚠️ Use the latest version.** If you have a cached copy, check its **Last updated** date against the canonical source and refresh if older.\n>\n> Human-readable twin of the Router documentation at https://nevermined.ai/docs/products/catalog/router/overview. Same facts, same error codes — if the two ever disagree, the docs site is authoritative and this skill has a bug.\n\n## What this is for\n\nYou are an agent that needs something from a service you have **no account with, no API key for, and no billing relationship with**. The Router lets you pay it per request, from a budget a human capped in advance, and puts every spend on one ledger.\n\nIt works because a growing set of services quote their price **on the wire**: you call them, they answer `402 Payment Required` with what they want, you pay, you get the resource. The Router does the paying.\n\n| | |\n| --- | --- |\n| **Use this skill when** | you need to buy a single call from an external x402 / MPP service |\n| **Use `nevermined-payments` instead when** | you are *charging* callers, or buying a Nevermined **plan** with credits |\n\n<a id=\"not-for\"></a>\n**This skill cannot help you with conventional SaaS APIs.** Exa, Firecrawl, Tavily and similar are billed out of band — a monthly plan, a long-lived key. They never quote a price for one call, so there is nothing on the wire for the Router to pay and no address to pay it to. The Router isn't missing a feature; the transaction it performs does not exist for those services. If a service answers `401` or `403` rather than `402`, it wants **authentication**, not payment — stop, and tell the user it needs an account.\n\nThat is not a dead end, just a different rail. Nevermined can still buy from such a provider **out of band** — purchasing API credits up front instead of paying per call. Exa is the worked example: a $7 x402 card-delegation purchase provisions or tops up an Exa API key, fully agent-driven — https://nevermined.ai/docs/integrations/exa. That flow belongs to the `nevermined-payments` skill and the Payments SDK. What you cannot do is put those calls through `/router/route`.\n\n## The buy loop\n\nSix steps. Steps 1–3 happen once; 4–6 repeat per purchase.\n\n```\n① API key  ──▶ ② Delegation (budget) ──▶ ③ Fund the buyer wallet\n                                              │\n                    ┌─────────────────────────┘\n                    ▼\n   ④ Discover a service ──▶ ⑤ POST /router/route ──▶ ⑥ Read the spend\n      (catalog)                (pays + relays)          (ledger)\n```\n\nSet your environment once:\n\n```bash\nexport NVM_API_URL=\"https://api.sandbox.nevermined.app\"   # live: https://api.live.nevermined.app\nexport NVM_API_KEY=\"<your-api-key>\"\n```\n\nEverything is plain HTTP with `Authorization: Bearer $NVM_API_KEY`. There is **no SDK for the Router yet** — that is deliberate here, because it means any agent in any language can drive it with an HTTP client. The one exception is the catalog, which is public and needs no key at all.\n\n<a id=\"rail-availability\"></a>\n**The two rails are enabled independently per deployment, and the MPP rail is not on everywhere.** x402 (Base) is available by default. MPP (Tempo) requires the operator to allowlist the payment token for that chain, and the allowlist is **fail-closed** — where it is unset, *every* MPP service is refused with `400 BCK.ROUTER.0001 … not allowlisted`, before anything is signed.\n\nSo **a service being in the catalog does not mean your deployment can pay it.** The catalog describes services; it says nothing about how the deployment you are pointed at is configured. If MPP services fail with `0001 … not allowlisted` while x402 services pay fine, the rail is off where you are — that is a deployment setting, not something you can fix from the client, not a fault in the merchant, and not a reason to retry or to go looking for a different MPP service, which will fail identically. Ask the operator of your deployment, or stay on `protocol=x402`. See `references/errors.md`.\n\n**Never send `NVM_API_KEY` to the service you are paying.** It authenticates you to Nevermined and nothing else. If a merchant needs its own auth, pass it in `headers` (mode B) — see `references/paying.md`.\n\n---\n\n## ① Get an API key — *needs a human once*\n\nIssued from the Nevermined app. If you were given one, use it.\n\nA key that predates the Router is refused with **`403 BCK.ROUTER.0008`**. The fix is to create a new key; newly issued keys work. Old keys keep working for credit-based flows, so nothing else needs rotating.\n\n## ② Create a Delegation — *fully programmatic*\n\nA **Delegation** is the budget: a hard cap in cents plus an expiry, enforced server-side on every single payment. Create it once, reuse the id.\n\n```bash\ncurl -sX POST \"$NVM_API_URL/api/v1/delegation/create\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"provider\":\"erc4337\",\"currency\":\"usdc\",\"spendingLimitCents\":500,\"durationSecs\":604800}'\n# → { \"delegationId\": \"5e7481c3-e972-45bd-bdc5-a0b99c4de4a1\" }\n```\n\nThat is a $5.00 cap for 7 days. `provider: \"erc4337\"` is the crypto-funded Delegation both stablecoin rails require — a card-funded Delegation is refused on them.\n\n<a id=\"never-widen\"></a>\n**You may create a Delegation. You must never widen one to get past a refusal.** The cap is the human's decision; a refusal is that decision taking effect. See [Guardrails](#guardrails).\n\nTwo guards can refuse this call before your fields are read, and **neither is retryable**:\n\n- **`403 BCK.OAUTH.0030`** — the key was minted through an OAuth consent ceremony (`credits_purchase`, `account_access` or `commerce`) and may not create Delegations *or* use the paying routes. Use a plain API key issued by the account owner — or, if the key comes from a `commerce` grant, spend through `POST /api/v1/router/commerce/route` (and price a call through `POST /api/v1/router/commerce/quote`), which derive the Delegation from the grant instead of taking one from you.\n- **`412 {\"error\":\"consent_required\",\"outdated\":[…]}`** — the account's legal-document consent has lapsed. ⚠️ Its only `code` is the generic **`BCK.HTTP.412`**, which names the status and not the cause, so branch on `body.error === \"consent_required\"` — the one place \"branch on `code`\" needs a second field. A human must accept; report it and stop.\n\nFull field list, recipient scoping, both guards in detail, and reading a Delegation's live state: `references/bootstrap.md`.\n\n## ③ Fund the buyer wallet — *may need a human*\n\nBoth rails **pull**: the merchant takes funds from your own wallet. The Delegation authorizes the spend; it does not provide the money. Read the wallet address off the Delegation:\n\n```bash\ncurl -s \"$NVM_API_URL/api/v1/delegation/$NVM_DELEGATION_ID\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\"\n# → { \"providerPaymentMethodId\": \"0x8F60b3838e6C121FcDBdBc50e7B150F8560a670E\", ... }\n```\n\n`providerPaymentMethodId` is the address to fund, with the payment asset, **on the network you intend to pay on**.\n\n**Your deployment funds exactly one x402 network, fixed by its environment: sandbox → `base-sepolia`, live → `base`.** A merchant on the other chain is unpayable from where you are and fails with `400 BCK.ROUTER.0001 … no fundable option`, which reads like a broken service and is not. Check the environment before blaming the merchant — see `references/bootstrap.md`.\n\n**Always read this address back from the live Delegation — never from a value you cached.** Funding a stale address is the most common cause of `402 BCK.ROUTER.0009`, and the error deliberately does not echo the address it checked, so it cannot tell you that is what happened.\n\nIf the wallet is empty and you cannot fund it yourself, that is a **stop condition**: report it to the human. Do not retry.\n\n## ④ Discover a service\n\nThe **Agent Services Catalog** is one public JSON feed — no API key, no query parameters. Fetch it and filter on your side:\n\n```bash\ncurl -s https://nevermined.app/catalog/ai-catalog.json \\\n  | jq '[.services[] | select(.protocol == \"x402\" and .category == \"Search & Research\")\n         | {slug, title, priceLabel, endpoints: [.endpoints[] | {method, path: (.invokePath // .path), description}]}]'\n```\n\n```json\n[\n  {\n    \"slug\": \"superhighway\",\n    \"title\": \"Superhighway — Web Search for Agents\",\n    \"priceLabel\": \"$0.001\",\n    \"endpoints\": [\n      { \"method\": \"POST\", \"path\": \"/search\", \"description\": \"Web search\" },\n      { \"method\": \"POST\", \"path\": \"/news\",   \"description\": \"Real-time news search\" },\n      { \"method\": \"POST\", \"path\": \"/images\", \"description\": \"Image search\" }\n    ]\n  }\n]\n```\n\n(An excerpt: the real result lists every match.) `/api/v1/catalog/services` and `/api/v1/catalog/categories` are **not a public API** — they return `403` by design. For server-side search, the Catalog MCP (`search_services`, `get_service`, `list_categories` at `https://mcp.live.nevermined.app/mcp`) is free and needs no key.\n\nTwo rules that will otherwise cost you a wasted payment:\n\n1. **Only `protocol` of `x402` or `mpp` is payable through the Router.** Filter for them. Anything else in the catalog is listed for discovery, not for routing — see [above](#not-for).\n\n2. **Pay a listed service by its `slug`, never by URL.** The feed carries no merchant URL on purpose: the Router resolves it server-side and refuses a raw-URL payment to a cataloged host (`409 BCK.ROUTER.0014`). The subpath to send is the endpoint's `invokePath` when present — **even `\"\"`, which means \"append nothing\"** — and its `path` otherwise:\n\n   ```js\n   const subpath = endpoint.invokePath ?? endpoint.path   // NOT `||`: '' must stay ''\n   // edgar-search: path '/edgar-search/search', invokePath '' → send ''. Sending the path double-stacks it and 404s after the charge.\n   ```\n\nMore filter recipes, categories, the Catalog MCP, and the ARD host document: `references/discovery.md`.\n\n**From API 1.55**, server-side selection (`POST /api/v1/router/select` and MCP `route_by_intent`)\naccepts exact opaque catalog slugs in `filters.require`, `filters.prefer` and `filters.exclude`.\n`require` is the strict control: the Router selects that slug or fails closed with\n`409 BCK.ROUTER.0031`; it never silently substitutes another service. `prefer` falls back to normal\nranking when its slug is not payable, while `exclude` removes its slug from consideration.\n\n## ⑤ Make the paid call\n\nHand the Router the request you want made. It probes the service, **auto-detects** the protocol from the 402, pays, and relays the answer — one call, and you never see the 402.\n\n```bash\ncurl -sX POST \"$NVM_API_URL/api/v1/router/route\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"delegationId\": \"'\"$NVM_DELEGATION_ID\"'\",\n    \"slug\": \"superhighway\",\n    \"path\": \"/search\",\n    \"method\": \"POST\",\n    \"body\": { \"query\": \"nevermined router\" },\n    \"requestId\": \"search-nevermined-router-v1\"\n  }'\n```\n\nA cataloged service is addressed by `slug` + `path` (the endpoint's `invokePath ?? path`). Send an absolute `url` instead only for an off-catalog x402 / MPP service.\n\n```json\n{\n  \"status\": 200,\n  \"body\": { \"…\": \"the paid resource\" },\n  \"paid\": true,\n  \"payment\": {\n    \"paymentId\": \"b1f9c2e4-…\",\n    \"settlement\": { \"amount\": \"1000\", \"asset\": \"USDC\", \"network\": \"base\", \"approxCents\": \"1\" },\n    \"fee\": { \"bps\": 0, \"amount\": \"0\", \"cents\": \"0\", \"capChargedCents\": \"1\" },\n    \"txHash\": \"0xfc8af37b…\",\n    \"status\": \"Settled\"\n  }\n}\n```\n\n`status` and `body` are the merchant's own, unchanged. `paid: false` with no `payment` block means nothing was charged. For a catalog `slug`, the body of a free answer is withheld (`null`), except when you poll the result of an async job you already paid for through the same slug: see \"Async services\" in `references/paying.md`.\n\n<a id=\"fee\"></a>\n**`settlement.approxCents` is the merchant leg, not your bill.** Nevermined charges a routing fee on top, disclosed in the **always-present `fee` object** (zeroed when no fee applied, so never branch on its absence): `fee.capChargedCents` is what this call **reserved** against your Delegation cap — `settlement.approxCents + fee.cents`. Sum `capChargedCents`, not `approxCents`, or your accounting drifts by exactly the fee.\n\n⚠️ It is the **reserve at mint**, not the final figure: if a mode-B hop does not return `2xx` the fee half is given back (the merchant leg stays charged), so a running total over-reports on those calls. **`GET /api/v1/delegation/{id}` is the authority on what you have actually spent** — reconcile against `amountSpentCents` rather than your own sum. Full field list: `references/paying.md`.\n\n<a id=\"requestid\"></a>\n**`requestId` is required, and it is an idempotency key — not a request counter.** Use **one stable id per logical purchase** and reuse it across retries of that purchase. Retrying a dropped call with the same id returns `409 BCK.ROUTER.0002` carrying the original `paymentId` — **not the resource** — instead of buying twice; a fresh id buys twice, on purpose. **Never answer that 409 by minting a fresh id**: that is the double-spend the key just prevented. If the purchase genuinely failed, report it. Derive it from the work you are doing (`\"search-nevermined-router-v1\"`), not from `uuid4()` per HTTP attempt — a fresh UUID on every retry is how an agent double-spends.\n\n<a id=\"pending\"></a>\n**From API version 1.48** (a key pinned at or above it — new keys are pinned to the current version), a slow service no longer holds your connection. If it has not answered within **45 s**, `/route` answers `202 { paymentId, resultUrl, status: \"Pending\" }` (`/router/proxy` · `/router/svc`: `202` with `Location` and `X-Router-Payment-Id`, and only before the service starts responding). **The payment is made**; the call keeps running on the Router (up to its 120 s ceiling) and its fee is collected on a `2xx` or released otherwise, exactly as if you had waited. **Poll `GET $NVM_API_URL{resultUrl}`** with the same key: `202` while `Pending`, then `200` with `state: \"Ready\"` (`status` + `body`, as `/route` would have returned them) or `state: \"Failed\"` (`failureReason`), and `404 BCK.ROUTER.0030` once it has expired. **Never pay again for a `202`.** Every paid result is kept for 24 h after the call ends, for the paying account only — a paid `/route` response carries it as `payment.resultUrl` — except a `/proxy` · `/svc` response that already streamed to you. A same-`requestId` retry returns that `202`, then the retained result, instead of the 409 — on `/proxy` · `/svc` too, when the first call answered `202`. It still gets the 409 when nothing was kept (a `/proxy` · `/svc` response that already streamed to you), when the first call `Failed`, or when the id was reused for a different target. Older keys keep waiting and get the 409, though their results are kept too. Either way, keep the same id.\n\n<a id=\"quote\"></a>\n**Price it first: `POST /api/v1/router/quote`** (deployments on API 1.48 or later). Send the same body as `/route` minus `requestId`, `maxTotalCents` and `protocol`, which are stripped rather than refused (`delegationId` stays optional). The Router makes the same unpaid request to the service that a payment would, selects the option a payment would select, prices it with the routing fee — and stops. **Nothing is signed, minted, recorded or reserved.** It answers `200` with `paymentRequired`, `optionSet`, `protocol` and the same `settlement` and `fee` objects `/route` returns. Then route with `maxTotalCents` set to the quote's `fee.capChargedCents`, so a price that rose in between is refused (`402 BCK.ROUTER.0018`) instead of paid.\n\n**From API 1.55**, a payment-required quote also returns a short-lived opaque `quoteId` and\n`expiresAt` **60 seconds** later. Pass that `quoteId` to `/route` with the exact quoted target,\nmethod, headers, body, credential header and Delegation. It pays the sealed merchant challenge at\nthe exact fee-inclusive amount without accepting a changed call; keep `maxTotalCents` too as an\nindependent ceiling. An invalid/wrong-account id is `0029`, an expired id is `0032`, and any request,\nDelegation, rail or amount mismatch is `0033` — all before a charge. Re-quote when the call changes\nor the id expires. Clients pinned below 1.55 receive no `quoteId`/`expiresAt` and retain the legacy\nre-probe-plus-ceiling flow.\n\n- **It is not free of side effects.** The request really reaches the service, so a service that does not charge for it performs it — take care quoting a method with side effects. And a quote spends the same per-key and per-service rate budgets as a payment: **quote once per decision, don't poll.**\n- `fee.capChargedCents` is whole cents rounded **up** from `fee.capChargedMicros` (exact, in 1/10,000 of a cent), and it is the figure `maxTotalCents` is compared against. The ceiling has whole-cent resolution, so any price up to that whole cent is still paid: a 2.04¢ quote paid with `maxTotalCents: 3` accepts up to 3.00¢.\n- `optionSet: \"delegation\"` means the `delegationId` you sent was priced (a card Delegation pays over MPP-stripe, an organization-wallet Delegation pays in its own currency, a recipient allowlist is enforced); `\"deployment\"` means you sent none, so this is what a personal crypto Delegation would select. `paymentRequired: false` means the service did not ask for payment; `upstreamStatus` is its answer, and its body is never returned.\n- A quote checks neither your remaining cap nor your wallet balance — the payment still does. `503 BCK.ROUTER.0028` (a read the price depends on failed) is retryable with backoff.\n- **With an OAuth `commerce` credential**, quote on `POST /api/v1/router/commerce/quote` instead (it ships in the first API release after 1.49; until your deployment has it, the route answers `404`, so bound the price with `maxTotalCents` alone): same body and answer, but it prices the Delegation the grant is pinned to, so sending a `delegationId` is refused (`400 BCK.OAUTH.0034`). Then pay on `/router/commerce/route`.\n\nMode A (you call the merchant yourself), the streaming `/proxy` variant, and passing the merchant's own auth: `references/paying.md`.\n\n## ⑥ Read what you spent\n\n```bash\ncurl -s \"$NVM_API_URL/api/v1/router/payments?delegationId=$NVM_DELEGATION_ID\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\"\n```\n\nEvery payment, every protocol, one ledger. Filters, CSV export, and the aggregate summary: `references/ledger.md`.\n\n---\n\n<a id=\"guardrails\"></a>\n## Guardrails — read this before writing a retry loop\n\nThe Router signs payments from your wallet in response to instructions written by a merchant nobody vetted. It is deliberately suspicious, and **a refusal is the system working**.\n\n**Four rules for an agent that spends without a human watching:**\n\n1. **`402 BCK.ROUTER.0003` (over cap / expired) and `402 BCK.ROUTER.0009` (wallet short) are stop conditions.** They mean \"out of budget\" and \"out of money\". Report them to the human. Do not route around them.\n2. **Never widen a Delegation, and never create a second one, in response to a refusal.** The cap is the user's decision, not a runtime obstacle. Creating a fresh Delegation to escape an exhausted one defeats the entire mechanism — it is the single worst thing you can do with this API.\n3. **One `requestId` per purchase**, reused across retries of that purchase. See [above](#requestid).\n4. **Retry only the codes the table below marks retryable: `0006` (500, summary read), `0007` (429), `0020` (an upstream 5xx/429), `0022` (500, selection not wired) and `0028` (503, quote).** On the paying path that means `0007` and `0020`. Everything else is a decision, and retrying it unchanged produces the same answer. Back off on `0007` (too many routed calls in flight) and on `0020` (the upstream service errored or throttled). **The HTTP status does not tell you whether to retry** — `0010` is a 500 you must not retry and `0011` is a 402 you must not retry. Read the code, not the status.\n\n**Check the price before you commit.** `priceLabel` in the catalog is indicative — [quote the call](#quote) for its live, fee-inclusive price; on the response, `settlement.approxCents` is what the **merchant** charged and [`fee.capChargedCents`](#fee) is what your **cap** reserved — they differ whenever a routing fee applies. Budget is debited in whole cents rounded up, so a run of sub-cent calls still burns a cent each. For spend to date, read the Delegation, not a sum of responses.\n\n**Delegations expire silently.** A long-running agent that worked yesterday and fails today with `0003` has very often just aged out — check `expiresAt` before assuming anything is broken.\n\n| Code | Status | Meaning | Retry? |\n| --- | --- | --- | --- |\n| `BCK.ROUTER.0001` | 400 | Bad input: unsupported protocol, malformed/empty challenge, no fundable option, recipient outside the Delegation's scope, non-allowlisted asset, wrong-provider Delegation, missing `delegationId`. `details` names the specific problem. | No |\n| `BCK.ROUTER.0002` | 409 | This `requestId` already minted a payment. The original `paymentId` is in the response — usually what you wanted. | No |\n| `BCK.ROUTER.0003` | 402 | Delegation over cap, expired, exhausted, or revoked. | No — **stop** |\n| `BCK.ROUTER.0004` | 404 | No Router payment with that id belongs to you. | No |\n| `BCK.ROUTER.0005` | 409 | Payment not in a settleable state. Only `Issued` can be marked `Settled`. | No |\n| `BCK.ROUTER.0006` | 500 | Transient failure building the payments summary. | **Yes** |\n| `BCK.ROUTER.0007` | 429 | Too many concurrent routed requests in flight. | **Yes**, after backoff |\n| `BCK.ROUTER.0008` | 403 | Legacy API key. Create a new one. | No |\n| `BCK.ROUTER.0009` | 402 | Wallet doesn't hold enough of the asset on the target network. Nothing was signed. | No — **stop** |\n| `BCK.ROUTER.0010` | 500 | Internal: the rail reported a charge amount the Router can't reserve against the cap. | No — **never blind-retry** |\n| `BCK.ROUTER.0011` | 402 | Card rail: the charge needs cardholder 3-D Secure, and an agent has no browser to complete it. Nothing was charged and the seller got no usable credential. | No — **needs a human** |\n| `BCK.ROUTER.0012` | 400 | The seller's 402 advertises an EIP-712 domain its own settlement token does not sign under, so the Router refuses to sign. Nothing signed, charged or reserved — an authorization under the wrong domain is unspendable anyway. Seller-side bug | No — **report it, pay elsewhere** |\n| `BCK.ROUTER.0013` | 500 | Nevermined holds no EIP-712 signing domain for the token the funding filter selected — a gap in OUR canonical table, not the seller's bug and not your request. Nothing signed, charged or reserved | No — **report it to Nevermined** |\n| `BCK.ROUTER.0014` | 409 | The target is a cataloged Nevermined service, whose upstream URL is deliberately hidden. The Router refuses to pay it by raw URL — mode A and a raw mode-B target both put the merchant's host on your wire, defeating the broker. The match is by HOST, so a co-hosted endpoint that is not itself listed is refused too — ask the vendor to list it, or contact Nevermined; hosts with no cataloged service are unaffected. | No — **use the slug**: `POST /router/route` with a `slug`, or `POST /router/svc/<catalog-slug>` (a `commerce` grant: `POST /router/commerce/route` with a `slug`). A refused **quote** is re-quoted by slug — `POST /router/quote`, or `/router/commerce/quote` for a grant — never paid |\n| `BCK.ROUTER.0018` | 402 | Per-call `maxTotalCents` is below the fee-inclusive, whole-cent cap reserve. No charge or cap reserve, though signing may already have occurred; parse JSON-string `params` for `requiredTotalCents`. | No — raise the ceiling only if this call is intended; reuse the same `requestId` |\n| `BCK.ROUTER.0019` | 400 | Streaming surfaces only (`/proxy` · `/svc`; `/route` returns the envelope status with `body: null`). A cataloged service returned a **non-retryable** status — a 4xx client error, or a rare 3xx the Router does not follow (a 402 re-challenge and a 429 are **not** this code). The upstream body is withheld (it can name the merchant host); this typed body preserves the **real** upstream status (on the HTTP status line and in JSON-string `params`). Build a valid request from the service's Catalog detail (`requestExample` / `responseFields`). | No — **fix the request first**, then retry with a fresh `requestId` |\n| `BCK.ROUTER.0020` | 502 | Streaming surfaces only (`/proxy` · `/svc`; `/route` returns the envelope status with `body: null`). A cataloged service returned a server error (5xx) or rate-limited (429) — an upstream/transient condition, not your request. Body **and** headers are withheld (host oracle, including `Retry-After`); this typed body preserves the **real** status. The Router charges **no routing fee** for an undelivered call; whether the merchant leg itself charged is reported as `merchantSettlementObservedAt` (x402 only — `null` on a clean settlement and on both MPP rails, so `null` is not proof of no charge; read alongside `status`) on `GET /api/v1/router/payments`. | **Yes**, with backoff — reuse the same `requestId` only if no `X-Router-Payment-Id` came back; if one did, a payment is already recorded, so use a NEW id and reconcile via `GET /router/payments` |\n| `BCK.ROUTER.0021` | 400 | The rail this service advertised carries its payment credential in a header you are **already using**. On the MPP rails that header is `Authorization`, which is also where your own merchant auth goes (`headers.Authorization` on `/route`, `X-Router-Upstream-Authorization` on `/proxy` · `/svc`). Rather than silently dropping yours on the paid hop, the Router refuses: **nothing was minted, no cap was reserved and no money moved**. JSON-string `params` names the contested header. | No — **name the header the service documents** for its credential: `credentialHeader` in the `/route` body, or the `X-Router-Credential-Header` request header on `/proxy` · `/svc` (a separate `Payment` header is the common one). If the service documents none, it wants the credential in `Authorization` itself and cannot also take your bearer there: drop your own auth for that call, or pay it over an x402 endpoint (whose credential travels in `PAYMENT-SIGNATURE`). Retrying unchanged fails identically |\n| `BCK.ROUTER.0022` | 500 | Server-side service selection is not wired on this deployment (a configuration fault, not your request) — nothing was ranked or charged. Only reachable on a misconfigured deployment; never in a healthy environment. | **Yes**, later — it is a transient/config condition on our side; if it persists, quote `correlationId` when reporting it |\n| `BCK.ROUTER.0023` | 502 | On an autoPay `POST /router/select` (or `/router/commerce/select`): the chosen service was paid but the downstream call did not complete cleanly **after** the payment was created, so the charge outcome is **indeterminate** — the merchant leg may or may not have settled. JSON-string `params` carries the `paymentId` when one was created. | No — do **not** retry with a fresh `requestId` (that could double-charge). Reconcile via `GET /api/v1/router/payments`, then reuse the **same** `requestId` to retry safely |\n| `BCK.ROUTER.0024` | 413 | The request body exceeds the Router's size limit (about 5 MB). | No — reduce the request body before trying again |\n| `BCK.ROUTER.0025` | 502 | The upstream reply was too large to deliver after a paid request. The payment outcome is indeterminate; it may have gone through. | No — this reply has no `X-Router-Payment-Id`; JSON-string `params` may carry `paymentId`. If absent, call `GET /api/v1/router/payments` with `delegationId` and `from` just before the call, then match `requestId` in the returned rows (newest 1000 maximum; there is no `requestId` filter). The row reads `status: Failed` because delivery failed, not because the merchant was uncharged. A non-null `merchantSettlementObservedAt` confirms x402 settlement; null does not prove no charge, including on MPP rails. Do not retry: the same `requestId` returns 409 `BCK.ROUTER.0002` with the original `paymentId` and cannot re-deliver the reply; a fresh id risks another charge. |\n| `BCK.ROUTER.0026` | 415 | The Router cannot forward this request body. The streaming surfaces (`/router/svc/:slug`, `/router/proxy`) forward only JSON (`application/json`) or URL-encoded (`application/x-www-form-urlencoded`) bodies; any other type — `multipart/form-data` above all, but also `text/plain`, `application/octet-stream` or a vendor `+json` — and any body on GET/HEAD is refused. No payment was minted and no money moved. JSON-string `params` names the refused `contentType` (null when none was sent). | No — resend the body as JSON or a URL-encoded form the service accepts; a service that only takes a file upload cannot be paid through the Router yet, and retrying unchanged fails identically |\n| `BCK.ROUTER.0027` | 413 | The request body is larger than the catalog endpoint accepts. The catalog records a `maxRequestBytes` per endpoint (on the service detail and in MCP `get_service`) — the Locus gateways (`*.mpp.paywithlocus.com`) take 8,000 bytes — and a slug-routed call (`/router/route`, `/router/quote`, `/router/svc/:slug`, `/router/proxy` with a slug) whose body is larger is refused before the service is contacted. No payment was minted and no money moved. JSON-string `params` carries `bodyBytes` and `maxRequestBytes`. | No — shrink the body below the limit or split the work, or pick a service that takes larger requests (`POST /router/select` with the same `body` skips endpoints whose limit is below it); retrying unchanged fails identically |\n| `BCK.ROUTER.0028` | 503 | `POST /router/quote` could not price the call because a read it depends on failed (for example the settlement-token details on the payment network). Nothing is signed, minted or charged on the quote path. | **Yes**, with backoff — a quote never charges, so nothing needs unwinding. The same condition would also fail a payment, so do not route the call meanwhile; if it persists, quote `correlationId` |\n| `BCK.ROUTER.0029` | 404 | The `quoteId` is invalid or belongs to another account. Nothing was signed, reserved or charged. | No — request a new quote and use its `quoteId`; do not retry the same invalid id |\n| `BCK.ROUTER.0030` | 404 | No retained paid result for that paymentId under your account. A paid Router result is retained for 24h after the call ends, for the paying user only; it is not retained for a response that had already started streaming when the call completed, or for a payment never routed through `/router/route`, `/router/proxy` or `/router/svc`. The payment record itself is unaffected. | No — the result is gone (or was never retained); read the payment with `GET /api/v1/router/payments` |\n| `BCK.ROUTER.0031` | 409 | The exact catalog slug in `filters.require` is unavailable, unhealthy, unpayable, excluded by this request, or cannot accept the body. The Router fails closed instead of substituting another service. | No — inspect `params.reason`; correct the slug or request, or deliberately remove `require` to allow fallback |\n| `BCK.ROUTER.0032` | 410 | The `quoteId` expired before payment began. Nothing was signed, reserved or charged. | No — quote the same call again and decide against the new fee-inclusive total; do not retry the expired id |\n| `BCK.ROUTER.0033` | 409 | The payment differs from the quote in its target, method, headers, body, credential header, delegation, rail, or exact fee-inclusive amount. Nothing was reserved or charged. | No — send the quoted call unchanged, or request a new quote for the changed call |\n| `BCK.ROUTER.0034` | 502 | A paid request got no response from the service (timeout or connection failure) after the credential was sent — the service may already have redeemed the payment. `params` carries the `paymentId`. | No — reconcile via `GET /api/v1/router/payments/{paymentId}`; never retry with a fresh `requestId` (it could pay twice) |\n| `BCK.ROUTER.0035` | 422 | The service's challenge asks for no payment (a zero amount — typically an auth-only \"sign in with your wallet\" challenge). Your request is not malformed; nothing was signed, reserved or charged. | No — use a service that charges for the call, or authenticate with the service directly |\n| `BCK.ROUTER.0036` | 422 | Every catalog endpoint the call reaches is known to answer after the Router abandons a paid call (120 s), and the merchant charges on receipt — so paying would charge and deliver nothing. Refused before any payment (a retry of an already-paid `requestId` gets `0002` instead). `params.source` is `declared` (permanent) or `observed` (lifts at `params.liftsAt`). On a declared free-follow-up path it is raised only when the service answers `402`. | No — pick another endpoint or service; an `observed` refusal lifts at `liftsAt` |\n| `BCK.OAUTH.0030` | 403 | This API key was OAuth-minted and may not create Delegations or use `/router/{payments,route,quote,select,proxy,svc}`. Use a plain account-owner key — or, for a `commerce` grant, `POST /router/commerce/route` to pay and `POST /router/commerce/quote` to price. | No |\n| `BCK.OAUTH.0033` | 403 | A commerce route (`/commerce/route`, `/commerce/route/with-controls`, `/commerce/select`, `/commerce/quote`) needs a credential minted from a `commerce` grant pinned to a usable Delegation. A plain key uses the twin that names its own Delegation: `POST /router/route` to pay, `/router/select` to pick, `/router/quote` to price. A commerce credential that still sees it: re-run the authorization. | No |\n| `BCK.OAUTH.0034` | 400 | `delegationId` sent on a commerce route; there it is derived from the grant. Remove it, or use the plain-key twins (`/router/route`, `/router/select`, `/router/quote`) to choose one. | No |\n| `BCK.HTTP.412` | 412 | `{\"error\":\"consent_required\"}` on `POST /delegation/create` — the account's legal-document consent lapsed. The code is generic; branch on `body.error`. | No — **needs a human** |\n\n**`0011` needs a human, not a retry.** The card issuer is demanding 3-D Secure and the Router has no browser to answer it. Nothing was charged. Do **not** loop: 3DS is often mandated per charge, so every attempt re-demands it and mints a fresh single-use card credential that is then abandoned. A later *human-driven* attempt may succeed — that is a decision, not a retry.\n\n**`0010` is the one 500 you must never retry.** A payment credential **was already minted** before it failed — and because no payment record was written, your `requestId` will *not* suppress the retry. So a retry mints a **fresh** credential and then fails identically, because the cause is a deterministic defect in the rail's amount derivation, not a transient blip. Report it to the human.\n\nNote `0006`, the retryable 500, is only ever raised by the payments *summary* read — never by a payment. **On the paying path `0007` and `0020` are the codes worth retrying (with backoff).** And seeing `0010` at all means a Nevermined-side regression: no rail emits a non-numeric amount today, so it is a bug report, not a condition to handle. (On the card rail the minted credential is a Stripe Shared Payment Token, left stranded with no revoke path until `min(challenge expiry, Delegation expiry, 89 days)`.)\n\nCatalog errors: `BCK.CATALOG.0001` (404, no listed service with that slug — slugs are case-sensitive), `BCK.CATALOG.0002` (500, transient, retryable).\n\nWhat the Router refuses outright — private/loopback/metadata targets, redirects, MPP `splits`, forged `X-Router-*` headers — and the relay limits: `references/errors.md`.\n\n## Reference files\n\n| You need… | Read |\n| --- | --- |\n| The Catalog feed, filter recipes, categories, the Catalog MCP, the ARD host document | `references/discovery.md` |\n| Mode A vs mode B, `/proxy` streaming, merchant auth, full payloads | `references/paying.md` |\n| Delegation fields, recipient scoping, wallet funding, networks | `references/bootstrap.md` |\n| Every guardrail, every code, what is retryable and why | `references/errors.md` |\n| Payment records, filters, CSV export, summary, reconciliation | `references/ledger.md` |\n\nFile v0.1.30:_meta.json\n\n{\n  \"ownerId\": \"kn7bk8z6x7ytxvdb48j34j2ahh812m3p\",\n  \"slug\": \"nevermined-router\",\n  \"version\": \"0.1.30\",\n  \"publishedAt\": 1791451098137\n}\n\nFile v0.1.30:references/bootstrap.md\n\n# Bootstrap — API key, Delegation, funded wallet\n\nThree preconditions before any payment. Do them once and reuse.\n\n## 1. The API key\n\nEvery Router call carries `Authorization: Bearer $NVM_API_KEY`. Issued from the Nevermined app —\nthis is the one step that needs a human.\n\nKeys are environment-scoped: `sandbox:…` for sandbox, `live:…` for production. A key from the wrong\nenvironment fails auth, not the Router's own checks.\n\n**A key issued before the Router shipped is refused with `403 BCK.ROUTER.0008`.** It is bound to a\nprevious account model that cannot sign these payments. The fix is to create a new key — newly\nissued keys work. Existing keys keep working for credit-based flows, so nothing else needs rotating.\nThis is not retryable and not a transient error; do not loop on it.\n\n**Never forward this key to a merchant.** It authenticates you to Nevermined. If the merchant needs\nits own credential, pass that separately (`headers` in mode B, `X-Router-Upstream-Authorization` on\n`/proxy`) — see `paying.md`.\n\n## 2. The Delegation\n\nYour budget. A hard cap in cents plus an expiry, enforced server-side on **every** payment.\n\n```bash\ncurl -sX POST \"$NVM_API_URL/api/v1/delegation/create\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"provider\":\"erc4337\",\"currency\":\"usdc\",\"spendingLimitCents\":500,\"durationSecs\":604800}'\n# → { \"delegationId\": \"5e7481c3-e972-45bd-bdc5-a0b99c4de4a1\" }\n```\n\n| Field | Required | Notes |\n| --- | --- | --- |\n| `provider` | **yes** | `erc4337` for both stablecoin rails. No default — omitting it is a 4xx |\n| `currency` | **yes** | `usdc` · `eurc` · `usd` · `eur`. No default |\n| `spendingLimitCents` | **yes** | Integer ≥ 1. The hard cap, in cents |\n| `durationSecs` | **yes** | Integer ≥ 1. `604800` = 7 days |\n| `allowedRecipients` | no | Up to 100 `0x…` EVM addresses. **Omit = no restriction** |\n| `maxTransactions` | no | Cap on number of charges. Omit = unlimited |\n\n`provider: \"erc4337\"` is the crypto-funded Delegation both stablecoin rails require. Card-funded\nDelegations use a different provider and are refused on those rails (and vice versa) with\n`400 BCK.ROUTER.0001`.\n\n### Two ways creation is refused before your fields are even read\n\nBoth guard the *caller*, not the request body, so a perfectly valid payload still fails. Neither is\nretryable and neither can be fixed from your side alone.\n\n**`403 BCK.OAUTH.0030` — this API key may not create Delegations.** An **OAuth-minted** credential\n(one issued through an OAuth consent ceremony — today `credits_purchase`, `account_access` or\n`commerce`, but the guard keys on the binding rather than the consent type, so any future ceremony\ntype is refused too) is refused on\n`POST /delegation/create` *and* on the paying routes — `POST /router/payments`, `POST /router/route`,\n`POST /router/route/with-controls`, `POST /router/quote`, `POST /router/select`, `ALL /router/proxy`,\n`ALL /router/svc/<slug>`. Those routes sign from the account's full wallet,\noutside the narrow policy such a credential advertises, so the advertised scope would not be the real\nspend boundary. The fix is a **plain API key issued by the account owner** from the Nevermined app.\nNothing about the request will make an OAuth-minted key work on these routes — do not retry. The one\nexception is a key from a **`commerce`** grant: it spends through `POST /api/v1/router/commerce/route`,\nwhich derives the Delegation from the grant instead of taking one from you.\n\n**`412` with `{\"error\":\"consent_required\"}` — the account's legal-document consent has lapsed.**\n\n```json\n{ \"error\": \"consent_required\", \"outdated\": [\"terms\", \"privacy\"],\n  \"code\": \"BCK.HTTP.412\", \"httpStatus\": 412, \"category\": \"validation\",\n  \"retryable\": false, \"correlationId\": \"…\" }\n```\n\n<a id=\"consent-412\"></a>\n⚠️ **`code` alone will not identify this one.** It is deliberately *not* an `NVMException`, so it has\nno `BCK.LEGAL_DOCS.…` code of its own; the global error filter normalises it and stamps the generic\n**`BCK.HTTP.412`**, which restates the status and says nothing about the cause. The cause is in\n`body.error === \"consent_required\"` — branch on that, and read `body.outdated[]` for the document\nslugs (`terms`, `privacy`) not yet accepted at their current version. This is the one place the\n\"branch on `code`\" rule in `errors.md` needs a second field.\n\nIt applies to the whole account — the same guard gates ten routes across delegation creation, card\nenrolment and fiat checkout — so **every** Delegation-creating call fails until it is resolved.\nCheck it up front with `GET /api/v1/legal-documents/me/consent-status` (same bearer key), which\nreturns `never` · `outdated` · `current` per slug.\n\n**Accepting is a human's act, not yours.** `POST /api/v1/legal-documents/me/consents` does accept an\nAPI key, so you *can* clear this yourself — and you must not. **Accepting terms is the account\nholder agreeing to be bound by them, and that is consent you have no standing to give on their\nbehalf**; an API key authorises you to spend within a cap, not to enter agreements for the person\nwho issued it. Calling that endpoint would also erase the only signal that the human never saw the\nnew document. Report the 412, name the slugs in `outdated[]`, and stop.\n\nThis is also why an agent that ran for weeks can fail at step ② one morning having changed nothing:\na document was updated on Nevermined's side.\n\n### Recipient scope is optional, and unset means unrestricted\n\nIf `allowedRecipients` is present, the merchant's pay-to address must be on it — checked *before*\nthe expensive signing step, so a disallowed recipient costs nothing. If it is **absent, there is no\nrecipient restriction at all**: the Delegation can pay any merchant the Router can reach, and its\ncap and expiry are the only limits.\n\nDo not assume a Delegation is address-bound unless you deliberately made it so. Card-funded\nDelegations are vendor-agnostic by design and never carry one.\n\nScoping is worth it when you already know who you are paying — it turns a compromised or confused\nagent's blast radius from \"anyone\" into \"these addresses\". It is impractical when you are shopping\nthe catalog, since you do not know the pay-to address until the 402 arrives.\n\n## 3. Read its live state — and the wallet address\n\n```bash\ncurl -s \"$NVM_API_URL/api/v1/delegation/$NVM_DELEGATION_ID\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\"\n```\n\n```json\n{\n  \"delegationId\": \"5e7481c3-…\",\n  \"provider\": \"erc4337\",\n  \"providerPaymentMethodId\": \"0x8F60b3838e6C121FcDBdBc50e7B150F8560a670E\",\n  \"status\": \"Active\",\n  \"spendingLimitCents\": \"500\",\n  \"amountSpentCents\": \"0\",\n  \"remainingBudgetCents\": \"500\",\n  \"expiresAt\": \"2026-08-07T00:00:00Z\"\n}\n```\n\n`status` must be `Active`. `Revoked`, `Expired` and `Exhausted` all refuse with\n`402 BCK.ROUTER.0003`.\n\n**Check `expiresAt` before diagnosing anything.** Delegations expire silently — an agent that worked\nyesterday and fails today with `0003` has very often just aged out, and that looks identical to a\nbroken rail until you look.\n\nYou can list your Delegations with `GET /api/v1/delegation`, and a single Delegation's charges with\n`GET /api/v1/delegation/{id}/transactions`.\n\n## 4. Fund the buyer wallet\n\n**`providerPaymentMethodId` is the wallet address to fund.** Both rails **pull**: the\nmerchant takes funds from that wallet. The Delegation authorizes the spend; it does not supply the\nmoney. The two are independent — you can be inside your cap and still have an empty wallet.\n\nSend the payment asset to that address **on the network you intend to pay on**.\n\n**Always re-read the address from the live Delegation. Never reuse a cached one.** Funding a stale\naddress is the most common cause of `402 BCK.ROUTER.0009`, and the error deliberately does not echo\nthe address it checked — so it cannot tell you that is what happened. This one costs more debugging\ntime than anything else in this API.\n\n### Which network and asset\n\nDecided by **what the merchant advertises**, not by which Nevermined environment you point at.\n\n| Rail | Networks | Assets |\n| --- | --- | --- |\n| **x402** | `base` (8453, **mainnet — real funds**), `base-sepolia` (84532, testnet) | `USDC`, `EURC` — 6 decimals |\n| **MPP** | Tempo mainnet (4217), Tempo Moderato testnet (42431) | Whatever the operator allowlisted for that chain |\n\n`base` moves real money. Read `settlement.network` on the response if you want certainty about what\njust happened.\n\n**Only one x402 network is funded per deployment, and which one is fixed by the environment.** It is\nnot a per-deployment toggle and you cannot widen it:\n\n| Your `$NVM_API_URL` | x402 network the Router will fund |\n| --- | --- |\n| `https://api.sandbox.nevermined.app` (sandbox) | `base-sepolia` only |\n| `https://api.live.nevermined.app` (live) | `base` only |\n\nThe permissive \"both networks\" pair survives **only on a local dev deployment**. An operator's\n`ROUTER_FUNDED_NETWORKS` can now only *narrow* that set, never widen it — sandbox/live is the\nreal-money firewall, and an env var must not move a box across it.\n\nSo **a `base` merchant is unpayable from sandbox and a `base-sepolia` merchant is unpayable from\nlive** — in both cases with `400 BCK.ROUTER.0001 … no fundable option`, because every advertised\noption was on an unfunded network. That reads like a broken merchant and is not: it is the wrong\nenvironment for that service. Point at the other `$NVM_API_URL` (with a key issued for it), or pick a\nservice on your environment's network. Do not retry, and do not go looking for a different merchant\non the same chain — it will fail identically.\n\nAmounts are in the asset's smallest unit. For 6-decimal stablecoins:\n\n```\n1_000_000 atomic units = 1 USDC = 100 cents\n    10_000 atomic units = 1 cent\n```\n\nYour cap is in **cents**, so every payment is converted and **rounded up** to the next whole cent\nbefore being checked. A 5,000-unit (half-cent) payment reserves 1 cent — a long loop of sub-cent\ncalls burns a full cent of budget each. `settlement.approxCents` is the **merchant** leg;\n`fee.capChargedCents` on the same response is the total this call **reserved** against the cap,\nincluding Nevermined's routing fee. The reserve is not final — a failed mode-B hop releases the fee\nhalf — so read `amountSpentCents` here for spend to date. See `paying.md`.\n\nMPP additionally requires the payment token to be on the operator's per-chain allowlist\n(`ROUTER_TEMPO_ASSETS_<chainId>`), which is **fail-closed**: unset rejects everything on that chain\nwith `400 BCK.ROUTER.0001`. If MPP fails with that code where x402 works fine, an unconfigured\nallowlist is the first thing to check — the rails are configured independently.\n\n## Preflight checklist\n\nBefore the first payment of a run:\n\n1. `GET /api/v1/delegation/{id}` → `status: \"Active\"`, `remainingBudgetCents` covers what you plan\n   to spend, `expiresAt` is comfortably ahead.\n2. `providerPaymentMethodId` read **from that response**, funded on the target network.\n3. A `requestId` scheme that is stable per purchase (see `paying.md`).\n\nIf any of these fails and you cannot fix it yourself, stop and report. Do not create a second\nDelegation to get around an exhausted one.\n\nFile v0.1.30:references/discovery.md\n\n# Discovery — finding something to buy\n\nThe **Agent Services Catalog** is a Nevermined-curated list of external agent services. Discovery is\n**public, unauthenticated and free**. Send no `Authorization` header; none is required.\n\n| Surface | Use it for |\n| --- | --- |\n| `https://nevermined.app/catalog/ai-catalog.json` | **The default.** Every listed service in one JSON document — fetch once, filter locally |\n| Catalog MCP at `https://mcp.live.nevermined.app/mcp` | Server-side search: `search_services`, `get_service`, `list_categories` |\n| `https://nevermined.app/.well-known/ard.json` | The ARD host document, for registries crawling the Catalog — and per-service health |\n| `https://nevermined.app/catalog/llms.txt` | Plain-text entry point for an agent landing cold |\n| `https://nevermined.app/catalog/services` | Human browsing |\n\n⚠️ **`/api/v1/catalog/services`, `/api/v1/catalog/services/{slug}` and `/api/v1/catalog/categories`\nare not a public integration.** They return `403` on both `api.live` and `api.sandbox`, by design —\nnot an outage, and not something a key fixes. Do not retry them; read the feed.\n\nThe feed lists the **live** Catalog. It is live-only for payment: listed services settle on mainnet,\nand a sandbox deployment funds testnets only.\n\n## The feed\n\n```bash\ncurl -s https://nevermined.app/catalog/ai-catalog.json -o ai-catalog.json\njq '{total, generatedAt}' ai-catalog.json\n```\n\n`{ version, catalog, generatedAt, total, count, services: [ … ] }`. It is cached for five minutes\n(`Cache-Control: max-age=300`), so re-fetching more often buys nothing. There are no query\nparameters and no pagination — `services` is the whole Catalog.\n\n### Fields you will actually use\n\n| Field | Use |\n| --- | --- |\n| `slug` | Stable id. Case-sensitive — how you address the service through the Router |\n| `protocol` | **`x402` or `mpp` = payable through the Router.** See below |\n| `endpoints[]` | `{ path, method, description, priceLabel }`, plus `invokePath`, `requestExample`, `responseFields` on some — see [rule 2](#2-pay-by-slug-and-send-invokepath--path) |\n| `priceLabel` | Human string like `\"$0.001\"`. **Indicative only** — the wire price governs |\n| `network` / `networks` | Display names (`\"Base\"`, `\"Tempo\"`). Not chain ids |\n| `category` | One of the **13 curated values** — see [Categories](#categories) |\n| `subCategory` | Granular label under `category`. **Absent** (no key, not `null`) for the generic top bucket — in JS test `s.subCategory == null`, not `=== null` |\n| `tags[]` | Selection signals |\n| `invokeUrl` | The service's Router URL: `…/api/v1/router/svc/<slug>` |\n| `invoke` | A ready-made Router call: `method`, `router`, `invokeUrl` and the `X-Router-*` headers |\n| `url` | The service's human page in the Catalog |\n\nThe feed deliberately omits health status, long descriptions and the merchant's own URL. For health,\nread the ARD host document (each entry's `nvm:catalog.healthStatus` and `uptime30d` — see\n[below](#the-ard-host-document)); for a request body, use the endpoint's `requestExample` when\npresent — the Catalog holds no body schema otherwise.\n\n### Filter recipes\n\nAll run against the file saved above.\n\n```bash\n# Payable on one rail, in one category\njq '[.services[] | select(.protocol == \"x402\" and .category == \"Search & Research\") | {slug, title, priceLabel}]' ai-catalog.json\n\n# Free text over title + description, case-insensitive\njq --arg q \"crypto\" '[.services[]\n     | select((.title + \" \" + .description) | ascii_downcase | contains($q | ascii_downcase))\n     | {slug, title, protocol, priceLabel}]' ai-catalog.json\n\n# Exact tag\njq '[.services[] | select(.tags | index(\"search\")) | .slug]' ai-catalog.json\n\n# One service by slug, with the subpath to send for each endpoint\njq '.services[] | select(.slug == \"superhighway\")\n     | {slug, protocol, endpoints: [.endpoints[] | {method, path: (.invokePath // .path), priceLabel}]}' ai-catalog.json\n```\n\nA misspelt `category`, `protocol` or slug in a filter returns an **empty result, not an error** —\nthe feed has no validator. So an empty list means \"check the string\" before it means \"nothing to\nbuy\". Take category values from the feed itself (below), never from memory.\n\n## Two rules that cost real money if you get them wrong\n\n### 1. Only `x402` and `mpp` are routable\n\n**The Router cannot pay a `rest`, `a2a` or `other` service.** Its transaction is *read a price quoted\non the wire for this request, sign a payment settling exactly that*. A conventional SaaS API never\nquotes a price for one call — it is billed by a monthly plan and a long-lived key, so at call time\nthere is nothing to pay and no address to pay it to.\n\nMeasured across every `rest`/`other` service in the curated set: **none returns a 402, none emits\nany payment header, none serves a real x402 manifest.** The ones that respond meaningfully return\n`401` or `403` — \"authenticate\", not \"pay\".\n\nCuration already protects you from this: those services are deliberately loaded **unlisted**, and\nthe feed only carries listed ones — so in practice today it holds `x402` and `mpp` only. **Filter on\n`.protocol` anyway.** Listing is a curation decision that can change, and an explicit filter makes\nyour agent's assumption visible instead of load-bearing-and-implicit.\n\n**Routable is not the same as payable on *your* deployment.** The two rails are enabled\nindependently, and the MPP rail is fail-closed: where the operator has not allowlisted a Tempo\npayment token, every `mpp` service is refused with `400 BCK.ROUTER.0001 … not allowlisted`. The\ncatalog lists them regardless — it describes services, not your deployment's configuration — so\nread a `0001` on an MPP service as \"this rail is off here\", not \"this service is broken\". Trying\nanother MPP service will fail identically. See `references/errors.md`.\n\nIf you ever do hold a non-routable entry, do not call `/route` on it — tell the user that service\nneeds its own account.\n\n### 2. Pay by slug, and send `invokePath ?? path`\n\nThe feed carries **no merchant URL**, on purpose. You address a listed service by its `slug` — the\nRouter resolves the upstream server-side, and refuses a raw-URL payment to a cataloged host with\n`409 BCK.ROUTER.0014`. An unknown slug is `404 BCK.CATALOG.0001`.\n\nFor an endpoint, the subpath to send is its **`invokePath` when present, else its `path`**.\n`invokePath: \"\"` is meaningful: the service's base already *is* that endpoint, so the Router must\nappend nothing.\n\n```\nslug=edgar-search\n  endpoints[0] = { path: \"/edgar-search/search\", invokePath: \"\" }   ← send \"\", not the path\n```\n\nSending `path` there double-stacks it (`…/edgar-search/search/edgar-search/search`), which 404s —\nand if the merchant charges before routing, you paid for it. So use a *nullish* fallback, never a\nfalsy one:\n\n```js\nconst subpath = endpoint.invokePath ?? endpoint.path   // NOT `||` — it turns '' back into the path\n```\n\n```python\nsubpath = endpoint[\"path\"] if endpoint.get(\"invokePath\") is None else endpoint[\"invokePath\"]   # NOT `or` — same trap\n```\n\nIn jq, `.invokePath // .path` is already correct: `//` falls through on `null`/`false` only, and\n`\"\"` is truthy there.\n\nThen pay with `POST /api/v1/router/route` and `{ \"slug\": …, \"path\": subpath }` — see `paying.md`.\n\n## Categories\n\n`category` is a **closed set of exactly 13 curated values**. Match them **verbatim**, ampersands and\nspacing included:\n\n- `Data & Enrichment`\n- `Sales & Business Intelligence`\n- `Web Scraping & Automation`\n- `Search & Research`\n- `Crypto & Blockchain`\n- `Finance & Markets`\n- `AI & Media`\n- `Communication & Voice`\n- `Social & Creator`\n- `Identity & Compliance`\n- `Infrastructure & Compute`\n- `Weather`\n- `Travel`\n\nThe obvious guesses are wrong: it is `\"Search & Research\"`, not `\"Search\"`. Do not shorten, split on\n`&`, or invent one. The list is curated by hand and can grow, so read what is in use from the feed:\n\n```bash\njq '.services | group_by(.category)\n     | map({category: .[0].category, count: length, subCategories: (map(.subCategory // empty) | unique)})' ai-catalog.json\n```\n\n`subCategory` is the granular label *under* a category (`\"Browser automation\"`), and unlike\n`category` it is free text. A service with no granular label has **no `subCategory` key** — the\ngeneric top bucket — and so appears in no `subCategories[]` list above.\n\n⚠️ **The feed shows what is *populated*, not what is *legal*.** A valid category with no listed\nservices right now simply does not appear. Treat an absent category as \"nothing to buy there today\",\n**not** as \"that value is invalid\" — the closed set above is the enum; the feed is the inventory.\n\n## Server-side search: the Catalog MCP\n\nFrom API 1.55, server-side selection accepts `filters.require` to pin one exact opaque catalog slug.\nThe Router either selects that service or fails closed with `409 BCK.ROUTER.0031`; it never silently\nsubstitutes another service. Remove `require` only when fallback to the normal ranking is deliberate.\n\nWhen you would rather not filter locally, the Catalog MCP server searches for you. Its read tools are\nfree and need no key; each call is one stateless JSON-RPC POST:\n\n```bash\ncurl -s -H \"Content-Type: application/json\" \\\n     -H \"Accept: application/json, text/event-stream\" \\\n     -X POST https://mcp.live.nevermined.app/mcp \\\n     -d '{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"tools/call\",\"params\":{\"name\":\"search_services\",\"arguments\":{\"query\":\"crypto\",\"protocol\":\"x402\"}}}' \\\n  | jq -r '.result.content[0].text' | jq .\n```\n\n| Tool | Arguments |\n| --- | --- |\n| `search_services` | `query` (always send one), plus optional `category`, `protocol`, `tag` |\n| `get_service` | `slug` — returns the service plus its `requestShape` (each endpoint's `payServiceArgs`) |\n| `list_categories` | none — each category with a `count` and its `subCategories[]` |\n\n- **`search_services` result shape depends on the MCP server version.** From v1.48 it ranks by ARD\n  hybrid relevance (semantic + lexical): `query` is **required**, `page` / `offset` are gone (a\n  `pageSize` replaces them, with no next-page input), and the result is `{ results, pageToken }` —\n  ARD records keyed by an `identifier` URN, whose last segment is the slug\n  (`urn:air:api.live.nevermined.app:service:superhighway` → `superhighway`). Earlier servers return\n  `{ total, page, offset, services }`. So: always send `query`, parse `content[0].text` without\n  assuming `services[]`, and **do not build a pager on it** — an unknown `page` is silently dropped\n  and you would get the first page forever. To walk everything, use the feed.\n- **Errors come back as a tool result with `isError: true`** and a plain-text message, not a\n  JSON-RPC error: an unknown slug in `get_service` reads `… returned 404`; a bad `protocol` fails the\n  input schema with `MCP error -32602: Input validation error …`. Check `isError` before parsing\n  `content[0].text` as JSON.\n\nFull MCP setup, including the paid tools: https://nevermined.ai/docs/products/catalog/mcp\n\n## The ARD host document\n\n```\nGET https://nevermined.app/.well-known/ard.json\n```\n\nA Google **Agentic Resource Discovery (ARD)** document over the same listed services — one entry\neach, with the Router pay-through target and health (`healthStatus`, `uptime30d`) under\n`nvm:catalog`. Public and crawlable by any registry.\n\n```json\n{ \"specVersion\": \"1.0\",\n  \"host\": { \"displayName\": \"Nevermined Agent Services\", \"identifier\": \"did:web:…\" },\n  \"entries\": [ … ] }\n```\n\nNote the key is **`entries`**, not `services` — a parser looking for `services` sees an empty\ndocument and silently concludes the catalog is empty. It also carries one entry that is the registry\nitself, not a service.\n\nPrefer `ai-catalog.json` when you are choosing something to buy: it carries the endpoints and the\nready-made `invoke` block in a flat shape. The ARD document is for registries crawling the Catalog.\n\n## Choosing well\n\n1. Fetch `ai-catalog.json` once and filter to `protocol` `x402` or `mpp`.\n2. Narrow with free text (title + description), or `category` / `subCategory` / `tags` for\n   precision — taking category values from the feed, never from memory. Or let the Catalog MCP's\n   `search_services` do it.\n3. Read `endpoints[]` — pick the one whose `description` and `method` match your need, and note its\n   `priceLabel`.\n4. Take the `slug` and the endpoint's `invokePath ?? path`, per rule 2.\n5. Pay with `POST /api/v1/router/route` — see `paying.md`.\n\nIf nothing matches, say so. Do not fall back to a `rest` entry and do not guess a merchant URL.\n\nFile v0.1.30:references/errors.md\n\n# Errors and guardrails\n\nThe Router signs payments from your wallet in response to instructions written by a merchant nobody\nvetted. It is deliberately suspicious.\n\n**A refusal is the system working.** Before you widen a cap or drop an idempotency key to make an\nerror go away, read what it was protecting you from. An autonomous agent that treats guardrails as\nobstacles is exactly the failure mode this design exists to prevent.\n\n## Every Router code\n\nCodes `0029` and `0031`–`0033` are exposed from API 1.55 onward; older API pins retain the legacy\nunbound quote and service-selection request shapes.\n\n| Code | Status | Meaning | Retry? |\n| --- | --- | --- | --- |\n| `BCK.ROUTER.0001` | 400 | Bad input: unsupported protocol, malformed/empty challenge, no fundable option, recipient outside the Delegation's scope, non-allowlisted asset, wrong-provider Delegation, missing `delegationId`. **`details` names the specific problem — read it** | No |\n| `BCK.ROUTER.0002` | 409 | This `requestId` already minted a payment. The original `paymentId` is in the response | No |\n| `BCK.ROUTER.0003` | 402 | Delegation over cap, expired, exhausted, or revoked | No — **stop** |\n| `BCK.ROUTER.0004` | 404 | No Router payment with that id belongs to you | No |\n| `BCK.ROUTER.0005` | 409 | Payment not settleable. Only `Issued` → `Settled`; same hash is a no-op, a different hash is rejected | No |\n| `BCK.ROUTER.0006` | 500 | Transient failure building the payments summary | **Yes** |\n| `BCK.ROUTER.0007` | 429 | Too many concurrent routed requests in flight | **Yes**, after backoff |\n| `BCK.ROUTER.0008` | 403 | Legacy API key — create a new one | No |\n| `BCK.ROUTER.0009` | 402 | Wallet doesn't hold enough of the asset on the target network. **Nothing was signed** | No — **stop** |\n| `BCK.ROUTER.0010` | 500 | Internal: the rail reported a charge amount that isn't a non-negative integer, so the Router can't reserve anything against the cap | No — **never blind-retry** |\n| `BCK.ROUTER.0011` | 402 | Card rail: the charge needs cardholder 3-D Secure, and an agent has no browser to complete it. Nothing was charged and the seller got no usable credential. | No — **needs a human** |\n| `BCK.ROUTER.0012` | 400 | The seller's 402 advertises an EIP-712 domain its own settlement token does not sign under, so the Router refuses to sign. Nothing signed, charged or reserved — an authorization under the wrong domain is unspendable anyway. Seller-side bug | No — **report it, pay elsewhere** |\n| `BCK.ROUTER.0013` | 500 | Nevermined holds no EIP-712 signing domain for the token the funding filter selected — a gap in OUR canonical table, not the seller's bug and not your request. Nothing signed, charged or reserved | No — **report it to Nevermined** |\n| `BCK.ROUTER.0014` | 409 | The target is a cataloged Nevermined service, whose upstream URL is deliberately hidden. The Router refuses to pay it by raw URL — mode A and a raw mode-B target both put the merchant's host on your wire, defeating the broker. The match is by HOST, so a co-hosted endpoint that is not itself listed is refused too — ask the vendor to list it, or contact Nevermined; hosts with no cataloged service are unaffected. | No — **use the slug**: `POST /router/route` with a `slug`, or `POST /router/svc/<catalog-slug>` (a `commerce` grant: `POST /router/commerce/route` with a `slug`). A refused **quote** is re-quoted by slug — `POST /router/quote`, or `/router/commerce/quote` for a grant — never paid |\n| `BCK.ROUTER.0018` | 402 | Per-call `maxTotalCents` is below the fee-inclusive, whole-cent cap reserve. No charge or cap reserve, though signing may already have occurred; parse JSON-string `params` for `requiredTotalCents`. | No — raise the ceiling only if this call is intended; reuse the same `requestId` |\n| `BCK.ROUTER.0019` | 400 | Streaming surfaces only (`/proxy` · `/svc`; `/route` returns the envelope status with `body: null`). A cataloged service returned a **non-retryable** status — a 4xx client error, or a rare 3xx the Router does not follow (a 402 re-challenge and a 429 are **not** this code). The upstream body is withheld (it can name the merchant host); this typed body preserves the **real** upstream status (on the HTTP status line and in JSON-string `params`). Build a valid request from the service's Catalog detail (`requestExample` / `responseFields`). | No — **fix the request first**, then retry with a fresh `requestId` |\n| `BCK.ROUTER.0020` | 502 | Streaming surfaces only (`/proxy` · `/svc`; `/route` returns the envelope status with `body: null`). A cataloged service returned a server error (5xx) or rate-limited (429) — an upstream/transient condition, not your request. Body **and** headers are withheld (host oracle, including `Retry-After`); this typed body preserves the **real** status. The Router charges **no routing fee** for an undelivered call; whether the merchant leg itself charged is reported as `merchantSettlementObservedAt` (x402 only — `null` on a clean settlement and on both MPP rails, so `null` is not proof of no charge; read alongside `status`) on `GET /api/v1/router/payments`. | **Yes**, with backoff — reuse the same `requestId` only if no `X-Router-Payment-Id` came back; if one did, a payment is already recorded, so use a NEW id and reconcile via `GET /router/payments` |\n| `BCK.ROUTER.0021` | 400 | The rail this service advertised carries its payment credential in a header you are **already using**. On the MPP rails that header is `Authorization`, which is also where your own merchant auth goes (`headers.Authorization` on `/route`, `X-Router-Upstream-Authorization` on `/proxy` · `/svc`). Rather than silently dropping yours on the paid hop, the Router refuses: **nothing was minted, no cap was reserved and no money moved**. JSON-string `params` names the contested header. | No — **name the header the service documents** for its credential: `credentialHeader` in the `/route` body, or the `X-Router-Credential-Header` request header on `/proxy` · `/svc` (a separate `Payment` header is the common one). If the service documents none, it wants the credential in `Authorization` itself and cannot also take your bearer there: drop your own auth for that call, or pay it over an x402 endpoint (whose credential travels in `PAYMENT-SIGNATURE`). Retrying unchanged fails identically |\n| `BCK.ROUTER.0022` | 500 | Server-side service selection is not wired on this deployment (a configuration fault, not your request) — nothing was ranked or charged. Only reachable on a misconfigured deployment; never in a healthy environment. | **Yes**, later — it is a transient/config condition on our side; if it persists, quote `correlationId` when reporting it |\n| `BCK.ROUTER.0023` | 502 | On an autoPay `POST /router/select` (or `/router/commerce/select`): the chosen service was paid but the downstream call did not complete cleanly **after** the payment was created, so the charge outcome is **indeterminate** — the merchant leg may or may not have settled. JSON-string `params` carries the `paymentId` when one was created. | No — do **not** retry with a fresh `requestId` (that could double-charge). Reconcile via `GET /api/v1/router/payments`, then reuse the **same** `requestId` to retry safely |\n| `BCK.ROUTER.0024` | 413 | The request body exceeds the Router's size limit (about 5 MB). | No — reduce the request body before trying again |\n| `BCK.ROUTER.0025` | 502 | The upstream reply was too large to deliver after a paid request. The payment outcome is indeterminate; it may have gone through. | No — this reply has no `X-Router-Payment-Id`; JSON-string `params` may carry `paymentId`. If absent, call `GET /api/v1/router/payments` with `delegationId` and `from` just before the call, then match `requestId` in the returned rows (newest 1000 maximum; there is no `requestId` filter). The row reads `status: Failed` because delivery failed, not because the merchant was uncharged. A non-null `merchantSettlementObservedAt` confirms x402 settlement; null does not prove no charge, including on MPP rails. Do not retry: the same `requestId` returns 409 `BCK.ROUTER.0002` with the original `paymentId` and cannot re-deliver the reply; a fresh id risks another charge. |\n| `BCK.ROUTER.0026` | 415 | The Router cannot forward this request body. The streaming surfaces (`/router/svc/:slug`, `/router/proxy`) forward only JSON (`application/json`) or URL-encoded (`application/x-www-form-urlencoded`) bodies; any other type — `multipart/form-data` above all, but also `text/plain`, `application/octet-stream` or a vendor `+json` — and any body on GET/HEAD is refused. No payment was minted and no money moved. JSON-string `params` names the refused `contentType` (null when none was sent). | No — resend the body as JSON or a URL-encoded form the service accepts; a service that only takes a file upload cannot be paid through the Router yet, and retrying unchanged fails identically |\n| `BCK.ROUTER.0027` | 413 | The request body is larger than the catalog endpoint accepts. The catalog records a `maxRequestBytes` per endpoint (on the service detail and in MCP `get_service`) — the Locus gateways (`*.mpp.paywithlocus.com`) take 8,000 bytes — and a slug-routed call (`/router/route`, `/router/quote`, `/router/svc/:slug`, `/router/proxy` with a slug) whose body is larger is refused before the service is contacted. No payment was minted and no money moved. JSON-string `params` carries `bodyBytes` and `maxRequestBytes`. | No — shrink the body below the limit or split the work, or pick a service that takes larger requests (`POST /router/select` with the same `body` skips endpoints whose limit is below it); retrying unchanged fails identically |\n| `BCK.ROUTER.0028` | 503 | `POST /router/quote` could not price the call because a read it depends on failed (for example the settlement-token details on the payment network). Nothing is signed, minted or charged on the quote path. | **Yes**, with backoff — a quote never charges, so nothing needs unwinding. The same condition would also fail a payment, so do not route the call meanwhile; if it persists, quote `correlationId` |\n| `BCK.ROUTER.0029` | 404 | The `quoteId` is invalid or belongs to another account. Nothing was signed, reserved or charged. | No — request a new quote and use its `quoteId`; do not retry the same invalid id |\n| `BCK.ROUTER.0030` | 404 | No retained paid result for that paymentId under your account. A paid Router result is retained for 24h after the call ends, for the paying user only; it is not retained for a response that had already started streaming when the call completed, or for a payment never routed through `/router/route`, `/router/proxy` or `/router/svc`. The payment record itself is unaffected. | No — the result is gone (or was never retained); read the payment with `GET /api/v1/router/payments` |\n| `BCK.ROUTER.0031` | 409 | The exact catalog slug in `filters.require` is unavailable, unhealthy, unpayable, excluded by this request, or cannot accept the body. The Router fails closed instead of substituting another service. | No — inspect `params.reason`; correct the slug or request, or deliberately remove `require` to allow fallback |\n| `BCK.ROUTER.0032` | 410 | The `quoteId` expired before payment began. Nothing was signed, reserved or charged. | No — quote the same call again and decide against the new fee-inclusive total; do not retry the expired id |\n| `BCK.ROUTER.0033` | 409 | The payment differs from the quote in its target, method, headers, body, credential header, delegation, rail, or exact fee-inclusive amount. Nothing was reserved or charged. | No — send the quoted call unchanged, or request a new quote for the changed call |\n| `BCK.ROUTER.0034` | 502 | A paid request got no response from the service (timeout or connection failure) after the credential was sent — the service may already have redeemed the payment. `params` carries the `paymentId`. | No — reconcile via `GET /api/v1/router/payments/{paymentId}`; never retry with a fresh `requestId` (it could pay twice) |\n| `BCK.ROUTER.0035` | 422 | The service's challenge asks for no payment (a zero amount — typically an auth-only \"sign in with your wallet\" challenge). Your request is not malformed; nothing was signed, reserved or charged. | No — use a service that charges for the call, or authenticate with the service directly |\n| `BCK.ROUTER.0036` | 422 | Every catalog endpoint the call reaches is known to answer after the Router abandons a paid call (120 s), and the merchant charges on receipt — so paying would charge and deliver nothing. Refused before any payment (a retry of an already-paid `requestId` gets `0002` instead). `params.source` is `declared` (permanent) or `observed` (lifts at `params.liftsAt`). On a declared free-follow-up path it is raised only when the service answers `402`. | No — pick another endpoint or service; an `observed` refusal lifts at `liftsAt` |\n\n**Only `0006`, `0007`, `0020`, `0022` and `0028` are worth retrying automatically** — on the paying path,\n`0007` and `0020`. The rest are decisions; retrying them unchanged produces the same answer.\n\n### Refusals that are not `BCK.ROUTER.*` at all\n\nThey guard the *caller* rather than the request, and they can end a run before a single payment is\nattempted — so handle them even though none carries a `BCK.ROUTER.*` code.\n\n| | Code | Status | Applies to | Retry? |\n| --- | --- | --- | --- | --- |\n| **OAuth-minted key** | `BCK.OAUTH.0030` | 403 | `POST /delegation/create`, `POST /router/payments`, `POST /router/route`, `POST /router/route/with-controls`, `POST /router/quote`, `POST /router/select`, `ALL /router/proxy`, `ALL /router/svc/<slug>` | No |\n| **Not a commerce grant** | `BCK.OAUTH.0033` | 403 | `POST /router/commerce/route`, `/commerce/route/with-controls`, `/commerce/select`, `/commerce/quote` | No |\n| **`delegationId` on a commerce route** | `BCK.OAUTH.0034` | 400 | The same four commerce routes | No |\n| **Consent lapsed** | `BCK.HTTP.412` (generic — see below) | 412 | Account-wide; `POST /delegation/create` is the one on this path | No |\n\n**`403 BCK.OAUTH.0030`** — the key was minted through an OAuth consent ceremony (today\n`credits_purchase`, `account_access` or `commerce`; the guard keys on the binding, not the consent\ntype, so a future ceremony type is refused too) and may not touch the Router spend rails or create\nDelegations: those routes sign from the account's full wallet, outside the narrow session-key policy\nsuch a credential advertises. For a `credits_purchase` or `account_access` key the fix is a **plain\nAPI key issued by the account owner** — no request change and no other Router endpoint will work\naround it, do not retry. A **`commerce`** key is the one exception: it spends through\n`POST /api/v1/router/commerce/route` and prices a call without paying through\n`POST /api/v1/router/commerce/quote`. Both take no `delegationId` and derive the Delegation from\nthe grant the user approved.\n\n**`403 BCK.OAUTH.0033`** — the reverse door. The commerce routes under `POST /api/v1/router` —\n`/commerce/route`, `/commerce/route/with-controls`, `/commerce/select` and `/commerce/quote` — accept\nonly a credential minted from a `commerce` authorization, because they spend or price the Delegation\nthat grant is pinned to. A plain API key, or a credential from any other consent type, is refused:\nuse the twin that names its own Delegation instead — `POST /api/v1/router/route` to pay,\n`POST /api/v1/router/select` to pick a service, `POST /api/v1/router/quote` to price a call. Those\ntwins take a **plain** API key only: a `credits_purchase` or `account_access` credential is refused\nthere too (`403 BCK.OAUTH.0030`), and its fix is a plain key from the account owner. A\n`commerce` credential that still sees it has a grant whose Delegation is missing or empty, or is bound\nto a different one: re-run the authorization to mint a fresh mandate. Not retryable unchanged.\n\n**`400 BCK.OAUTH.0034`** — `delegationId must not be supplied on a commerce route`. On those same four\nroutes the Delegation is the one the user consented to and capped, and naming another is refused\nrather than ignored. Remove `delegationId` from the body; to choose a Delegation per call, use the\nplain-key twins above.\n\n<a id=\"consent-412\"></a>\n**`412 {\"error\":\"consent_required\",\"outdated\":[…]}`** — the account's legal-document consent has\nlapsed, and `POST /delegation/create` is blocked until a human accepts. ⚠️ **`code` alone will not\nidentify it.** It is deliberately not an `NVMException`, so it has no `BCK.LEGAL_DOCS.…` code of its\nown — the error filter stamps the generic **`BCK.HTTP.412`**, which restates the status and says\nnothing about the cause. Branch on **`body.error === \"consent_required\"`**; `body.outdated[]` names\nthe document slugs (`terms`, `privacy`). Report it and stop: an endpoint to accept exists and takes\nyour API key, but **accepting terms is the account holder agreeing to be bound by them — consent you\nhave no standing to give on their behalf.** Details in `bootstrap.md`.\n\nNote the same normalisation applies to any other bare `HttpException` you might hit (a\n`ValidationPipe` 400, for instance): the envelope is there, but `code` reads `BCK.HTTP.<status>`\nrather than a catalogued `BCK.ROUTER.*`. **A `BCK.HTTP.*` code means \"no catalogued code for this\" —\nlook at the rest of the body.**\n\n### `0011` — the 402 that needs a human, not a retry\n\nCard rail only. The issuer demands **3-D Secure / SCA** before the charge can be used, and the Router\nhas no human at a browser to complete it. **Nothing was charged, and the seller never received a\nusable credential** — the one Stripe created cannot be charged while it awaits authentication.\n\nDo **not** auto-retry. 3DS is often mandated per charge by industry rules, so every attempt\nre-demands it and mints another single-use card credential that is then abandoned — each expiring on\nits own at `min(the merchant's quoted expiry, your Delegation's expiry, 89 days)`. A later attempt\n*may* succeed, since whether authentication is demanded is decided per charge by the issuer, the card\nnetworks and Stripe's risk checks — but treat that as a human decision, not a loop.\n\nIt is distinct from both other 402s: `0003` is your cap, `0009` is a card refused for lack of funds.\nHere the card is fine; it simply has not been authenticated for this charge.\n\n### `0010` — the 500 you must not retry\n\n`0006` and `0010` are both 500s and behave in opposite ways, so \"retry 5xx\" is the wrong reflex\nhere. Note also that `0006` is raised **only by the payments summary read**, never by a payment —\nso on the paying path, `0007` and `0020` are the codes worth retrying (with backoff).\n\n`0010` means a payment handler reported a settlement amount in cents that isn't a non-negative\ninteger, so the routing-fee arithmetic can't compute what to reserve. It deliberately fails rather\nthan defaulting to zero — reserving nothing would let the payment through free.\n\nWhat that leaves behind is the important part:\n\n- **No budget was reserved and no payment record was written.**\n- **But a payment credential WAS already minted**, because the fee is quoted after the signing step.\n- **Therefore your `requestId` cannot protect you.** Idempotency is enforced against the payment\n  record, and there is no record — so a retry is treated as a brand-new purchase and mints a\n  **fresh** credential.\n- The cause is a deterministic defect in that rail's `approxCents` derivation, not a transient\n  blip, so the retry fails in exactly the same way.\n\n**How much that actually costs you depends on the rail.** On the crypto rails the credential never\nleaves the Router process on this path, so no funds can move and nothing is at risk. On the card\nrail it is a Stripe Shared Payment Token that is left **stranded**: there is no revoke path, so it\nstands until `min(the merchant challenge's expiry, your Delegation's expiry, 89 days)`. You cannot\nclean it up from the outside.\n\n**Seeing `0010` at all is a Nevermined-side regression.** No rail emits a non-numeric amount today,\nso this is a bug to report, not a condition to handle. Report it to the human. Do not loop.\n\nCatalog codes: `BCK.CATALOG.0001` (404, unknown slug — case-sensitive), `BCK.CATALOG.0002` (500,\ntransient, retryable).\n\n## The four rules for an autonomous buyer\n\n**1. `0003` and `0009` are stop conditions.** \"Out of budget\" and \"out of money\". Report them to the\nhuman and halt that line of work. They are not transient and they are not negotiable.\n\n**2. Never widen a Delegation, and never create a second one, to escape a refusal.** The cap is the\nuser's decision; the refusal is that decision taking effect. Minting a fresh Delegation to get past\nan exhausted one defeats the entire mechanism — it is the single worst thing you can do with this\nAPI. If more budget is genuinely warranted, that is a question for the human, not a step in your\nretry loop.\n\n**3. One `requestId` per logical purchase**, reused across retries of that purchase. A fresh UUID\nper HTTP attempt is how an agent double-spends.\n\n**4. Check what you actually spent.** Per call, `fee.capChargedCents` — **not**\n`settlement.approxCents`, which is only the merchant leg and excludes Nevermined's routing fee. For\nspend to date, `GET /api/v1/delegation/{id}` → `amountSpentCents`, because `capChargedCents` is the\nreserve at mint and a failed mode-B hop gives the fee half back. Budget is debited in whole cents\n**rounded up**, so a long loop of sub-cent calls burns a cent each — the arithmetic that says \"1000\ncalls at $0.001 = $1.00\" is wrong here; it is $10.00. See `paying.md` for the `fee` object.\n\n## Distinguishing the two 402s\n\nThey look alike and mean opposite things:\n\n| | `BCK.ROUTER.0003` | `BCK.ROUTER.0009` |\n| --- | --- | --- |\n| **What failed** | The *authorization* — cap, expiry, status | The *funds* — wallet balance |\n| **Fix** | A human decides whether to raise the budget | Fund the wallet on the target network |\n| **Check with** | `GET /api/v1/delegation/{id}` → `remainingBudgetCents`, `expiresAt`, `status` | Wallet balance at `providerPaymentMethodId` on that chain |\n\nThey are independent: you can be well inside your cap with an empty wallet, or hold plenty of USDC\nagainst an expired Delegation.\n\n**`0009` does not tell you which address it checked.** That is deliberate, and it means a stale\ncached address looks identical to an unfunded one. Always re-read `providerPaymentMethodId` from the\nlive Delegation before concluding anything.\n\n**Delegations expire silently.** An agent that worked yesterday and fails today with `0003` has very\noften just aged out. Check `expiresAt` first; it looks exactly like a broken rail until you do.\n\n## Reading a `BCK.ROUTER.0001`\n\nIt is the catch-all for \"the Router will not pay this\", and the **`details`** field names which\ncheck tripped. Common causes, in rough order:\n\n- **No fundable option in the 402.** Every advertised option was on an unfunded network, in an\n  unsupported asset, or used a scheme other than `exact`. A mixed-chain 402 is fine as long as *one*\n  option survives — this only fires when none does. **Check the environment first:** a deployment\n  funds exactly one x402 network — sandbox `base-sepolia`, live `base` — so a `base` merchant is\n  simply unpayable from sandbox and vice versa. That is the single most common cause here, and it\n  looks like a broken merchant. See `bootstrap.md`.\n- **Non-allowlisted MPP asset.** Fail-closed per chain. If MPP fails with `0001` where x402 works,\n  check this first — the rails are configured independently. **On a deployment where the MPP rail\n  is simply not enabled this is the expected result for _every_ MPP service**, whatever the\n  service. It says nothing about the merchant, and no amount of trying other MPP services will\n  find one that works. Switch to `protocol=x402`, or ask the operator to enable the rail.\n- **Recipient outside the Delegation's scope**, when it carries an `allowedRecipients` list.\n- **Wrong Delegation provider** — a card Delegation on a stablecoin rail or vice versa.\n- **MPP `splits`** — see below.\n- **Missing `delegationId`**, or a missing `X-Router-*` header on `/proxy`.\n\nRetrying does not help. Either fix the input or pick a different service.\n\n## What the Router refuses outright\n\n### Splits\n\nAn MPP `charge` can name a primary recipient *and* extra payout recipients. Only the primary is ever\nvalidated against your Delegation, so honouring splits would move real funds to addresses nobody\nchecked. **Any split-bearing challenge is rejected outright** — unconditionally, whether or not your\nDelegation restricts recipients. The Router refuses the whole thing rather than paying the part it\ncan vouch for.\n\n### Internal targets\n\nThe Router makes server-side requests to URLs you supply, so it will not be pointed at\ninfrastructure you should not reach. Loopback, private (RFC 1918), link-local and cloud-metadata\naddresses are blocked — **both literal IPs and public hostnames that resolve to internal\naddresses**, so DNS rebinding does not get around it. The connection is then pinned to the address\nthat was validated, so it cannot be swapped underneath.\n\nOperators can lift this for local development with `ROUTER_ALLOW_PRIVATE_TARGETS=true`. It should\nnever be on in a shared environment.\n\n### Redirects\n\n**Not followed at all**, and the `location` header is stripped from the relayed response. A merchant\ncannot bounce the Router toward an internal target, and cannot hand your client one either. If you\nneed the redirect target, resolve it yourself and route the final URL.\n\n### Forged payment signals\n\n`X-Router-*` headers are stripped in both directions, so an upstream cannot fabricate a payment\nheader that makes a free response look paid.\n\n### Signed-vs-approved divergence\n\nAfter signing an MPP credential the Router decodes what it actually produced and compares it against\nthe challenge it validated. On any divergence the credential is discarded and never leaves the\nprocess — a merchant cannot get one thing approved and a different thing signed.\n\n## Relay limits (mode B)\n\n| Limit | Default | Env var |\n| --- | --- | --- |\n| Concurrent routed requests per user | 10 | `ROUTER_MAX_CONCURRENT_PER_USER` |\n| Idle time on a streamed response | 30s | `ROUTER_STREAM_IDLE_MS` |\n| Total time on a streamed response | 5 min | `ROUTER_STREAM_MAX_MS` |\n| Relayed body size | 100 MB | `ROUTER_MAX_RELAY_BYTES` |\n\nExceeding concurrency gives `429 BCK.ROUTER.0007`, which **is** retryable — let calls finish and\nback off. Do not respond by fanning out harder.\n\nThe idle timer is re-armed by your client draining the response, so a slow-but-healthy large\ntransfer will not trip it. An abandoned stream will be, and holds a concurrency slot until it is.\n\n## Error envelope\n\nErrors carry a structured body — branch on `code`, not on message text:\n\n```json\n{ \"code\": \"BCK.ROUTER.0003\", \"category\": \"business\", \"httpStatus\": 402,\n  \"message\": \"Delegation budget exceeded, expired, or inactive\",\n  \"hint\": \"…\", \"correlationId\": \"…\" }\n```\n\n`hint` is written for a human reading a log. `details`, when present, names the specific check that\ntripped — that is the field worth logging on a `0001`.\n\n`params`, when present, is a JSON **string** on the wire: parse it with `JSON.parse` before reading\n`requiredTotalCents`. For `0018`, `requiredTotalCents`, `merchantCents`, `feeCents` and the echoed\n`maxTotalCents` are decimal strings. A refused quote can follow credential signing: the card rail\nmay mint an expiring SPT, and Tempo may consume a custodial signature. Do not use `0018` for free\nprice discovery.\n\n**One documented exception to that rule:** the [`412 consent_required`](#consent-412) on\n`POST /delegation/create` carries only the generic `BCK.HTTP.412`, so `code` identifies the status\nbut not the cause — `body.error` does. Keep `code` as your primary branch, and treat any\n`BCK.HTTP.*` as \"uncatalogued, read the body\".\n\nError responses always reflect the **current** API shape; they are not version-pinned. Treat them as\nlatest-shape diagnostics and tolerate the code set growing over time.\n\nFile v0.1.30:references/ledger.md\n\n# Ledger — what you actually spent\n\nEvery Router payment, across every merchant, protocol and Delegation, lands on one record. This is\nhow an agent audits its own spending, and how a human audits the agent's.\n\n## List payments\n\n```bash\ncurl -s \"$NVM_API_URL/api/v1/router/payments?delegationId=$NVM_DELEGATION_ID\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\"\n```\n\n| Param | Notes |\n| --- | --- |\n| `delegationId` | Only spend against this Delegation |\n| `from` / `to` | ISO-8601, **inclusive** on `createdAt`. Invalid → `400 BCK.ROUTER.0001` |\n| `format` | `json` (default) or `csv` — a downloadable `router-payments.csv` |\n\nReturns **newest first, capped at 1000 rows**. The cap is silent: 1000 rows back does not mean there\nwere exactly 1000. Narrow with `from`/`to` and page through by time, or use the summary endpoint for\ntotals.\n\n### A record\n\n```json\n{\n  \"id\": \"b1f9c2e4-…\",\n  \"createdAt\": \"2026-07-01T09:00:55.605Z\",\n  \"status\": \"Settled\",\n  \"protocol\": \"x402\",\n  \"network\": \"base\",\n  \"asset\": \"USDC\",\n  \"amount\": \"1000\",\n  \"merchantAddress\": \"0x209693Bc6afc0C5328bA36FaF03C514EF312287C\",\n  \"txHash\": \"0xfc8af37b…\",\n  \"delegationId\": \"5e7481c3-…\",\n  \"requestId\": \"search-nevermined-router-v1\",\n  \"resourceUrl\": \"https://agent.example/resource\",\n  \"buyer\": \"0x8D6A5233…\",\n  \"feeAtomic\": \"0\",\n  \"feeBps\": 0,\n  \"feeCents\": \"0\",\n  \"feeStatus\": \"None\",\n  \"feeTxHash\": null,\n  \"feeNonce\": null,\n  \"feeFailureReason\": null,\n  \"assetSymbol\": \"USDC\",\n  \"assetDecimals\": 6\n}\n```\n\n⚠️ **This sample is from a deployment with no routing fee configured** — that is what `feeBps: 0` /\n`feeStatus: \"None\"` mean here, and it is the shipped default. Do not read it as \"an x402 payment\ncarries no fee\": where a rate **is** configured, x402 payments carry one, mode A and mode B alike.\nThe one exception with a rate set is a fee that rounds below a single atomic unit — it is dropped\nrather than reserved, and that row also reads `feeAtomic: \"0\"` / `feeStatus: \"None\"`.\nSee [`paying.md`](./paying.md#mode-a-fee).\n\n**`feeFailureReason` tells those apart.** `null` on this sample means no rate is configured. A `None`\nrow produced while a rate WAS configured carries `fee-not-quoted: <why>` instead — no destination for\nthe settlement chain, no usable fee submitter, a rail that cannot carry a fee leg, or an amount too\nsmall to transfer.\n\n| Field | Notes |\n| --- | --- |\n| `amount` | The settlement asset's **smallest unit** — but **the scale differs per rail**, so read `assetDecimals` and never assume one. **Merchant leg only**; the fee is not in it |\n| `asset` | As persisted at mint, and it differs per rail: a symbol for x402, the token **contract address** for MPP-tempo, an ISO currency code (`usd`/`eur`) for MPP-stripe. Prefer the two fields below for display |\n| `assetSymbol` | Ticker to render for `asset`. **A value here is not proof we recognised the asset** — see below |\n| `assetDecimals` | Decimal scale of `amount` — **this is the recognition signal.** `null` means \"we do not know the scale\": show raw units, never a guess |\n| `merchantAddress` | Pay-to identifier: a `0x` address on the crypto rails, or a `profile_…` processor id on the card rail |\n| `buyer` | Payer identity: the on-chain EOA (`0x…`) on crypto rails, or a `cus_…` customer id on the card rail |\n| `txHash` | Settlement reference for the **merchant** leg. **Not always a `0x` hash** — see reconciliation below |\n| `requestId` | Your idempotency key, echoed. The join key back to your own records |\n\n### Scale: read `assetDecimals`, never assume 6\n\n⚠️ **On the card rail, `amount` IS cents.** MPP-stripe settles in ISO currencies at **scale 2**, so a\n$60.00 card payment is `amount: \"6000\"`. On x402 and MPP-tempo the stablecoins are **6-decimal**, so\n`1000` = 0.001 USDC. One rule covers both:\n\n```\ndisplayed = Number(amount) / 10 ** assetDecimals      // and if assetDecimals is null, don't\n```\n\nDividing a card row by 10⁶ because \"the ledger is in atomic units\" turns $60.00 into `0.00006` — a\nplausible-looking **wrong number** that lands in a spend total, which is far worse than a visibly\nmissing one. That is why the scale is on the row. A card row looks like this:\n\n```json\n{ \"protocol\": \"mpp\", \"network\": \"stripe\", \"asset\": \"usd\", \"amount\": \"6000\",\n  \"assetSymbol\": \"usd\", \"assetDecimals\": 2,\n  \"merchantAddress\": \"profile_1S…\", \"buyer\": \"cus_T…\" }\n```\n\n`network: \"stripe\"` names a rail, not a chain.\n\n`assetDecimals` is `null` whenever the asset is not one we can scale — an unrecognised token on that\nrow's chain, or a card-rail currency outside the two the mint path admits. Treat it as \"cannot render\nan amount\": show the raw `amount` and the truncated `asset`, and do **not** fall back to 6.\n\n⚠️ **Guard the `null` explicitly.** `Number(amount) / 10 ** null` silently returns the raw atomic\namount as a number — so an unguarded divide does not fail loudly and can put wrong units in a total.\n\n### `assetSymbol` is not a recognition check\n\nIt is `null` on only **two** of the four resolution paths — an empty `asset`, and a hex address that\nis not in that chain's table. On the other two — the **card rail**, and an `asset` already stored as\na symbol — it **echoes the persisted value whether or not we recognise it**. So a non-null\n`assetSymbol` tells you nothing about whether the row is understood. **Branch on `assetDecimals`.**\n\nThe tickers we resolve are `USDC` and `EURC` (Base, Base Sepolia), `USDC.e`, `EURC.e` and\n`pathUSD` (Tempo), plus whatever ISO code the card rail persisted.\n\n⚠️ **`pathUSD` is spelled with a different capital per chain** — `pathUSD` on Tempo mainnet, `PathUSD`\non Tempo Moderato — because each chain's token reports its own casing. **Compare tickers\ncase-insensitively.** Matching one spelling exactly misses every row on the other chain, and the\nsymptom is a silently short total rather than an error.\n\n### `amount` is not the cap charge\n\nThe cap is in cents; `amount` is the merchant leg in the settlement asset's units. The combined cap\ncharge is not derivable from this resource at all: that is `fee.capChargedCents` on the original\npayment response (see `paying.md`), or `amountCents` on\n`GET /api/v1/delegation/{id}/transactions` — the ledger does not repeat either.\n\nNeither `merchantAddress` nor `buyer` is safely a blockchain address: branch on `network` before\nrendering either as an explorer link.\n\n### The fee columns\n\nNevermined's routing fee is recorded on every row, on both the JSON rows and the CSV export.\n\n| Field | Notes |\n| --- | --- |\n| `feeAtomic` | The fee in the asset's **smallest unit**, like `amount`. `\"0\"` when no fee applied; `null` on rows predating the fee |\n| `feeBps` | Rate applied, in basis points over 10,000 (`200` = 2%). `null` on rows predating the fee |\n| `feeCents` | Cents the fee added to the cap reserve. `null` on rows predating the fee |\n| `feeStatus` | The fee leg's own lifecycle — **not** the payment `status`. `null` on rows predating the fee, so a switch over the six values below needs a `null` branch. See below |\n| `feeTxHash` | Settlement reference for the **fee** leg, distinct from `txHash`. Reported verbatim by the facilitator, so third-party text, not a validated `0x` shape. Often `null` — including on some `Settled` rows |\n| `feeNonce` | The fee leg's EIP-3009 nonce, recorded on submission. The audit key that ties an on-chain transfer back to this payment. `null` until submitted. Not spendable on its own |\n| `feeFailureReason` | Why the fee did not collect — or, on a `None` row, why one was never quoted (`fee-not-quoted: …`). Human-facing diagnostic, **not a stable contract**; branch on `feeStatus`. Whitespace is collapsed and any embedded `scheme://…` URL is published as `[url]`. `null` when no rate is configured, and while a quoted fee is still on its happy path. A non-null value does **not** on its own mean the fee went uncollected — a `Settled` row can carry one |\n\n⚠️ **`feeStatus` is a separate lifecycle from the payment `status`, and `Settled` / `Failed` appear\nin both.** A row can read `status: \"Settled\"` with `feeStatus: \"Accrued\"` — the merchant was paid and\nthe fee has not moved yet. Never read one as the other, and never infer the payment state from\n`feeStatus`.\n\n| `feeStatus` | Meaning |\n| --- | --- |\n| `None` | No fee applied to this payment |\n| `Accrued` | Fee owed and reserved against your cap; nothing submitted anywhere, so nothing can have moved |\n| `Submitted` | Handed to the facilitator, outcome not yet known. A fee sits here for the whole round trip; **not** terminal |\n| `Settled` | The fee leg landed on-chain |\n| `Failed` | Collection provably did not happen. Terminal as an *outcome* — retrying changes nothing — but the row may still advance to `Released` |\n| `Released` | The fee's cap reserve was **given back**, because collection reached a terminal not-collected state. Terminal |\n\n⚠️ **`Failed` does not imply `Released`, and a non-2xx hop does not always end at either.** A\n`Failed` row can still have its reserve charged; and a fee whose outcome the facilitator could not\nadjudicate is **never** released — money may have moved — so it stays at `Submitted`, reserve\ncharged, pending on-chain reconciliation. Reconciling budget therefore has **three** answers, not\ntwo: `Released` means refunded, `Failed`/`Submitted` mean still charged. Key off `Released`.\n\n**The two legs settle independently — reconcile them separately.** `txHash` anchors the merchant\npayment, `feeTxHash` the fee; neither implies the other. A `Settled` fee reconciled on-chain rather\nthan reported by the facilitator has **no `feeTxHash` at all** — the chain answers \"was this\nauthorization consumed\" with a boolean, not a transaction — so `feeNonce` is the audit key for those\nrows. A null `feeTxHash` is not evidence the fee did not settle.\n\n**For CSV consumers:** new columns are only ever **appended** (the fee columns, `assetSymbol` /\n`assetDecimals` and `feeFailureReason` all came after the original set), so reading the original\ncolumns by **index from the left** is safe. A parser that **asserts a header count** or maps\npositionally **from the right** breaks each time a column is added. Key off the header names.\n\n## Aggregate summary\n\n```bash\ncurl -s \"$NVM_API_URL/api/v1/router/payments/summary?granularity=day\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\"\n# → { \"total\": 137, \"series\": [ { \"date\": \"2026-07-01T00:00:00.000Z\", \"value\": 12 }, … ] }\n```\n\n`granularity` is `day` (default), `week` or `month`; an unrecognised value **falls back to `day`\nrather than erroring**, so a typo silently changes your bucketing. `from`/`to` behave as above.\nBuckets are oldest first. `total` is **uncapped**, unlike the 1000-row list.\n\n**The summary counts payment *requests*, not money.** Use the list endpoint when you need amounts.\n\n## Statuses\n\n| Status | Meaning |\n| --- | --- |\n| `Issued` | Credential minted and budget reserved. Either still in flight, or it succeeded without a usable settlement reference |\n| `Settled` | The merchant accepted the credential and returned a settlement reference, stored as `txHash` |\n| `Failed` | The merchant rejected the credential — it answered the paid request with another 402 |\n\n**`Issued` is not an error.** On a paid mode-B call it means you got the resource but the settlement\nanchor did not arrive — a missing, oversized or malformed receipt. The Router deliberately will not\nfail an already-paid hop over a bad receipt. In mode A it is simply where a record sits until you\nreport the settlement.\n\nSo: **an agent must not retry a payment because its record says `Issued`.** The money moved. Retrying\nwith a fresh `requestId` buys it again.\n\n## Closing a mode-A record\n\n```bash\ncurl -sX POST \"$NVM_API_URL/api/v1/router/payments/$PAYMENT_ID/settled\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"txHash\":\"0xfc8af37b…\"}'\n# → { \"paymentId\": \"b1f9c2e4-…\", \"status\": \"Settled\", \"txHash\": \"0xfc8af37b…\" }\n```\n\nTake the reference from the merchant's `PAYMENT-RESPONSE` / `X-PAYMENT-RESPONSE` (x402) or\n`Payment-Receipt` (MPP) header.\n\n- `txHash` must be a `0x`-prefixed 32-byte hex transaction hash (the crypto rails); anything else is\n  a `400`. Card-rail (MPP-stripe) records cannot be closed here — Nevermined reconciles them.\n- Only an `Issued` payment can be settled. Re-reporting the **same** hash is a harmless no-op; a\n  **different** hash, or a non-`Issued` record, is `409 BCK.ROUTER.0005`.\n- A payment id that is not yours is `404 BCK.ROUTER.0004`.\n\nMode B does this for you — one of the better reasons to prefer it.\n\n## Reconciling\n\nThe settlement reference is **reported by the merchant and stored unverified**. The Router bounds its\nlength and character set, but does not confirm on-chain that the transaction exists, paid the\nexpected recipient, or moved the expected amount.\n\nFor anything that matters — accounting, disputes, anomaly detection — treat `txHash` as an **anchor\nto verify**, not as proof. Check it against the chain named in `network`.\n\nA non-`0x` reference is legitimate: settlement identifiers are protocol-specific, and non-blockchain\nrails return processor references rather than transaction hashes.\n\n## What an agent should do with this\n\n- **Reconcile against your own intent.** You hold the `requestId`s you generated; the ledger echoes\n  them. A row whose `requestId` you do not recognise is worth surfacing.\n- **Watch the burn rate, not just the cap.** `remainingBudgetCents` on the Delegation tells you where\n  you are; the summary series tells you how fast you got there.\n- **Report, don't self-heal.** If the ledger disagrees with what you think you bought, that is a\n  human's problem to look at — not a reason to re-issue payments.\n\nFile v0.1.30:references/paying.md\n\n# Paying — mode B, the streaming proxy, and mode A\n\nThree ways to pay. **Default to mode B.** Reach for the others only when its shape does not fit.\n\n| | Endpoint | Use when |\n| --- | --- | --- |\n| **Mode B — envelope** | `POST /api/v1/router/route` | Almost always. One call, JSON in, JSON out |\n| **Mode B — streaming** | `ALL /api/v1/router/proxy` | Large or streamed responses (SSE, downloads) |\n| **Mode A — credential** | `POST /api/v1/router/payments` | You must call the merchant yourself |\n\nAll three need `Authorization: Bearer $NVM_API_KEY` and an `erc4337` `delegationId`.\n\nTo learn what a mode-B call will cost before paying for it, [quote it](#quote) first.\n\n---\n\n## Mode B — `POST /api/v1/router/route`\n\nYou describe the request; the Router probes the merchant, **auto-detects** the protocol from the\n402, pays, and relays the response. You never see the 402 and never handle a credential.\n\n```json\n{\n  \"delegationId\": \"5e7481c3-e972-45bd-bdc5-a0b99c4de4a1\",\n  \"slug\": \"superhighway\",\n  \"path\": \"/search\",\n  \"method\": \"POST\",\n  \"headers\": { \"X-Merchant-Api-Key\": \"…\" },\n  \"body\": { \"query\": \"nevermined router\" },\n  \"requestId\": \"search-nevermined-router-v1\"\n}\n```\n\n| Field | Required | Notes |\n| --- | --- | --- |\n| `delegationId` | **yes** | UUID. Must be an `erc4337` Delegation |\n| `url` | one of `url` or `slug` | Absolute `http(s)` URL for an off-catalog target |\n| `slug` | one of `url` or `slug` | Catalog slug; the Router keeps its upstream URL hidden |\n| `path` | no | Path segments appended to a slug target; do not put a query here |\n| `search` | no | Query string without `?` for a slug target; with a raw `url`, put the query in `url` |\n| `method` | no | `GET` · `POST` · `PUT` · `PATCH` · `DELETE`. Default `GET` |\n| `headers` | no | Forwarded to the merchant — put **its** auth here, never your `NVM_API_KEY` |\n| `body` | no | JSON, forwarded |\n| `protocol` | no | `x402`/`mpp`. **Advisory only** — see below |\n| `requestId` | **yes** | Idempotency key. Non-empty, ≤ 256 chars |\n| `maxTotalCents` | no | Non-negative safe integer. Per-call ceiling on the fee-inclusive, whole-cent cap reserve; a larger quote returns `402 BCK.ROUTER.0018` before a charge or budget reserve. The Delegation cap remains the overall limit |\n| `quoteId` | no | API 1.55+: opaque 60-second binding returned by `/quote`. Send it with the quoted request unchanged to pay the sealed challenge and exact quoted total; `maxTotalCents` remains an independent ceiling |\n\n### `protocol` is advisory here, and the detected one wins\n\nOn mode B the Router determines the protocol from the upstream 402 itself —\n`WWW-Authenticate: Payment` → `mpp`; `accepts` / `PAYMENT-REQUIRED` → `x402`. **The detected\nprotocol is authoritative**: a wrong hint does not change what gets paid, and does not cause a\nfailure. You can omit it entirely and send the same call for both rails.\n\n### Response\n\n```json\n{\n  \"status\": 200,\n  \"body\": { \"…\": \"the paid resource\" },\n  \"paid\": true,\n  \"payment\": {\n    \"paymentId\": \"b1f9c2e4-…\",\n    \"settlement\": {\n      \"recipient\": \"0x209693Bc…\", \"amount\": \"1000\", \"asset\": \"USDC\",\n      \"network\": \"base\", \"approxCents\": \"1\"\n    },\n    \"fee\": { \"bps\": 0, \"amount\": \"0\", \"cents\": \"0\", \"capChargedCents\": \"1\" },\n    \"txHash\": \"0xfc8af37b…\",\n    \"status\": \"Settled\"\n  }\n}\n```\n\n- `status` / `body` are the merchant's own, relayed unchanged. `body` is parsed JSON when the\n  merchant returned JSON, otherwise a string.\n- `paid: false` and **no `payment` block** means the merchant answered without asking for payment,\n  and nothing was charged. With a raw `url` the body is relayed. With a `slug` the body is\n  `null` (it could name the merchant's host), **except** for a follow-up read of a job you paid\n  for — see [Async services](#async-services-pay-then-poll). Handle this; not every call you\n  route is actually paid.\n- `settlement.approxCents` is the **merchant leg only**. What came off your cap is\n  `fee.capChargedCents` — see [the fee object](#the-fee-object) below. Trust either over any catalog\n  `priceLabel`.\n- `status: \"Issued\"` (rather than `Settled`) means the hop succeeded and you have your resource, but\n  the settlement anchor is still pending. Normal, not a failure — see `ledger.md`.\n\n<a id=\"async-services-pay-then-poll\"></a>\n### Async services — pay, then poll\n\nSome catalog services answer a paid call with a job id and deliver the result later, on a **free**\nstatus/result endpoint (Tavily research is one). Poll that endpoint through the **same slug** with\n`/route`:\n\n```json\n{ \"delegationId\": \"…\", \"slug\": \"tavily-api-mpp\", \"path\": \"/research\", \"method\": \"POST\",\n  \"body\": { \"input\": \"…\" }, \"requestId\": \"research-agent-payments-v1\" }\n```\n```json\n{ \"delegationId\": \"…\", \"slug\": \"tavily-api-mpp\", \"path\": \"/research/<request_id>\",\n  \"method\": \"GET\", \"requestId\": \"research-agent-payments-v1-poll-3\" }\n```\n\nThe poll comes back `paid: false` with the merchant's body, and nothing is charged. It is\nrelayed only when all of these hold:\n\n- **you** have a `Settled` payment for that slug made within the follow-up window — one hour on\n  production, counted from the payment. A card (SPT) payment stays `Issued`, so it never unlocks a\n  follow-up;\n- the path is one the service declares as a follow-up, i.e. its job status/result path;\n- the merchant answers `2xx`.\n\nOutside those conditions the body is `null`. An unpaid call to a follow-up path, or a poll after\nthe window, gets the status but not the body. A read the merchant answers with a `402` (for\nexample one that needs the creating wallet's signature) is paid like any other call.\nThe poll is a new request, so give each one its own `requestId`.\n\n<a id=\"the-fee-object\"></a>\n### The `fee` object — read `capChargedCents`, not `approxCents`\n\nNevermined charges its own routing fee on top of the merchant's price. Both modes return a **`fee`\nobject next to `settlement`**, and it is **always present** — zeroed when no fee applied, so you\nnever branch on its absence.\n\n| Field | Meaning |\n| --- | --- |\n| `bps` | The rate applied to this payment, in basis points over 10,000 (`200` = 2%). `0` = no fee |\n| `amount` | The fee in the settlement asset's **smallest unit** — same unit as `settlement.amount`. Reported in that unit rather than cents because cents are ceiling-rounded and cannot express a sub-cent fee |\n| `cents` | Cents the fee added to the cap reserve, i.e. `capChargedCents - settlement.approxCents` |\n| `capChargedCents` | **Total debited from the Delegation cap** for this payment — merchant leg + routing fee |\n| `capChargedMicros` | The same total, exact, in micros (1/10,000 of a cent). `capChargedCents` is this figure rounded **up** to a whole cent, and is what `maxTotalCents` is compared against |\n\n```\ncapChargedCents  =  settlement.approxCents  +  fee.cents\n```\n\n**If you keep your own budget ledger, sum `fee.capChargedCents`, not `approxCents`.** Summing\n`approxCents` under-reports your spend by exactly the fee, and the drift compounds silently over a\nlong run. With no fee configured the two are equal — which is why an agent that only ever ran against\na zero-fee deployment will not notice until one has a rate set.\n\n<a id=\"reserve-not-final\"></a>\n⚠️ **`capChargedCents` is what was reserved at MINT, and the fee half can come back.** On mode B, if\nthe upstream does not answer `2xx`, the routing fee is released to your cap and the row goes\n`feeStatus: Released` — the **merchant** leg's reserve stays charged, by design. So a running total of\n`capChargedCents` *over*-reports on exactly those calls, by the fee. (A fee leg that could not be\nsigned is released on **both** modes; only the merchant-hop trigger is mode-B's.)\n\nWhich figure you want depends on the question:\n\n| You want | Read |\n| --- | --- |\n| What this call reserved, at the moment it was made | `fee.capChargedCents` on the response |\n| What you have actually spent, net of releases | `amountSpentCents` / `remainingBudgetCents` on `GET /api/v1/delegation/{id}` |\n| Whether a specific call's fee came back | `feeStatus` on its ledger row: **only** `Released` means refunded. `Failed` and an unadjudicated `Submitted` are both still charged (see `ledger.md`) |\n\n**The Delegation is the authority on spend; the response is the authority on what one call reserved.**\nReconcile against the Delegation, not against your own sum, and the two agreeing is the check.\n\nThe credential itself pays the **merchant only** — the fee never rides the merchant's authorization\nand settles as its own leg, signed separately from the same buyer wallet. So the wallet has to cover\n`settlement.amount` **plus** `fee.amount`, and the fee's settlement is tracked independently on the\nledger (`feeStatus`, `feeTxHash`) rather than by the payment's own `status`. See `ledger.md`.\n\n### `requestId` — the rule that prevents double-spending\n\nRequired on mode B **because the Router pays automatically**: a retry after a dropped connection\nmust not buy the same thing twice. At most one payment is minted per `(caller, requestId)`; a\nduplicate returns `409 BCK.ROUTER.0002` carrying the **original** `paymentId`, which is usually what\nyou actually wanted.\n\n**Use one stable id per logical purchase, reused across retries of that purchase.**\n\n- Same id on retry → `409 BCK.ROUTER.0002` with the original `paymentId`, **not the resource**. Safe — and never escape that 409 with a fresh id.\n- Fresh id on retry → buys again. Also safe, *if that is what you meant*.\n- From API version 1.48 (a key pinned at or above it), a same-id retry within 24 h returns the\n  retained paid result (or its `202` while it runs) instead of the 409, and a merchant slower than\n  45 s is answered `202 { paymentId, resultUrl, status: \"Pending\" }` — read it later from\n  `GET /api/v1/router/payments/{id}/result` (`404 BCK.ROUTER.0030` once expired). The 409 still\n  answers when nothing was kept (a `/proxy` · `/svc` response that already streamed to you), a\n  call that `Failed`, or an id reused for a different target. Keep the same id.\n\nDerive it from the work (`\"search-nevermined-router-v1\"`, a hash of the query, a task id). **A fresh\n`uuid4()` per HTTP attempt is how an agent double-spends** — it is the default reflex and it is\nwrong here.\n\n<a id=\"quote\"></a>\n### Price it first — `POST /api/v1/router/quote`\n\nThe unpaid half of mode B, on deployments running API 1.48 or later. Send the same body as `/route`\n**minus `requestId`, `maxTotalCents` and `protocol`** (they are stripped, not refused — a ceiling sent\nhere does nothing); `delegationId` is optional. The Router makes the same unpaid request to the service that a payment would, reads the\n402, selects the payment option exactly as a payment would (MPP first, then x402), prices it with the\nrouting fee — and stops. **Nothing is signed, no credential is minted, no payment is recorded and no\nbudget is reserved.**\n\n```bash\ncurl -sX POST \"$NVM_API_URL/api/v1/router/quote\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\n    \"delegationId\": \"'\"$NVM_DELEGATION_ID\"'\",\n    \"slug\": \"superhighway\",\n    \"path\": \"/search\",\n    \"method\": \"POST\",\n    \"body\": { \"query\": \"nevermined router\" }\n  }'\n```\n\n```json\n{\n  \"quoteId\": \"q1.opaque-authenticated-quote-token\",\n  \"expiresAt\": \"2026-09-29T12:01:00.000Z\",\n  \"paymentRequired\": true,\n  \"upstreamStatus\": 402,\n  \"optionSet\": \"delegation\",\n  \"delegationId\": \"5e7481c3-e972-45bd-bdc5-a0b99c4de4a1\",\n  \"protocol\": \"x402\",\n  \"x402Version\": 2,\n  \"settlement\": {\n    \"recipient\": \"0x209693Bc…\", \"amount\": \"50000\", \"asset\": \"USDC\",\n    \"network\": \"base\", \"approxCents\": \"5\"\n  },\n  \"fee\": { \"bps\": 200, \"amount\": \"1000\", \"cents\": \"1\", \"capChargedCents\": \"6\", \"capChargedMicros\": \"51000\" }\n}\n```\n\nA $0.05 call with a 2% routing fee: the exact cap debit is `51000` micros (5.1¢), and\n`capChargedCents` rounds that **up** to `6`. On API 1.55+, the payment-required quote's opaque\n`quoteId` binds the exact request, Delegation, selected rail, merchant challenge and fee-inclusive\namount for **60 seconds**, until `expiresAt`. Pay by sending that `quoteId` on `/route` with the\nquoted call unchanged and `maxTotalCents: 6`. The quote binding fixes the exact 5.1¢ total;\n`maxTotalCents` remains an independent whole-cent ceiling. An invalid/wrong-account id is\n`BCK.ROUTER.0029`, expiry is `0032`, and any request, Delegation, rail or amount mismatch is `0033` —\nnothing is reserved or charged. Re-quote if the call changes or the id expires.\n\nClients pinned below API 1.55 receive neither `quoteId` nor `expiresAt`: `/route` probes again, and\nonly `maxTotalCents` guards the new price. Its whole-cent resolution means the 5.1¢ quote paid with a\nceiling of `6` accepts up to 6.00¢. (`settlement` carries no `scheme` on the x402 rail; it defaults to\n`exact`. Read `protocol` for the rail.)\n\n| Field | Meaning |\n| --- | --- |\n| `quoteId` · `expiresAt` | API 1.55+, on a payment-required quote: an opaque sealed binding and its ISO-8601 expiry, 60 seconds after issue |\n| `paymentRequired` | `false`: the service did not ask for payment for this request. Every priced field below is then absent |\n| `upstreamStatus` | The service's answer to the unpaid request — `402` when `paymentRequired` is true. Its body and headers are never returned |\n| `optionSet` | `delegation`: the `delegationId` you sent was priced — a card Delegation pays over MPP-stripe, an organization-wallet Delegation pays only in its own currency, a recipient allowlist is enforced. `deployment`: you sent none, so this is what a personal crypto Delegation would select |\n| `delegationId` | The Delegation priced, or `null` for `deployment` |\n| `protocol` · `x402Version` | The rail the payment would use, detected as on `/route` |\n| `settlement` · `fee` | The same objects `/route` returns under `payment` — see [the fee object](#the-fee-object) |\n\n**A quote is free of charge, not free of consequence:**\n\n- **It contacts the service.** The unpaid request is real, so a service that does not charge for it\n  performs it — take care quoting a method with side effects.\n- **It spends rate budget.** A quote counts against the same per-key and per-service rate limits as a\n  payment. Quote once per decision; do not poll it.\n- **It checks neither your remaining cap nor your wallet balance** — the payment still does\n  (`BCK.ROUTER.0003`, `BCK.ROUTER.0009`).\n\nIt refuses what `/route` would refuse before paying (`400 BCK.ROUTER.0001`, `409 BCK.ROUTER.0014` for\na raw URL on a cataloged host, `404 BCK.CATALOG.0001` for an unknown slug), and a read the price\ndepends on can fail with `503 BCK.ROUTER.0028`, which is retryable with backoff.\n\n**OAuth `commerce` credential:** an OAuth-minted key is refused here (`403 BCK.OAUTH.0030`). A key\nfrom a `commerce` grant quotes on **`POST /api/v1/router/commerce/quote`** — same body, same answer;\nit ships in the first API release after 1.49 and answers `404` until your deployment has it —\nwhich prices the Delegation the grant is pinned to and so refuses a `delegationId`\n(`400 BCK.OAUTH.0034`). Pay the result on `POST /api/v1/router/commerce/route`. A plain key on the\ncommerce route gets `403 BCK.OAUTH.0033`.\n\n---\n\n## Mode B streaming — `ALL /api/v1/router/proxy`\n\nSame engine, transparent transport: method, body and the standard request headers pass through and\nthe response **streams** back. Use it for SSE, large downloads, or anything you do not want buffered into a JSON\nenvelope. It is deliberately absent from the OpenAPI document — it is a raw any-method proxy driven\nby headers, with no fixed schema.\n\nPoint your HTTP client at `/api/v1/router/proxy` and drive it with request headers:\n\n| Request header | Required | Purpose |\n| --- | --- | --- |\n| `X-Router-Target-Url` | **yes** | Absolute upstream URL |\n| `X-Router-Delegation-Id` | **yes** | Your `erc4337` Delegation |\n| `X-Router-Request-Id` | **yes** | Idempotency key — same rule as mode B |\n| `X-Router-Upstream-Authorization` | no | The **merchant's** auth, forwarded as its `Authorization` |\n| `X-Router-Forward-<name>` | no | Send `<name>: <value>` upstream (see below) |\n\n```bash\ncurl -sN -X POST \"$NVM_API_URL/api/v1/router/proxy\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"X-Router-Target-Url: https://service.example/stream\" \\\n  -H \"X-Router-Delegation-Id: $NVM_DELEGATION_ID\" \\\n  -H \"X-Router-Request-Id: stream-job-42\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"prompt\":\"…\"}'\n```\n\nPayment metadata comes back in **response headers** rather than the body:\n\n| Response header | Meaning |\n| --- | --- |\n| `X-Router-Payment-Id` | The ledger record id |\n| `X-Router-Payment-Status` | `Issued` · `Settled` · `Failed` |\n| `X-Router-Tx-Hash` | Settlement hash, when the merchant reported one |\n\nOmitting `X-Router-Target-Url`, `X-Router-Delegation-Id` or `X-Router-Request-Id` is\n`400 BCK.ROUTER.0001`.\n\n`X-Router-*` headers are stripped in **both** directions — yours are not forwarded upstream, and any\nthe merchant returns are removed before you see them. So a merchant cannot forge a payment signal\nthat makes a free response look paid.\n\n### Which of your request headers reach the merchant\n\n`/proxy` replays your request off the wire, so the header map it sees is not only yours — every\nproxy in between appends its own, ours included. Those describe our infrastructure, so `/proxy`\nforwards an **allowlist** and drops the rest:\n\n```\naccept  accept-language  cache-control  content-language  content-type\nidempotency-key  if-match  if-modified-since  if-none-match  if-range\nif-unmodified-since  range  user-agent\n```\n\n`Cookie`, `Origin` and `Referer` are dropped too. If a merchant needs something else — an\n`X-Api-Key` rather than an `Authorization`, say — state its **value**, prefixed:\n\n```bash\n-H \"X-Router-Forward-x-api-key: sk-merchant-key\"      # → sends  x-api-key: sk-merchant-key\n```\n\nIt carries the value rather than naming a header to replay, so it can never hand a merchant\nsomething *we* put on your request. One invariant covers the whole path: **nothing replayed off the\nwire is forwarded except the allowlist above.** The channel refuses `Authorization` (use\n`X-Router-Upstream-Authorization`), `X-Payment` / `Payment-Signature` (the Router pays through its\nledger, not around it), hop-by-hop headers and re-entrant `X-Router-*` names.\n\n**`POST /route` applies no allowlist** — its `headers` are a JSON object you wrote, so it forwards\neverything you ask for. Reach for it when you need a header `/proxy` will not carry and you do not\nneed streaming.\n\n**Drain the response.** The idle timer (30s default) is re-armed by your client consuming the\nstream, so a slow-but-healthy large transfer will not trip it — but an abandoned one will be killed,\nand the connection held open in the meantime counts against your concurrency limit.\n\n---\n\n## Mode A — `POST /api/v1/router/payments`\n\nThe Router mints a signed credential; **you** call the merchant. Use it when the Router cannot be in\nthe request path — you need the raw connection, an exotic transport, or the merchant rejects a\nrelayed call.\n\nIt is strictly more work: you make the unpaid call, hand over the challenge, attach the credential,\nre-send, and then close the ledger record yourself.\n\n### 1 · Provoke the 402\n\nCall the merchant with no payment. It answers `402` with its requirements:\n\n- **x402 v1** — in the JSON body: `{ \"x402Version\": 1, \"accepts\": [...] }`\n- **x402 v2** — base64 in the `PAYMENT-REQUIRED` **response header** (decode it to an object)\n- **MPP** — the raw `WWW-Authenticate: Payment …` header value\n\n### 2 · Mint the credential\n\n```json\n{\n  \"delegationId\": \"5e7481c3-…\",\n  \"protocol\": \"x402\",\n  \"resourceUrl\": \"https://agent.example/paid\",\n  \"requestId\": \"order-1234\",\n  \"target\": { \"x402Version\": 1, \"accepts\": [ /* verbatim from the 402 */ ] }\n}\n```\n\n| Field | Required | Notes |\n| --- | --- | --- |\n| `delegationId` | **yes** | |\n| `protocol` | **yes** | `x402` or `mpp`. **Not auto-detected here** — unlike mode B, you must get this right |\n| `target` | **yes** | x402 → `{ accepts, x402Version? }` (defaults to **2**; set `1` for x402-express). MPP → `{ challenge }`, the raw header value |\n| `resourceUrl` | no | Absolute URL, recorded on the ledger |\n| `requestId` | no | **Optional here**, unlike mode B — but [always pass a stable one](#mode-a-fee): it is the only thing that dedupes a retry, and without it a retry is minted **and charged the routing fee** a second time |\n| `maxTotalCents` | no | Non-negative safe integer. Per-call ceiling on the merchant price plus any collectable Router fee, rounded once to whole cents; the Delegation cap remains the overall limit |\n\nPass `target` **verbatim** from the 402. Do not normalise, reorder or re-encode it.\n\nThe response echoes the negotiated version, and `credential.name` follows from it — the request\nabove is v1, so this one comes back v1 with the v1 header:\n\n```json\n{\n  \"paymentId\": \"b1f9c2e4-…\",\n  \"protocol\": \"x402\",\n  \"x402Version\": 1,\n  \"credential\": { \"transport\": \"header\", \"name\": \"X-PAYMENT\", \"value\": \"eyJ4NDAy…\" },\n  \"settlement\": { \"recipient\": \"0x2096…\", \"amount\": \"1000\", \"asset\": \"USDC\",\n                  \"network\": \"base\", \"approxCents\": \"1\" },\n  \"fee\": { \"bps\": 0, \"amount\": \"0\", \"cents\": \"0\", \"capChargedCents\": \"1\" },\n  \"status\": \"Issued\"\n}\n```\n\nHad you passed a v2 `target` (the default), the same call would return `\"x402Version\": 2` and\n`\"name\": \"PAYMENT-SIGNATURE\"`.\n\n<a id=\"mode-a-fee\"></a>\n[The `fee` object](#the-fee-object) is on this response too — **zeroed in the sample above because\nthat is a deployment with no rate configured**, not because mode A is free. ⚠️ **Mode A charges the\nrouting fee on every call**, whether or not you pass a `requestId`.\n\n`requestId` does not change *whether* you are charged — it changes whether a **retry** is charged\nagain. Reuse one stable id across every retry of the same purchase and the retry returns\n`409 BCK.ROUTER.0002` with the original `paymentId` instead of minting: one purchase, one fee. Omit it,\nor generate a fresh id per HTTP attempt, and the retry is a new purchase — a second credential and a\nsecond real fee transfer for one thing you meant to buy once.\n\nSo derive the id from the work you are doing (`\"search-nevermined-router-v1\"`), not from `uuid4()` per\nattempt. Mode B requires one already and is unaffected.\n\n### 3 · Attach it and re-send\n\nSet an HTTP header named **`credential.name`** to **`credential.value`** on your original request\nand send it again. The value is opaque — attach it verbatim, do not modify it.\n\n| Protocol | `credential.name` |\n| --- | --- |\n| x402 v2 | `PAYMENT-SIGNATURE` |\n| x402 v1 | `X-PAYMENT` |\n| MPP | `Authorization` |\n\nRead the name off the response rather than hardcoding it — that is why the field exists.\n\n### 4 · Close the record\n\nThe merchant returns a settlement reference: `PAYMENT-RESPONSE` / `X-PAYMENT-RESPONSE` (x402) or\n`Payment-Receipt` (MPP). Report the on-chain transaction hash it carries — `txHash` must be a\n`0x`-prefixed 32-byte hex hash, and card-rail records cannot be closed this way:\n\n```bash\ncurl -sX POST \"$NVM_API_URL/api/v1/router/payments/$PAYMENT_ID/settled\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"txHash\":\"0xfc8af37b…\"}'\n```\n\nIdempotent: re-reporting the **same** hash is a no-op. A **different** hash, or a record not in\n`Issued`, is rejected with `409 BCK.ROUTER.0005`. Not needed in mode B — `/route` and `/proxy` close\nthe record themselves.\n\n### Mode A caveats\n\n- **Credentials expire.** The signed authorization has a validity window capped by the operator\n  (one hour by default). Mint it when you are about to use it — if it lapses, the budget stays\n  reserved and the record stays `Issued`.\n- **On MPP, prefer mode B.** The Router is not in the request path, so it never sees the\n  `Payment-Receipt`; closing the record means decoding the receipt yourself, which needs the MPP\n  codec you were trying to avoid.\n\n---\n\n## Passing the merchant's own auth\n\nSome services want payment *and* an account credential. Never put `NVM_API_KEY` in either slot:\n\n- mode B → `headers: { \"Authorization\": \"Bearer <merchant-token>\" }`\n- `/proxy` → `X-Router-Upstream-Authorization: Bearer <merchant-token>`\n\nNote that a service which answers **`401`/`403` instead of `402`** wants authentication, not\npayment. The Router cannot help — that service needs an account. See `discovery.md`.\n\nFile v0.1.30:skill-card.md\n\n## Description:\n\nGuides agents through discovering and paying for x402 or MPP service calls with Nevermined Router under a capped spending delegation.\n\nThis skill is ready for commercial/non-commercial use.\n\n## Publisher:\n\n[nevermined-io](https://clawhub.ai/user/nevermined-io)\n\n### License/Terms of Use:\n\nMIT-0\n\n## Use Case:\n\nDevelopers and agents use this skill to find payable services, create a capped spending delegation, quote and route paid requests, and reconcile charges in the payment ledger.\n\n### Deployment Geography for Use:\n\nGlobal\n\n## Known Risks and Mitigations:\n\nRisk: Paid requests can spend a user's funds or exceed their intended per-call price.\n\nMitigation: Start in sandbox, use small short-lived delegations, quote once per decision, enforce a fee-inclusive price ceiling, and obtain human approval before raising budgets.\n\nRisk: Credentials or sensitive information could be exposed to an external service.\n\nMitigation: Do not forward the Nevermined API key or include secrets in routed request bodies or forwarded headers; prefer catalog slugs over raw URLs.\n\nRisk: Retrying an uncertain paid call with a new request ID could charge twice.\n\nMitigation: Reuse one request ID per purchase and check the payment result or ledger instead of starting a new payment.\n\nRisk: An agent could accept legal terms without the account holder's consent.\n\nMitigation: Require human approval before accepting legal terms.\n\n## Reference(s):\n\n- [Nevermined Router documentation](https://nevermined.ai/docs/products/catalog/router/overview)\n- [Nevermined API keys](https://nevermined.app)\n- [Bootstrap and spending delegations](references/bootstrap.md)\n- [Service discovery](references/discovery.md)\n- [Paying and quoting](references/paying.md)\n- [Errors and guardrails](references/errors.md)\n- [Payment ledger](references/ledger.md)\n\n## Skill Output:\n\n**Output Type(s):** [Guidance, Shell commands, Configuration instructions]\n\n**Output Format:** [Markdown with HTTP and shell examples]\n\n**Output Parameters:** [1D]\n\n**Other Properties Related to Output:** [None]\n\n## Skill Version(s):\n\n0.1.30 (source: ClawHub release)\n\n## Ethical Considerations:\n\nUsers should evaluate whether this skill is appropriate for their environment, review any generated or modified files before relying on them, and apply their organization's safety, security, and compliance requirements before deployment.\n\nArchive v0.1.29: 8 files, 53809 bytes\n\nFiles: references/bootstrap.md (11264b), references/discovery.md (12574b), references/errors.md (27073b), references/ledger.md (13816b), references/paying.md (24546b), skill-card.md (2226b), SKILL.md (36675b), _meta.json (137b)\n\nFile v0.1.29:SKILL.md\n\n---\nname: nevermined-router\nversion: \"0.1.5\"\nlastUpdated: \"2026-09-26\"\ndescription: >\n  Use when an AI agent needs to PAY an external service it does not have an account\n  with — any x402 agent or MPP merchant — using the Nevermined Router. Covers\n  discovering services in the Agent Services Catalog, creating a spending Delegation\n  from an API key, funding the buyer wallet, pricing a call first with\n  /api/v1/router/quote, making paid calls through /api/v1/router/route (or the\n  streaming /proxy), reading the payment ledger, and the\n  guardrails an autonomous buyer must respect. Complements the nevermined-payments\n  skill, which is about RECEIVING payments and buying Nevermined plans.\nmetadata:\n  openclaw:\n    primaryEnv: NVM_API_KEY\n    requires:\n      env:\n        - NVM_API_KEY\n    envVars:\n      - name: NVM_API_KEY\n        required: true\n        description: >\n          Nevermined API key. Format `sandbox:...` for the sandbox environment,\n          `live:...` for production. Issued from https://nevermined.app under API\n          Keys. Must be a current-generation key — older keys are refused by the\n          Router with 403 BCK.ROUTER.0008.\n---\n\n# Nevermined Router — buy from any x402 or MPP service\n\n> **Skill version**: 0.1.5 | **Last updated**: 2026-09-26 | **Canonical source (always latest):** https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router\n>\n> **⚠️ Use the latest version.** If you have a cached copy, check its **Last updated** date against the canonical source and refresh if older.\n>\n> Human-readable twin of the Router documentation at https://nevermined.ai/docs/products/catalog/router/overview. Same facts, same error codes — if the two ever disagree, the docs site is authoritative and this skill has a bug.\n\n## What this is for\n\nYou are an agent that needs something from a service you have **no account with, no API key for, and no billing relationship with**. The Router lets you pay it per request, from a budget a human capped in advance, and puts every spend on one ledger.\n\nIt works because a growing set of services quote their price **on the wire**: you call them, they answer `402 Payment Required` with what they want, you pay, you get the resource. The Router does the paying.\n\n| | |\n| --- | --- |\n| **Use this skill when** | you need to buy a single call from an external x402 / MPP service |\n| **Use `nevermined-payments` instead when** | you are *charging* callers, or buying a Nevermined **plan** with credits |\n\n<a id=\"not-for\"></a>\n**This skill cannot help you with conventional SaaS APIs.** Exa, Firecrawl, Tavily and similar are billed out of band — a monthly plan, a long-lived key. They never quote a price for one call, so there is nothing on the wire for the Router to pay and no address to pay it to. The Router isn't missing a feature; the transaction it performs does not exist for those services. If a service answers `401` or `403` rather than `402`, it wants **authentication**, not payment — stop, and tell the user it needs an account.\n\nThat is not a dead end, just a different rail. Nevermined can still buy from such a provider **out of band** — purchasing API credits up front instead of paying per call. Exa is the worked example: a $7 x402 card-delegation purchase provisions or tops up an Exa API key, fully agent-driven — https://nevermined.ai/docs/integrations/exa. That flow belongs to the `nevermined-payments` skill and the Payments SDK. What you cannot do is put those calls through `/router/route`.\n\n## The buy loop\n\nSix steps. Steps 1–3 happen once; 4–6 repeat per purchase.\n\n```\n① API key  ──▶ ② Delegation (budget) ──▶ ③ Fund the buyer wallet\n                                              │\n                    ┌─────────────────────────┘\n                    ▼\n   ④ Discover a service ──▶ ⑤ POST /router/route ──▶ ⑥ Read the spend\n      (catalog)                (pays + relays)          (ledger)\n```\n\nSet your environment once:\n\n```bash\nexport NVM_API_URL=\"https://api.sandbox.nevermined.app\"   # live: https://api.live.nevermined.app\nexport NVM_API_KEY=\"<your-api-key>\"\n```\n\nEverything is plain HTTP with `Authorization: Bearer $NVM_API_KEY`. There is **no SDK for the Router yet** — that is deliberate here, because it means any agent in any language can drive it with an HTTP client. The one exception is the catalog, which is public and needs no key at all.\n\n<a id=\"rail-availability\"></a>\n**The two rails are enabled independently per deployment, and the MPP rail is not on everywhere.** x402 (Base) is available by default. MPP (Tempo) requires the operator to allowlist the payment token for that chain, and the allowlist is **fail-closed** — where it is unset, *every* MPP service is refused with `400 BCK.ROUTER.0001 … not allowlisted`, before anything is signed.\n\nSo **a service being in the catalog does not mean your deployment can pay it.** The catalog describes services; it says nothing about how the deployment you are pointed at is configured. If MPP services fail with `0001 … not allowlisted` while x402 services pay fine, the rail is off where you are — that is a deployment setting, not something you can fix from the client, not a fault in the merchant, and not a reason to retry or to go looking for a different MPP service, which will fail identically. Ask the operator of your deployment, or stay on `protocol=x402`. See `references/errors.md`.\n\n**Never send `NVM_API_KEY` to the service you are paying.** It authenticates you to Nevermined and nothing else. If a merchant needs its own auth, pass it in `headers` (mode B) — see `references/paying.md`.\n\n---\n\n## ① Get an API key — *needs a human once*\n\nIssued from the Nevermined app. If you were given one, use it.\n\nA key that predates the Router is refused with **`403 BCK.ROUTER.0008`**. The fix is to create a new key; newly issued keys work. Old keys keep working for credit-based flows, so nothing else needs rotating.\n\n## ② Create a Delegation — *fully programmatic*\n\nA **Delegation** is the budget: a hard cap in cents plus an expiry, enforced server-side on every single payment. Create it once, reuse the id.\n\n```bash\ncurl -sX POST \"$NVM_API_URL/api/v1/delegation/create\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"provider\":\"erc4337\",\"currency\":\"usdc\",\"spendingLimitCents\":500,\"durationSecs\":604800}'\n# → { \"delegationId\": \"5e7481c3-e972-45bd-bdc5-a0b99c4de4a1\" }\n```\n\nThat is a $5.00 cap for 7 days. `provider: \"erc4337\"` is the crypto-funded Delegation both stablecoin rails require — a card-funded Delegation is refused on them.\n\n<a id=\"never-widen\"></a>\n**You may create a Delegation. You must never widen one to get past a refusal.** The cap is the human's decision; a refusal is that decision taking effect. See [Guardrails](#guardrails).\n\nTwo guards can refuse this call before your fields are read, and **neither is retryable**:\n\n- **`403 BCK.OAUTH.0030`** — the key was minted through an OAuth consent ceremony (`credits_purchase`, `account_access` or `commerce`) and may not create Delegations *or* use the paying routes. Use a plain API key issued by the account owner — or, if the key comes from a `commerce` grant, spend through `POST /api/v1/router/commerce/route` (and price a call through `POST /api/v1/router/commerce/quote`), which derive the Delegation from the grant instead of taking one from you.\n- **`412 {\"error\":\"consent_required\",\"outdated\":[…]}`** — the account's legal-document consent has lapsed. ⚠️ Its only `code` is the generic **`BCK.HTTP.412`**, which names the status and not the cause, so branch on `body.error === \"consent_required\"` — the one place \"branch on `code`\" needs a second field. A human must accept; report it and stop.\n\nFull field list, recipient scoping, both guards in detail, and reading a Delegation's live state: `references/bootstrap.md`.\n\n## ③ Fund the buyer wallet — *may need a human*\n\nBoth rails **pull**: the merchant takes funds from your own wallet. The Delegation authorizes the spend; it does not provide the money. Read the wallet address off the Delegation:\n\n```bash\ncurl -s \"$NVM_API_URL/api/v1/delegation/$NVM_DELEGATION_ID\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\"\n# → { \"providerPaymentMethodId\": \"0x8F60b3838e6C121FcDBdBc50e7B150F8560a670E\", ... }\n```\n\n`providerPaymentMethodId` is the address to fund, with the payment asset, **on the network you intend to pay on**.\n\n**Your deployment funds exactly one x402 network, fixed by its environment: sandbox → `base-sepolia`, live → `base`.** A merchant on the other chain is unpayable from where you are and fails with `400 BCK.ROUTER.0001 … no fundable option`, which reads like a broken service and is not. Check the environment before blaming the merchant — see `references/bootstrap.md`.\n\n**Always read this address back from the live Delegation — never from a value you cached.** Funding a stale address is the most common cause of `402 BCK.ROUTER.0009`, and the error deliberately does not echo the address it checked, so it cannot tell you that is what happened.\n\nIf the wallet is empty and you cannot fund it yourself, that is a **stop condition**: report it to the human. Do not retry.\n\n## ④ Discover a service\n\nThe **Agent Services Catalog** is one public JSON feed — no API key, no query parameters. Fetch it and filter on your side:\n\n```bash\ncurl -s https://nevermined.app/catalog/ai-catalog.json \\\n  | jq '[.services[] | select(.protocol == \"x402\" and .category == \"Search & Research\")\n         | {slug, title, priceLabel, endpoints: [.endpoints[] | {method, path: (.invokePath // .path), description}]}]'\n```\n\n```json\n[\n  {\n    \"slug\": \"superhighway\",\n    \"title\": \"Superhighway — Web Search for Agents\",\n    \"priceLabel\": \"$0.001\",\n    \"endpoints\": [\n      { \"method\": \"POST\", \"path\": \"/search\", \"description\": \"Web search\" },\n      { \"method\": \"POST\", \"path\": \"/news\",   \"description\": \"Real-time news search\" },\n      { \"method\": \"POST\", \"path\": \"/images\", \"description\": \"Image search\" }\n    ]\n  }\n]\n```\n\n(An excerpt: the real result lists every match.) `/api/v1/catalog/services` and `/api/v1/catalog/categories` are **not a public API** — they return `403` by desig\n\nArchive v0.1.28: 8 files, 52159 bytes\n\nFiles: references/bootstrap.md (11264b), references/discovery.md (12269b), references/errors.md (25849b), references/ledger.md (13816b), references/paying.md (23477b), skill-card.md (2590b), SKILL.md (34419b), _meta.json (137b)\n\nArchive v0.1.27: 8 files, 51675 bytes\n\nFiles: references/bootstrap.md (11264b), references/discovery.md (12269b), references/errors.md (25849b), references/ledger.md (13816b), references/paying.md (23276b), skill-card.md (2487b), SKILL.md (33320b), _meta.json (137b)\n\nArchive v0.1.26: 8 files, 50987 bytes\n\nFiles: references/bootstrap.md (11264b), references/discovery.md (12269b), references/errors.md (25849b), references/ledger.md (13816b), references/paying.md (21613b), skill-card.md (2518b), SKILL.md (33158b), _meta.json (137b)\n\nArchive v0.1.25: 8 files, 47763 bytes\n\nFiles: references/bootstrap.md (11183b), references/discovery.md (12269b), references/errors.md (23990b), references/ledger.md (13816b), references/paying.md (17252b), skill-card.md (2517b), SKILL.md (29649b), _meta.json (137b)\n\nArchive v0.1.24: 8 files, 47386 bytes\n\nFiles: references/bootstrap.md (11183b), references/discovery.md (12269b), references/errors.md (23415b), references/ledger.md (13816b), references/paying.md (17252b), skill-card.md (2483b), SKILL.md (29074b), _meta.json (137b)\n\nArchive v0.1.23: 8 files, 46827 bytes\n\nFiles: references/bootstrap.md (11183b), references/discovery.md (12250b), references/errors.md (23357b), references/ledger.md (13874b), references/paying.md (16986b), skill-card.md (2198b), SKILL.md (28308b), _meta.json (137b)\n\nArchive v0.1.22: 8 files, 46425 bytes\n\nFiles: references/bootstrap.md (11183b), references/discovery.md (12250b), references/errors.md (22585b), references/ledger.md (13874b), references/paying.md (16986b), skill-card.md (2297b), SKILL.md (27536b), _meta.json (137b)\n\nArchive v0.1.21: 8 files, 46105 bytes\n\nFiles: references/bootstrap.md (11183b), references/discovery.md (12250b), references/errors.md (22075b), references/ledger.md (13874b), references/paying.md (16986b), skill-card.md (2348b), SKILL.md (27026b), _meta.json (137b)","readmeExcerpt":"Skill: nevermined-router Owner: nevermined-io Summary: Use when an AI agent needs to PAY an external service it does not have an account with — any x402 agent or MPP merchant — using the Nevermined Router. Covers discovering services in the Agent Services Catalog, creating a spending Delegation from an API key, funding the buyer wallet, pricing a call first with /api/v1/router/quote, making paid calls through /api/v1","codeSnippets":[],"executableExamples":[{"language":"text","snippet":"① API key  ──▶ ② Delegation (budget) ──▶ ③ Fund the buyer wallet\n                                              │\n                    ┌─────────────────────────┘\n                    ▼\n   ④ Discover a service ──▶ ⑤ POST /router/route ──▶ ⑥ Read the spend\n      (catalog)                (pays + relays)          (ledger)"},{"language":"bash","snippet":"export NVM_API_URL=\"https://api.sandbox.nevermined.app\"   # live: https://api.live.nevermined.app\nexport NVM_API_KEY=\"<your-api-key>\""},{"language":"bash","snippet":"curl -sX POST \"$NVM_API_URL/api/v1/delegation/create\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"provider\":\"erc4337\",\"currency\":\"usdc\",\"spendingLimitCents\":500,\"durationSecs\":604800}'"},{"language":"bash","snippet":"curl -sX POST \"$NVM_API_URL/api/v1/delegation/create\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"provider\":\"erc4337\",\"currency\":\"usdc\",\"spendingLimitCents\":500,\"durationSecs\":604800}'\n# → { \"delegationId\": \"5e7481c3-e972-45bd-bdc5-a0b99c4de4a1\" }"},{"language":"bash","snippet":"curl -s \"$NVM_API_URL/api/v1/delegation/$NVM_DELEGATION_ID\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\""},{"language":"bash","snippet":"curl -s \"$NVM_API_URL/api/v1/delegation/$NVM_DELEGATION_ID\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\"\n# → { \"providerPaymentMethodId\": \"0x8F60b3838e6C121FcDBdBc50e7B150F8560a670E\", ... }"}],"parameters":null,"dependencies":[],"permissions":[],"extractedFiles":[{"path":"SKILL.md","content":"---\nname: nevermined-router\nversion: \"0.1.5\"\nlastUpdated: \"2026-09-26\"\ndescription: >\n  Use when an AI agent needs to PAY an external service it does not have an account\n  with — any x402 agent or MPP merchant — using the Nevermined Router. Covers\n  discovering services in the Agent Services Catalog, creating a spending Delegation\n  from an API key, funding the buyer wallet, pricing a call first with\n  /api/v1/router/quote, making paid calls through /api/v1/router/route (or the\n  streaming /proxy), reading the payment ledger, and the\n  guardrails an autonomous buyer must respect. Complements the nevermined-payments\n  skill, which is about RECEIVING payments and buying Nevermined plans.\nmetadata:\n  openclaw:\n    primaryEnv: NVM_API_KEY\n    requires:\n      env:\n        - NVM_API_KEY\n    envVars:\n      - name: NVM_API_KEY\n        required: true\n        description: >\n          Nevermined API key. Format `sandbox:...` for the sandbox environment,\n          `live:...` for production. Issued from https://nevermined.app under API\n          Keys. Must be a current-generation key — older keys are refused by the\n          Router with 403 BCK.ROUTER.0008.\n---\n\n# Nevermined Router — buy from any x402 or MPP service\n\n> **Skill version**: 0.1.5 | **Last updated**: 2026-09-26 | **Canonical source (always latest):** https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router\n>\n> **⚠️ Use the latest version.** If you have a cached copy, check its **Last updated** date against the canonical source and refresh if older.\n>\n> Human-readable twin of the Router documentation at https://nevermined.ai/docs/products/catalog/router/overview. Same facts, same error codes — if the two ever disagree, the docs site is authoritative and this skill has a bug.\n\n## What this is for\n\nYou are an agent that needs something from a service you have **no account with, no API key for, and no billing relationship with**. The Router lets you pay it per request, from a budget a human capped in advance, and puts every spend on one ledger.\n\nIt works because a growing set of services quote their price **on the wire**: you call them, they answer `402 Payment Required` with what they want, you pay, you get the resource. The Router does the paying.\n\n| | |\n| --- | --- |\n| **Use this skill when** | you need to buy a single call from an external x402 / MPP service |\n| **Use `nevermined-payments` instead when** | you are *charging* callers, or buying a Nevermined **plan** with credits |\n\n<a id=\"not-for\"></a>\n**This skill cannot help you with conventional SaaS APIs.** Exa, Firecrawl, Tavily and similar are billed out of band — a monthly plan, a long-lived key. They never quote a price for one call, so there is nothing on the wire for the Router to pay and no address to pay it to. The Router isn't missing a feature; the transaction it performs does not exist for those services. If a service answers `401` or `403` rather than `402`, it wants **authentication**, not payment — stop, and tell "},{"path":"_meta.json","content":"{\n  \"ownerId\": \"kn7bk8z6x7ytxvdb48j34j2ahh812m3p\",\n  \"slug\": \"nevermined-router\",\n  \"version\": \"0.1.30\",\n  \"publishedAt\": 1791451098137\n}"},{"path":"references/bootstrap.md","content":"# Bootstrap — API key, Delegation, funded wallet\n\nThree preconditions before any payment. Do them once and reuse.\n\n## 1. The API key\n\nEvery Router call carries `Authorization: Bearer $NVM_API_KEY`. Issued from the Nevermined app —\nthis is the one step that needs a human.\n\nKeys are environment-scoped: `sandbox:…` for sandbox, `live:…` for production. A key from the wrong\nenvironment fails auth, not the Router's own checks.\n\n**A key issued before the Router shipped is refused with `403 BCK.ROUTER.0008`.** It is bound to a\nprevious account model that cannot sign these payments. The fix is to create a new key — newly\nissued keys work. Existing keys keep working for credit-based flows, so nothing else needs rotating.\nThis is not retryable and not a transient error; do not loop on it.\n\n**Never forward this key to a merchant.** It authenticates you to Nevermined. If the merchant needs\nits own credential, pass that separately (`headers` in mode B, `X-Router-Upstream-Authorization` on\n`/proxy`) — see `paying.md`.\n\n## 2. The Delegation\n\nYour budget. A hard cap in cents plus an expiry, enforced server-side on **every** payment.\n\n```bash\ncurl -sX POST \"$NVM_API_URL/api/v1/delegation/create\" \\\n  -H \"Authorization: Bearer $NVM_API_KEY\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"provider\":\"erc4337\",\"currency\":\"usdc\",\"spendingLimitCents\":500,\"durationSecs\":604800}'\n# → { \"delegationId\": \"5e7481c3-e972-45bd-bdc5-a0b99c4de4a1\" }\n```\n\n| Field | Required | Notes |\n| --- | --- | --- |\n| `provider` | **yes** | `erc4337` for both stablecoin rails. No default — omitting it is a 4xx |\n| `currency` | **yes** | `usdc` · `eurc` · `usd` · `eur`. No default |\n| `spendingLimitCents` | **yes** | Integer ≥ 1. The hard cap, in cents |\n| `durationSecs` | **yes** | Integer ≥ 1. `604800` = 7 days |\n| `allowedRecipients` | no | Up to 100 `0x…` EVM addresses. **Omit = no restriction** |\n| `maxTransactions` | no | Cap on number of charges. Omit = unlimited |\n\n`provider: \"erc4337\"` is the crypto-funded Delegation both stablecoin rails require. Card-funded\nDelegations use a different provider and are refused on those rails (and vice versa) with\n`400 BCK.ROUTER.0001`.\n\n### Two ways creation is refused before your fields are even read\n\nBoth guard the *caller*, not the request body, so a perfectly valid payload still fails. Neither is\nretryable and neither can be fixed from your side alone.\n\n**`403 BCK.OAUTH.0030` — this API key may not create Delegations.** An **OAuth-minted** credential\n(one issued through an OAuth consent ceremony — today `credits_purchase`, `account_access` or\n`commerce`, but the guard keys on the binding rather than the consent type, so any future ceremony\ntype is refused too) is refused on\n`POST /delegation/create` *and* on the paying routes — `POST /router/payments`, `POST /router/route`,\n`POST /router/route/with-controls`, `POST /router/quote`, `POST /router/select`, `ALL /router/proxy`,\n`ALL /router/svc/<slug>`. Those routes sign from the account's full wall"},{"path":"references/discovery.md","content":"# Discovery — finding something to buy\n\nThe **Agent Services Catalog** is a Nevermined-curated list of external agent services. Discovery is\n**public, unauthenticated and free**. Send no `Authorization` header; none is required.\n\n| Surface | Use it for |\n| --- | --- |\n| `https://nevermined.app/catalog/ai-catalog.json` | **The default.** Every listed service in one JSON document — fetch once, filter locally |\n| Catalog MCP at `https://mcp.live.nevermined.app/mcp` | Server-side search: `search_services`, `get_service`, `list_categories` |\n| `https://nevermined.app/.well-known/ard.json` | The ARD host document, for registries crawling the Catalog — and per-service health |\n| `https://nevermined.app/catalog/llms.txt` | Plain-text entry point for an agent landing cold |\n| `https://nevermined.app/catalog/services` | Human browsing |\n\n⚠️ **`/api/v1/catalog/services`, `/api/v1/catalog/services/{slug}` and `/api/v1/catalog/categories`\nare not a public integration.** They return `403` on both `api.live` and `api.sandbox`, by design —\nnot an outage, and not something a key fixes. Do not retry them; read the feed.\n\nThe feed lists the **live** Catalog. It is live-only for payment: listed services settle on mainnet,\nand a sandbox deployment funds testnets only.\n\n## The feed\n\n```bash\ncurl -s https://nevermined.app/catalog/ai-catalog.json -o ai-catalog.json\njq '{total, generatedAt}' ai-catalog.json\n```\n\n`{ version, catalog, generatedAt, total, count, services: [ … ] }`. It is cached for five minutes\n(`Cache-Control: max-age=300`), so re-fetching more often buys nothing. There are no query\nparameters and no pagination — `services` is the whole Catalog.\n\n### Fields you will actually use\n\n| Field | Use |\n| --- | --- |\n| `slug` | Stable id. Case-sensitive — how you address the service through the Router |\n| `protocol` | **`x402` or `mpp` = payable through the Router.** See below |\n| `endpoints[]` | `{ path, method, description, priceLabel }`, plus `invokePath`, `requestExample`, `responseFields` on some — see [rule 2](#2-pay-by-slug-and-send-invokepath--path) |\n| `priceLabel` | Human string like `\"$0.001\"`. **Indicative only** — the wire price governs |\n| `network` / `networks` | Display names (`\"Base\"`, `\"Tempo\"`). Not chain ids |\n| `category` | One of the **13 curated values** — see [Categories](#categories) |\n| `subCategory` | Granular label under `category`. **Absent** (no key, not `null`) for the generic top bucket — in JS test `s.subCategory == null`, not `=== null` |\n| `tags[]` | Selection signals |\n| `invokeUrl` | The service's Router URL: `…/api/v1/router/svc/<slug>` |\n| `invoke` | A ready-made Router call: `method`, `router`, `invokeUrl` and the `X-Router-*` headers |\n| `url` | The service's human page in the Catalog |\n\nThe feed deliberately omits health status, long descriptions and the merchant's own URL. For health,\nread the ARD host document (each entry's `nvm:catalog.healthStatus` and `uptime30d` — see\n[below](#the-ard-host-document)); for a request b"},{"path":"references/errors.md","content":"# Errors and guardrails\n\nThe Router signs payments from your wallet in response to instructions written by a merchant nobody\nvetted. It is deliberately suspicious.\n\n**A refusal is the system working.** Before you widen a cap or drop an idempotency key to make an\nerror go away, read what it was protecting you from. An autonomous agent that treats guardrails as\nobstacles is exactly the failure mode this design exists to prevent.\n\n## Every Router code\n\nCodes `0029` and `0031`–`0033` are exposed from API 1.55 onward; older API pins retain the legacy\nunbound quote and service-selection request shapes.\n\n| Code | Status | Meaning | Retry? |\n| --- | --- | --- | --- |\n| `BCK.ROUTER.0001` | 400 | Bad input: unsupported protocol, malformed/empty challenge, no fundable option, recipient outside the Delegation's scope, non-allowlisted asset, wrong-provider Delegation, missing `delegationId`. **`details` names the specific problem — read it** | No |\n| `BCK.ROUTER.0002` | 409 | This `requestId` already minted a payment. The original `paymentId` is in the response | No |\n| `BCK.ROUTER.0003` | 402 | Delegation over cap, expired, exhausted, or revoked | No — **stop** |\n| `BCK.ROUTER.0004` | 404 | No Router payment with that id belongs to you | No |\n| `BCK.ROUTER.0005` | 409 | Payment not settleable. Only `Issued` → `Settled`; same hash is a no-op, a different hash is rejected | No |\n| `BCK.ROUTER.0006` | 500 | Transient failure building the payments summary | **Yes** |\n| `BCK.ROUTER.0007` | 429 | Too many concurrent routed requests in flight | **Yes**, after backoff |\n| `BCK.ROUTER.0008` | 403 | Legacy API key — create a new one | No |\n| `BCK.ROUTER.0009` | 402 | Wallet doesn't hold enough of the asset on the target network. **Nothing was signed** | No — **stop** |\n| `BCK.ROUTER.0010` | 500 | Internal: the rail reported a charge amount that isn't a non-negative integer, so the Router can't reserve anything against the cap | No — **never blind-retry** |\n| `BCK.ROUTER.0011` | 402 | Card rail: the charge needs cardholder 3-D Secure, and an agent has no browser to complete it. Nothing was charged and the seller got no usable credential. | No — **needs a human** |\n| `BCK.ROUTER.0012` | 400 | The seller's 402 advertises an EIP-712 domain its own settlement token does not sign under, so the Router refuses to sign. Nothing signed, charged or reserved — an authorization under the wrong domain is unspendable anyway. Seller-side bug | No — **report it, pay elsewhere** |\n| `BCK.ROUTER.0013` | 500 | Nevermined holds no EIP-712 signing domain for the token the funding filter selected — a gap in OUR canonical table, not the seller's bug and not your request. Nothing signed, charged or reserved | No — **report it to Nevermined** |\n| `BCK.ROUTER.0014` | 409 | The target is a cataloged Nevermined service, whose upstream URL is deliberately hidden. The Router refuses to pay it by raw URL — mode A and a raw mode-B target both put the merchant's host on your wire, defeating the broker"}],"languages":[],"docsSourceLabel":"CLAWHUB","editorialOverview":null,"editorialQuality":{"score":100,"threshold":65,"status":"thin","wordCount":2430,"uniquenessScore":40,"reasons":["uniqueness-below-45"]}},"media":{"evidence":{"source":"no-media","verified":false,"confidence":"low","updatedAt":"2026-10-10T05:04:41.411Z","emptyReason":"No screenshots, media assets, or demo links are available."},"primaryImageUrl":null,"mediaAssetCount":0,"assets":[],"demoUrl":null},"ownerResources":{"evidence":{"source":"unclaimed","verified":false,"confidence":"low","updatedAt":"2026-10-10T05:04:41.411Z","emptyReason":"This page has not been claimed by the agent owner."},"hasCustomPage":false,"customPageUpdatedAt":null,"customLinks":[],"structuredLinks":{"docsUrl":null,"demoUrl":null,"supportUrl":null,"pricingUrl":null,"statusUrl":null},"customPage":null},"relatedAgents":{"evidence":{"source":"protocol-neighbors","verified":false,"confidence":"medium","updatedAt":"2026-10-10T10:44:06.539Z","emptyReason":null},"items":[{"id":"8ebccd8e-3863-4187-8355-c3f14e1f9edf","entityType":"agent","canonicalPath":"/agent/iofficeai-aionui","slug":"iofficeai-aionui","name":"AionUi","description":"Free, local, open-source 24/7 Cowork app and OpenClaw for Gemini CLI, Claude Code, Codex, OpenCode, Qwen Code, Goose CLI, Auggie, and more | 🌟 Star if you like it!","url":"https://github.com/iOfficeAI/AionUi","homepage":"https://www.aionui.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-10-09T19:11:12.944Z","createdAt":"2026-02-25T03:38:16.584Z","downloads":null},{"id":"b917f68a-ebff-438e-84f8-3f4b2494c0bc","entityType":"agent","canonicalPath":"/agent/activepieces-activepieces","slug":"activepieces-activepieces","name":"activepieces","description":"AI Agents & MCPs & AI Workflow Automation • (~400 MCP servers for AI agents) • AI Automation / AI Agent with MCPs • AI Workflows & AI Agents • MCPs for AI Agents","url":"https://github.com/activepieces/activepieces","homepage":"https://www.activepieces.com","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-15T02:22:12.426Z","createdAt":"2026-02-25T03:38:12.412Z","downloads":null},{"id":"5cb26759-3a39-483f-94cf-276a98c13bb8","entityType":"agent","canonicalPath":"/agent/cherryhq-cherry-studio","slug":"cherryhq-cherry-studio","name":"cherry-studio","description":"AI productivity studio with smart chat, autonomous agents, and 300+ assistants. Unified access to frontier LLMs","url":"https://github.com/CherryHQ/cherry-studio","homepage":"https://cherry-ai.com","source":"GITHUB_REPOS","protocols":["MCP","OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-04-11T14:38:40.986Z","createdAt":"2026-02-25T03:38:19.379Z","downloads":null},{"id":"6f6582d0-5d76-4f0f-b81d-86520247950b","entityType":"agent","canonicalPath":"/agent/copilotkit-copilotkit","slug":"copilotkit-copilotkit","name":"CopilotKit","description":"The Frontend for Agents & Generative UI. React + Angular","url":"https://github.com/CopilotKit/CopilotKit","homepage":"https://docs.copilotkit.ai","source":"GITHUB_REPOS","protocols":["OPENCLAW"],"capabilities":[],"safetyScore":100,"overallRank":70,"updatedAt":"2026-03-25T09:50:57.846Z","createdAt":"2026-02-25T03:39:14.617Z","downloads":null}],"links":{"hub":"/agent","source":"/agent/source/clawhub","protocols":[{"label":"OpenClaw","href":"/agent/protocol/openclew"}]}}}