From 8cacd26c5e33b2b381e1fe48d8f0855d32be0273 Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Mon, 3 Aug 2026 15:45:49 +0200 Subject: [PATCH 1/2] fix(skills): add router IDE rule files and stop silent 404 installs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Follow-up to #267, addressing @eruizgar91's REQUEST_CHANGES review, which landed ~8 minutes after that PR merged. HIGH — the install page claimed "every install method below works for either, swap nevermined-payments for nevermined-router in the paths". That held for 2 of 8 tabs. There were no router counterparts under .cursor/rules/, .windsurf/rules/, .clinerules/ or .amazonq/rules/, and Copilot/Codex load a single file with no skill-name segment to swap at all. The failure was silent: every curl on the page used -o with no --fail, so a 404 exits 0 and writes the literal string "404: Not Found" into the rule file. Cursor then loads that as a valid rule and the assistant has zero Router knowledge, with nothing anywhere saying so. Reproduced before fixing: curl -o -> exit 0, file contains "404: Not Found"; curl --fail -> exit 22, no file. Rather than retract the sentence, made it true: - Adds condensed router rules for Cursor, Windsurf, Cline and Amazon Q, all generated from one shared body so they cannot drift, and all under the 6,000-char Windsurf limit the page documents (5,292-5,457; the first draft was 6,057 and had to be trimmed). - Scopes the claim honestly for Copilot and Codex CLI, which genuinely cannot be swapped, and points those users at the full skill. - Adds --fail to all six curl commands, and router download commands to the four tabs that support them. - Updates the "Supported Tools at a Glance" table, which still listed payments-only paths and contradicted the new claim two screens later. Root cause of the missing Cursor file, caught pre-commit: .gitignore had a blanket `/.cursor`, so the new rule would have been silently left out of the commit and shipped as the very 404 this PR fixes. nevermined-payments.mdc only survives it because it was tracked before the rule existed. Narrowed to `/.cursor/*` + `!/.cursor/rules/` so published rules track normally while local Cursor state stays ignored; verified both directions. LOW — the mode A worked example paired a v1 request with a v2 response, teaching the wrong version->header mapping in the one step where the header name decides whether an already-minted, already-reserved payment is accepted. Now a coherent v1 round trip, with a line noting what v2 returns instead. Also, per @aaitor on #267: drops the one-shot `version: "0.1.0"` seed now that the first publish has happened, so auto-increment takes over (leaving it would re-try 0.1.0 forever, hit the "already published" no-op, and freeze the skill). Bumps the skill to 0.1.1 to match. The SKILL_VERSION plumbing stays for the next new slug. And @eruizgar91's noted tension on the Exa paragraph: it now points at the out-of-band rail (/integrations/exa, a $7 card-delegation purchase of Exa API credits) instead of dead-ending at "tell the user it needs an account". Verified: every raw.githubusercontent URL on the install page resolves to a real, committable file (10/10); mintlify broken-links clean; workflow YAML parses with both matrix legs on auto-increment. Refs nevermined-io/nvm-monorepo#2595 Co-Authored-By: Claude Opus 5 (1M context) --- .amazonq/rules/nevermined-router.md | 118 ++++++++++++++++ .clinerules/nevermined-router.md | 127 ++++++++++++++++++ .cursor/rules/nevermined-router.mdc | 123 +++++++++++++++++ .github/workflows/publish-skill-clawhub.yml | 4 - .gitignore | 9 +- .windsurf/rules/nevermined-router.md | 118 ++++++++++++++++ development-guide/build-using-nvm-skill.mdx | 56 +++++--- skills/nevermined-router/SKILL.md | 6 +- skills/nevermined-router/references/paying.md | 10 +- 9 files changed, 544 insertions(+), 27 deletions(-) create mode 100644 .amazonq/rules/nevermined-router.md create mode 100644 .clinerules/nevermined-router.md create mode 100644 .cursor/rules/nevermined-router.mdc create mode 100644 .windsurf/rules/nevermined-router.md diff --git a/.amazonq/rules/nevermined-router.md b/.amazonq/rules/nevermined-router.md new file mode 100644 index 00000000..039c2619 --- /dev/null +++ b/.amazonq/rules/nevermined-router.md @@ -0,0 +1,118 @@ +# Nevermined Router — paying external services + +You are writing an agent that must **pay** an external service it has no account with — any x402 +agent or MPP merchant — through the Nevermined Router. If you are *receiving* payments or buying a +Nevermined plan, use the `nevermined-payments` rules instead. + +Full skill: https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +Docs: https://nevermined.ai/docs/products/router/overview + +## When the Router does NOT apply + +It pays a price quoted **on the wire** for one request. A conventional SaaS API (Exa, Firecrawl, +Tavily…) is billed out of band by a monthly plan and a long-lived key, so it never quotes a +per-request price — there is nothing to pay and no address to pay it to. + +**A service answering `401`/`403` rather than `402` wants authentication, not payment.** Say so; +don't try to route it. + +## Environment + +- `NVM_API_URL` — `https://api.sandbox.nevermined.app` or `https://api.live.nevermined.app` +- `NVM_API_KEY` — `sandbox:…` / `live:…`. **Never send it to the merchant**; it authenticates you to + Nevermined only. The merchant's own auth goes in `headers`. +- `NVM_DELEGATION_ID` — the spending Delegation + +## 1. Create a Delegation (the budget) + +```bash +curl -sX POST "$NVM_API_URL/api/v1/delegation/create" \ + -H "Authorization: Bearer $NVM_API_KEY" -H "Content-Type: application/json" \ + -d '{"provider":"erc4337","currency":"usdc","spendingLimitCents":500,"durationSecs":604800}' +``` + +All four fields are required — no defaults. `erc4337` is the crypto-funded Delegation both +stablecoin rails need. + +## 2. Fund the buyer wallet + +Both rails **pull** from your own custodial wallet: a Delegation authorizes a spend, it does not +supply funds. Read the address from the live Delegation every time — +`GET /api/v1/delegation/{id}` → `providerPaymentMethodId`. + +**Never reuse a cached address.** A stale one is the most common cause of `402 BCK.ROUTER.0009`, and +that error deliberately doesn't echo the address it checked. + +## 3. Discover (public, no API key) + +```bash +curl -s "$NVM_API_URL/api/v1/catalog/services?protocol=x402&search=web+search" +``` + +- **Only `protocol` of `x402` or `mpp` is routable.** Filter for them. +- **`targetUrl` is the default endpoint's COMPLETE URL, not a base** — it may already contain the + path (`https://host/search` while `endpoints[0].path` is also `/search`), so concatenating yields + `/search/search`. Resolve instead: + +```ts +const url = endpoint ? new URL(endpoint.path, service.targetUrl).toString() : service.targetUrl +``` + +- `offset` is the page **size**, not a skip count, and default ordering is shuffled within each + tier — never assume `services[0]` is stable. + +## 4. Pay + +```ts +const res = await fetch(`${process.env.NVM_API_URL}/api/v1/router/route`, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.NVM_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + delegationId: process.env.NVM_DELEGATION_ID, + url: 'https://service.example/api/resource', + method: 'POST', + body: { query: '…' }, + requestId: 'search-nevermined-router-v1', // stable per PURCHASE, not per attempt + }), +}) +const { status, body, paid, payment } = await res.json() +``` + +The Router probes, auto-detects the protocol from the 402, pays and relays. `status`/`body` are the +merchant's own; `paid: false` with no `payment` means it was free — handle that. For streaming use +`ALL /api/v1/router/proxy` with `X-Router-Target-Url`, `X-Router-Delegation-Id` and +`X-Router-Request-Id` headers. + +### `requestId` is an idempotency key, not a request counter + +**A fresh `uuid4()` per HTTP attempt is how an agent double-spends.** Use one stable id per logical +purchase, reused across retries of it: the same id returns the original payment, a fresh id buys +again. Derive it from the work being done. + +### Money arithmetic + +Budget is debited in **whole cents, rounded up** — 1000 calls at $0.001 costs **$10.00, not $1.00**. +`settlement.approxCents` is what was actually reserved; trust it over any catalog `priceLabel`. + +## Guardrails — a refusal is the system working + +- `BCK.ROUTER.0003` (402) — Delegation over cap, expired, exhausted or revoked. **Stop.** +- `BCK.ROUTER.0009` (402) — wallet short on the target network; nothing was signed. **Stop.** +- `BCK.ROUTER.0002` (409) — `requestId` already used; the original `paymentId` is in the response. +- `BCK.ROUTER.0001` (400) — bad input / no fundable option / non-allowlisted asset; `details` names it. +- `BCK.ROUTER.0008` (403) — legacy API key; create a new one. +- Only `BCK.ROUTER.0006` (500) and `0007` (429, too many concurrent) are **retryable**. Everything + else is a decision — retrying unchanged gives the same answer. + +**Never widen a Delegation, and never create a second one, to get past a refusal.** The cap is the +user's decision, not a runtime obstacle; minting a fresh Delegation to escape an exhausted one +defeats the whole mechanism. Report and stop. + +## Accounting + +`GET /api/v1/router/payments` (filters `delegationId`, `from`, `to`, `format=csv`) and +`/payments/summary`. `amount` is in the asset's smallest unit, not cents. A record at `Issued` is +**not** an error — the money moved; do not retry it. diff --git a/.clinerules/nevermined-router.md b/.clinerules/nevermined-router.md new file mode 100644 index 00000000..a1897952 --- /dev/null +++ b/.clinerules/nevermined-router.md @@ -0,0 +1,127 @@ +--- +paths: + - "**/*.ts" + - "**/*.tsx" + - "**/*.js" + - "**/*.jsx" + - "**/*.py" +--- + +# Nevermined Router — paying external services + +You are writing an agent that must **pay** an external service it has no account with — any x402 +agent or MPP merchant — through the Nevermined Router. If you are *receiving* payments or buying a +Nevermined plan, use the `nevermined-payments` rules instead. + +Full skill: https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +Docs: https://nevermined.ai/docs/products/router/overview + +## When the Router does NOT apply + +It pays a price quoted **on the wire** for one request. A conventional SaaS API (Exa, Firecrawl, +Tavily…) is billed out of band by a monthly plan and a long-lived key, so it never quotes a +per-request price — there is nothing to pay and no address to pay it to. + +**A service answering `401`/`403` rather than `402` wants authentication, not payment.** Say so; +don't try to route it. + +## Environment + +- `NVM_API_URL` — `https://api.sandbox.nevermined.app` or `https://api.live.nevermined.app` +- `NVM_API_KEY` — `sandbox:…` / `live:…`. **Never send it to the merchant**; it authenticates you to + Nevermined only. The merchant's own auth goes in `headers`. +- `NVM_DELEGATION_ID` — the spending Delegation + +## 1. Create a Delegation (the budget) + +```bash +curl -sX POST "$NVM_API_URL/api/v1/delegation/create" \ + -H "Authorization: Bearer $NVM_API_KEY" -H "Content-Type: application/json" \ + -d '{"provider":"erc4337","currency":"usdc","spendingLimitCents":500,"durationSecs":604800}' +``` + +All four fields are required — no defaults. `erc4337` is the crypto-funded Delegation both +stablecoin rails need. + +## 2. Fund the buyer wallet + +Both rails **pull** from your own custodial wallet: a Delegation authorizes a spend, it does not +supply funds. Read the address from the live Delegation every time — +`GET /api/v1/delegation/{id}` → `providerPaymentMethodId`. + +**Never reuse a cached address.** A stale one is the most common cause of `402 BCK.ROUTER.0009`, and +that error deliberately doesn't echo the address it checked. + +## 3. Discover (public, no API key) + +```bash +curl -s "$NVM_API_URL/api/v1/catalog/services?protocol=x402&search=web+search" +``` + +- **Only `protocol` of `x402` or `mpp` is routable.** Filter for them. +- **`targetUrl` is the default endpoint's COMPLETE URL, not a base** — it may already contain the + path (`https://host/search` while `endpoints[0].path` is also `/search`), so concatenating yields + `/search/search`. Resolve instead: + +```ts +const url = endpoint ? new URL(endpoint.path, service.targetUrl).toString() : service.targetUrl +``` + +- `offset` is the page **size**, not a skip count, and default ordering is shuffled within each + tier — never assume `services[0]` is stable. + +## 4. Pay + +```ts +const res = await fetch(`${process.env.NVM_API_URL}/api/v1/router/route`, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.NVM_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + delegationId: process.env.NVM_DELEGATION_ID, + url: 'https://service.example/api/resource', + method: 'POST', + body: { query: '…' }, + requestId: 'search-nevermined-router-v1', // stable per PURCHASE, not per attempt + }), +}) +const { status, body, paid, payment } = await res.json() +``` + +The Router probes, auto-detects the protocol from the 402, pays and relays. `status`/`body` are the +merchant's own; `paid: false` with no `payment` means it was free — handle that. For streaming use +`ALL /api/v1/router/proxy` with `X-Router-Target-Url`, `X-Router-Delegation-Id` and +`X-Router-Request-Id` headers. + +### `requestId` is an idempotency key, not a request counter + +**A fresh `uuid4()` per HTTP attempt is how an agent double-spends.** Use one stable id per logical +purchase, reused across retries of it: the same id returns the original payment, a fresh id buys +again. Derive it from the work being done. + +### Money arithmetic + +Budget is debited in **whole cents, rounded up** — 1000 calls at $0.001 costs **$10.00, not $1.00**. +`settlement.approxCents` is what was actually reserved; trust it over any catalog `priceLabel`. + +## Guardrails — a refusal is the system working + +- `BCK.ROUTER.0003` (402) — Delegation over cap, expired, exhausted or revoked. **Stop.** +- `BCK.ROUTER.0009` (402) — wallet short on the target network; nothing was signed. **Stop.** +- `BCK.ROUTER.0002` (409) — `requestId` already used; the original `paymentId` is in the response. +- `BCK.ROUTER.0001` (400) — bad input / no fundable option / non-allowlisted asset; `details` names it. +- `BCK.ROUTER.0008` (403) — legacy API key; create a new one. +- Only `BCK.ROUTER.0006` (500) and `0007` (429, too many concurrent) are **retryable**. Everything + else is a decision — retrying unchanged gives the same answer. + +**Never widen a Delegation, and never create a second one, to get past a refusal.** The cap is the +user's decision, not a runtime obstacle; minting a fresh Delegation to escape an exhausted one +defeats the whole mechanism. Report and stop. + +## Accounting + +`GET /api/v1/router/payments` (filters `delegationId`, `from`, `to`, `format=csv`) and +`/payments/summary`. `amount` is in the asset's smallest unit, not cents. A record at `Issued` is +**not** an error — the money moved; do not retry it. diff --git a/.cursor/rules/nevermined-router.mdc b/.cursor/rules/nevermined-router.mdc new file mode 100644 index 00000000..30b6992b --- /dev/null +++ b/.cursor/rules/nevermined-router.mdc @@ -0,0 +1,123 @@ +--- +description: Nevermined Router — pay any external x402 or MPP service from a capped Delegation +globs: ["*.ts", "*.tsx", "*.js", "*.jsx", "*.py", "*.mjs"] +--- + +# Nevermined Router — paying external services + +You are writing an agent that must **pay** an external service it has no account with — any x402 +agent or MPP merchant — through the Nevermined Router. If you are *receiving* payments or buying a +Nevermined plan, use the `nevermined-payments` rules instead. + +Full skill: https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +Docs: https://nevermined.ai/docs/products/router/overview + +## When the Router does NOT apply + +It pays a price quoted **on the wire** for one request. A conventional SaaS API (Exa, Firecrawl, +Tavily…) is billed out of band by a monthly plan and a long-lived key, so it never quotes a +per-request price — there is nothing to pay and no address to pay it to. + +**A service answering `401`/`403` rather than `402` wants authentication, not payment.** Say so; +don't try to route it. + +## Environment + +- `NVM_API_URL` — `https://api.sandbox.nevermined.app` or `https://api.live.nevermined.app` +- `NVM_API_KEY` — `sandbox:…` / `live:…`. **Never send it to the merchant**; it authenticates you to + Nevermined only. The merchant's own auth goes in `headers`. +- `NVM_DELEGATION_ID` — the spending Delegation + +## 1. Create a Delegation (the budget) + +```bash +curl -sX POST "$NVM_API_URL/api/v1/delegation/create" \ + -H "Authorization: Bearer $NVM_API_KEY" -H "Content-Type: application/json" \ + -d '{"provider":"erc4337","currency":"usdc","spendingLimitCents":500,"durationSecs":604800}' +``` + +All four fields are required — no defaults. `erc4337` is the crypto-funded Delegation both +stablecoin rails need. + +## 2. Fund the buyer wallet + +Both rails **pull** from your own custodial wallet: a Delegation authorizes a spend, it does not +supply funds. Read the address from the live Delegation every time — +`GET /api/v1/delegation/{id}` → `providerPaymentMethodId`. + +**Never reuse a cached address.** A stale one is the most common cause of `402 BCK.ROUTER.0009`, and +that error deliberately doesn't echo the address it checked. + +## 3. Discover (public, no API key) + +```bash +curl -s "$NVM_API_URL/api/v1/catalog/services?protocol=x402&search=web+search" +``` + +- **Only `protocol` of `x402` or `mpp` is routable.** Filter for them. +- **`targetUrl` is the default endpoint's COMPLETE URL, not a base** — it may already contain the + path (`https://host/search` while `endpoints[0].path` is also `/search`), so concatenating yields + `/search/search`. Resolve instead: + +```ts +const url = endpoint ? new URL(endpoint.path, service.targetUrl).toString() : service.targetUrl +``` + +- `offset` is the page **size**, not a skip count, and default ordering is shuffled within each + tier — never assume `services[0]` is stable. + +## 4. Pay + +```ts +const res = await fetch(`${process.env.NVM_API_URL}/api/v1/router/route`, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.NVM_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + delegationId: process.env.NVM_DELEGATION_ID, + url: 'https://service.example/api/resource', + method: 'POST', + body: { query: '…' }, + requestId: 'search-nevermined-router-v1', // stable per PURCHASE, not per attempt + }), +}) +const { status, body, paid, payment } = await res.json() +``` + +The Router probes, auto-detects the protocol from the 402, pays and relays. `status`/`body` are the +merchant's own; `paid: false` with no `payment` means it was free — handle that. For streaming use +`ALL /api/v1/router/proxy` with `X-Router-Target-Url`, `X-Router-Delegation-Id` and +`X-Router-Request-Id` headers. + +### `requestId` is an idempotency key, not a request counter + +**A fresh `uuid4()` per HTTP attempt is how an agent double-spends.** Use one stable id per logical +purchase, reused across retries of it: the same id returns the original payment, a fresh id buys +again. Derive it from the work being done. + +### Money arithmetic + +Budget is debited in **whole cents, rounded up** — 1000 calls at $0.001 costs **$10.00, not $1.00**. +`settlement.approxCents` is what was actually reserved; trust it over any catalog `priceLabel`. + +## Guardrails — a refusal is the system working + +- `BCK.ROUTER.0003` (402) — Delegation over cap, expired, exhausted or revoked. **Stop.** +- `BCK.ROUTER.0009` (402) — wallet short on the target network; nothing was signed. **Stop.** +- `BCK.ROUTER.0002` (409) — `requestId` already used; the original `paymentId` is in the response. +- `BCK.ROUTER.0001` (400) — bad input / no fundable option / non-allowlisted asset; `details` names it. +- `BCK.ROUTER.0008` (403) — legacy API key; create a new one. +- Only `BCK.ROUTER.0006` (500) and `0007` (429, too many concurrent) are **retryable**. Everything + else is a decision — retrying unchanged gives the same answer. + +**Never widen a Delegation, and never create a second one, to get past a refusal.** The cap is the +user's decision, not a runtime obstacle; minting a fresh Delegation to escape an exhausted one +defeats the whole mechanism. Report and stop. + +## Accounting + +`GET /api/v1/router/payments` (filters `delegationId`, `from`, `to`, `format=csv`) and +`/payments/summary`. `amount` is in the asset's smallest unit, not cents. A record at `Issued` is +**not** an error — the money moved; do not retry it. diff --git a/.github/workflows/publish-skill-clawhub.yml b/.github/workflows/publish-skill-clawhub.yml index 7dbbab8e..35a443f8 100644 --- a/.github/workflows/publish-skill-clawhub.yml +++ b/.github/workflows/publish-skill-clawhub.yml @@ -57,10 +57,6 @@ jobs: - path: skills/nevermined-router slug: nevermined-router name: Nevermined Router - # First publish of a new slug only — pins it to the frontmatter - # version instead of the CLI's 1.0.0 default. Delete this line once - # it has published once; auto-increment takes over from there. - version: "0.1.0" env: SKILL_PATH: ${{ matrix.path }} OWNER: nevermined-io diff --git a/.gitignore b/.gitignore index 52f27b26..4ce65a4a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,10 @@ -# Cursor -/.cursor +# Cursor — local editor state, EXCEPT `.cursor/rules/`, which is published +# content: the install page tells users to curl it from `main`, so a rule file +# that silently fails to commit becomes a 404 (and, without `--fail`, writes +# "404: Not Found" into their rules directory). `nevermined-payments.mdc` only +# survived the blanket `/.cursor` because it was tracked before the rule existed. +/.cursor/* +!/.cursor/rules/ # Node node_modules diff --git a/.windsurf/rules/nevermined-router.md b/.windsurf/rules/nevermined-router.md new file mode 100644 index 00000000..039c2619 --- /dev/null +++ b/.windsurf/rules/nevermined-router.md @@ -0,0 +1,118 @@ +# Nevermined Router — paying external services + +You are writing an agent that must **pay** an external service it has no account with — any x402 +agent or MPP merchant — through the Nevermined Router. If you are *receiving* payments or buying a +Nevermined plan, use the `nevermined-payments` rules instead. + +Full skill: https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +Docs: https://nevermined.ai/docs/products/router/overview + +## When the Router does NOT apply + +It pays a price quoted **on the wire** for one request. A conventional SaaS API (Exa, Firecrawl, +Tavily…) is billed out of band by a monthly plan and a long-lived key, so it never quotes a +per-request price — there is nothing to pay and no address to pay it to. + +**A service answering `401`/`403` rather than `402` wants authentication, not payment.** Say so; +don't try to route it. + +## Environment + +- `NVM_API_URL` — `https://api.sandbox.nevermined.app` or `https://api.live.nevermined.app` +- `NVM_API_KEY` — `sandbox:…` / `live:…`. **Never send it to the merchant**; it authenticates you to + Nevermined only. The merchant's own auth goes in `headers`. +- `NVM_DELEGATION_ID` — the spending Delegation + +## 1. Create a Delegation (the budget) + +```bash +curl -sX POST "$NVM_API_URL/api/v1/delegation/create" \ + -H "Authorization: Bearer $NVM_API_KEY" -H "Content-Type: application/json" \ + -d '{"provider":"erc4337","currency":"usdc","spendingLimitCents":500,"durationSecs":604800}' +``` + +All four fields are required — no defaults. `erc4337` is the crypto-funded Delegation both +stablecoin rails need. + +## 2. Fund the buyer wallet + +Both rails **pull** from your own custodial wallet: a Delegation authorizes a spend, it does not +supply funds. Read the address from the live Delegation every time — +`GET /api/v1/delegation/{id}` → `providerPaymentMethodId`. + +**Never reuse a cached address.** A stale one is the most common cause of `402 BCK.ROUTER.0009`, and +that error deliberately doesn't echo the address it checked. + +## 3. Discover (public, no API key) + +```bash +curl -s "$NVM_API_URL/api/v1/catalog/services?protocol=x402&search=web+search" +``` + +- **Only `protocol` of `x402` or `mpp` is routable.** Filter for them. +- **`targetUrl` is the default endpoint's COMPLETE URL, not a base** — it may already contain the + path (`https://host/search` while `endpoints[0].path` is also `/search`), so concatenating yields + `/search/search`. Resolve instead: + +```ts +const url = endpoint ? new URL(endpoint.path, service.targetUrl).toString() : service.targetUrl +``` + +- `offset` is the page **size**, not a skip count, and default ordering is shuffled within each + tier — never assume `services[0]` is stable. + +## 4. Pay + +```ts +const res = await fetch(`${process.env.NVM_API_URL}/api/v1/router/route`, { + method: 'POST', + headers: { + Authorization: `Bearer ${process.env.NVM_API_KEY}`, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + delegationId: process.env.NVM_DELEGATION_ID, + url: 'https://service.example/api/resource', + method: 'POST', + body: { query: '…' }, + requestId: 'search-nevermined-router-v1', // stable per PURCHASE, not per attempt + }), +}) +const { status, body, paid, payment } = await res.json() +``` + +The Router probes, auto-detects the protocol from the 402, pays and relays. `status`/`body` are the +merchant's own; `paid: false` with no `payment` means it was free — handle that. For streaming use +`ALL /api/v1/router/proxy` with `X-Router-Target-Url`, `X-Router-Delegation-Id` and +`X-Router-Request-Id` headers. + +### `requestId` is an idempotency key, not a request counter + +**A fresh `uuid4()` per HTTP attempt is how an agent double-spends.** Use one stable id per logical +purchase, reused across retries of it: the same id returns the original payment, a fresh id buys +again. Derive it from the work being done. + +### Money arithmetic + +Budget is debited in **whole cents, rounded up** — 1000 calls at $0.001 costs **$10.00, not $1.00**. +`settlement.approxCents` is what was actually reserved; trust it over any catalog `priceLabel`. + +## Guardrails — a refusal is the system working + +- `BCK.ROUTER.0003` (402) — Delegation over cap, expired, exhausted or revoked. **Stop.** +- `BCK.ROUTER.0009` (402) — wallet short on the target network; nothing was signed. **Stop.** +- `BCK.ROUTER.0002` (409) — `requestId` already used; the original `paymentId` is in the response. +- `BCK.ROUTER.0001` (400) — bad input / no fundable option / non-allowlisted asset; `details` names it. +- `BCK.ROUTER.0008` (403) — legacy API key; create a new one. +- Only `BCK.ROUTER.0006` (500) and `0007` (429, too many concurrent) are **retryable**. Everything + else is a decision — retrying unchanged gives the same answer. + +**Never widen a Delegation, and never create a second one, to get past a refusal.** The cap is the +user's decision, not a runtime obstacle; minting a fresh Delegation to escape an exhausted one +defeats the whole mechanism. Report and stop. + +## Accounting + +`GET /api/v1/router/payments` (filters `delegationId`, `from`, `to`, `format=csv`) and +`/payments/summary`. `amount` is in the asset's smallest unit, not cents. A record at `Issued` is +**not** an error — the money moved; do not retry it. diff --git a/development-guide/build-using-nvm-skill.mdx b/development-guide/build-using-nvm-skill.mdx index 89ea7cee..c14863a9 100644 --- a/development-guide/build-using-nvm-skill.mdx +++ b/development-guide/build-using-nvm-skill.mdx @@ -17,7 +17,10 @@ There are two, and which one you want depends on which side of the payment you'r | **`nevermined-payments`** | **receiving** money, or buying a Nevermined plan | SDK integration, middleware, plans, credits — the rest of this page | | **`nevermined-router`** | **spending** money at external services | Discovering services in the catalog, creating a spending Delegation, paying any x402 or MPP endpoint through the [Nevermined Router](/products/router/overview) | -Every install method below works for either — swap `nevermined-payments` for `nevermined-router` in the paths. Installing both is fine; they don't overlap. +Installing both is fine — they don't overlap. How you install the Router one depends on your tool: + +- **Claude Code, Cursor, Windsurf, Cline, Amazon Q** — swap `nevermined-payments` for `nevermined-router` in the paths below, or run the extra command shown in each tab. +- **GitHub Copilot and Codex CLI** — these load a *single* file (`.github/copilot-instructions.md`, `AGENTS.md`) with no skill name in the path, so there is nothing to swap. Both files currently carry the **payments** guidance only. Until that changes, use the [full skill on GitHub](https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router) or paste it in manually. ## What's Included @@ -46,9 +49,10 @@ Choose your AI coding tool below for step-by-step installation instructions. tmpdir="$(mktemp -d)" git clone --depth 1 --filter=blob:none --sparse https://github.com/nevermined-io/docs "$tmpdir" cd "$tmpdir" - git sparse-checkout set skills/nevermined-payments + git sparse-checkout set skills/nevermined-payments skills/nevermined-router cp -R skills/nevermined-payments ~/.claude/skills/ + cp -R skills/nevermined-router ~/.claude/skills/ # buying through the Router cd - rm -rf "$tmpdir" @@ -92,8 +96,12 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .cursor/rules - curl -o .cursor/rules/nevermined-payments.mdc \ + curl --fail -o .cursor/rules/nevermined-payments.mdc \ https://raw.githubusercontent.com/nevermined-io/docs/main/.cursor/rules/nevermined-payments.mdc + + # Buying from external x402 / MPP services through the Router + curl --fail -o .cursor/rules/nevermined-router.mdc \ + https://raw.githubusercontent.com/nevermined-io/docs/main/.cursor/rules/nevermined-router.mdc ``` The `.cursor/rules/*.mdc` format supports glob-based activation — the Nevermined rule auto-activates when you work with `.ts`, `.js`, or `.py` files. @@ -106,8 +114,12 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .windsurf/rules - curl -o .windsurf/rules/nevermined-payments.md \ + curl --fail -o .windsurf/rules/nevermined-payments.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.windsurf/rules/nevermined-payments.md + + # Buying from external x402 / MPP services through the Router + curl --fail -o .windsurf/rules/nevermined-router.md \ + https://raw.githubusercontent.com/nevermined-io/docs/main/.windsurf/rules/nevermined-router.md ``` Windsurf automatically discovers rules from `.windsurf/rules/` and applies them based on their activation mode. @@ -126,7 +138,7 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .github - curl -o .github/copilot-instructions.md \ + curl --fail -o .github/copilot-instructions.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.github/copilot-instructions.md ``` @@ -151,7 +163,7 @@ Choose your AI coding tool below for step-by-step installation instructions. Codex CLI reads `AGENTS.md` files for project context. Add it to your project: ```bash - curl -o AGENTS.md \ + curl --fail -o AGENTS.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/AGENTS.md ``` @@ -174,8 +186,12 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .clinerules - curl -o .clinerules/nevermined-payments.md \ + curl --fail -o .clinerules/nevermined-payments.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.clinerules/nevermined-payments.md + + # Buying from external x402 / MPP services through the Router + curl --fail -o .clinerules/nevermined-router.md \ + https://raw.githubusercontent.com/nevermined-io/docs/main/.clinerules/nevermined-router.md ``` Cline discovers all `.md` files in the `.clinerules/` directory and applies them based on the `paths` frontmatter. The Nevermined rule activates for `.ts`, `.js`, and `.py` files. @@ -190,8 +206,12 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .amazonq/rules - curl -o .amazonq/rules/nevermined-payments.md \ + curl --fail -o .amazonq/rules/nevermined-payments.md \ https://raw.githubusercontent.com/nevermined-io/docs/main/.amazonq/rules/nevermined-payments.md + + # Buying from external x402 / MPP services through the Router + curl --fail -o .amazonq/rules/nevermined-router.md \ + https://raw.githubusercontent.com/nevermined-io/docs/main/.amazonq/rules/nevermined-router.md ``` Amazon Q automatically uses rules from `.amazonq/rules/` as context. You can toggle rules on/off via the Rules button in the Amazon Q chat panel. @@ -212,15 +232,17 @@ Choose your AI coding tool below for step-by-step installation instructions. ## Supported Tools at a Glance -| Tool | Config File | Auto-Activates | -|---|---|---| -| **Claude Code** | `~/.claude/skills/nevermined-payments/SKILL.md` | Yes — when relevant | -| **Cursor** | `.cursor/rules/nevermined-payments.mdc` | Yes — on `.ts`, `.js`, `.py` files | -| **Windsurf** | `.windsurf/rules/nevermined-payments.md` | Yes — model decides | -| **GitHub Copilot** | `.github/copilot-instructions.md` | Yes — always | -| **Codex CLI** | `AGENTS.md` or `.agents/skills/nevermined-payments/` | Yes — always or when relevant | -| **Cline** | `.clinerules/nevermined-payments.md` | Yes — on `.ts`, `.js`, `.py` files | -| **Amazon Q** | `.amazonq/rules/nevermined-payments.md` | Yes — always | +`` is `nevermined-payments` or `nevermined-router` — install either or both. + +| Tool | Config File | Router available? | Auto-Activates | +|---|---|---|---| +| **Claude Code** | `~/.claude/skills//SKILL.md` | Yes | Yes — when relevant | +| **Cursor** | `.cursor/rules/.mdc` | Yes | Yes — on `.ts`, `.js`, `.py` files | +| **Windsurf** | `.windsurf/rules/.md` | Yes | Yes — model decides | +| **GitHub Copilot** | `.github/copilot-instructions.md` | **No** — single file, payments only | Yes — always | +| **Codex CLI** | `AGENTS.md` or `.agents/skills//` | **No** via `AGENTS.md` — single file, payments only | Yes — always or when relevant | +| **Cline** | `.clinerules/.md` | Yes | Yes — on `.ts`, `.js`, `.py` files | +| **Amazon Q** | `.amazonq/rules/.md` | Yes | Yes — always | ## Example Prompts diff --git a/skills/nevermined-router/SKILL.md b/skills/nevermined-router/SKILL.md index bf5b25a1..ff3546d2 100644 --- a/skills/nevermined-router/SKILL.md +++ b/skills/nevermined-router/SKILL.md @@ -1,6 +1,6 @@ --- name: nevermined-router -version: "0.1.0" +version: "0.1.1" lastUpdated: "2026-08-03" description: > Use when an AI agent needs to PAY an external service it does not have an account @@ -28,7 +28,7 @@ metadata: # Nevermined Router — buy from any x402 or MPP service -> **Skill version**: 0.1.0 | **Last updated**: 2026-08-03 | **Canonical source (always latest):** https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +> **Skill version**: 0.1.1 | **Last updated**: 2026-08-03 | **Canonical source (always latest):** https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router > > **⚠️ Use the latest version.** If you have a cached copy, check its **Last updated** date against the canonical source and refresh if older. > @@ -48,6 +48,8 @@ It works because a growing set of services quote their price **on the wire**: yo **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. +That 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`. + ## The buy loop Six steps. Steps 1–3 happen once; 4–6 repeat per purchase. diff --git a/skills/nevermined-router/references/paying.md b/skills/nevermined-router/references/paying.md index 027ea5e7..4dceffb4 100644 --- a/skills/nevermined-router/references/paying.md +++ b/skills/nevermined-router/references/paying.md @@ -177,18 +177,24 @@ Call the merchant with no payment. It answers `402` with its requirements: Pass `target` **verbatim** from the 402. Do not normalise, reorder or re-encode it. +The response echoes the negotiated version, and `credential.name` follows from it — the request +above is v1, so this one comes back v1 with the v1 header: + ```json { "paymentId": "b1f9c2e4-…", "protocol": "x402", - "x402Version": 2, - "credential": { "transport": "header", "name": "PAYMENT-SIGNATURE", "value": "eyJ4NDAy…" }, + "x402Version": 1, + "credential": { "transport": "header", "name": "X-PAYMENT", "value": "eyJ4NDAy…" }, "settlement": { "recipient": "0x2096…", "amount": "1000", "asset": "USDC", "network": "base", "approxCents": "1" }, "status": "Issued" } ``` +Had you passed a v2 `target` (the default), the same call would return `"x402Version": 2` and +`"name": "PAYMENT-SIGNATURE"`. + ### 3 · Attach it and re-send Set an HTTP header named **`credential.name`** to **`credential.value`** on your original request From 7db844622a76edc53e720d1b7b279bc74c661a1d Mon Sep 17 00:00:00 2001 From: Rodolphe Marques Date: Tue, 4 Aug 2026 10:58:59 +0200 Subject: [PATCH 2/2] docs(skills): fold in @eruizgar91's discarded-but-correct review points MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit He approved and explicitly rated these below the blocking bar. Two are genuine inaccuracies in prose I wrote, so they're worth fixing rather than shipping. 1. Codex CLI is NOT "nothing to swap". Verified: its tab already documents an `.agents/skills/` install, which takes a skill DIRECTORY. Only Copilot is truly single-file. Split the bullet, added the router `cp` line to that tab, and corrected the glance table from "No" to "Yes — via `.agents/skills/`; not via AGENTS.md". 2. `lastUpdated` now moves with the version (0.1.1 / 2026-08-04), matching how the sibling skill treats the pair. Also fixes the stale IDE-surface inventory in CLAUDE.md that he flagged as out-of-scope. It named a `.cursorrules` that does not exist and omitted `.windsurf/rules/`, `.clinerules/` and `.amazonq/rules/` entirely — the same rule I cited when justifying this PR, so leaving it wrong would mislead the next person the same way. Replaced with a verified table (both skills present in all four rule dirs; Copilot + AGENTS.md single-file), the `.gitignore` re-include rationale, and a pointer to keep the install page and its glance table in sync. Also corrects "7 reference files" — payments has 12, router has 5. Every number and path in that table was checked against the tree, not carried over. Deliberately NOT folded in, agreeing with his assessment: the condensed rule files stop at the Exa dead-end without the out-of-band off-ramp (an omission in a size-capped surface, and Windsurf is already at 5,240 of 6,000 chars), and the CLAUDE.md v2-headers rule governs the Payments SDK server surface rather than the Router paying a merchant whose version is the merchant's choice. mintlify broken-links: clean. Co-Authored-By: Claude Opus 5 (1M context) --- CLAUDE.md | 20 +++++++++++++++++++- development-guide/build-using-nvm-skill.mdx | 6 ++++-- skills/nevermined-router/SKILL.md | 4 ++-- 3 files changed, 25 insertions(+), 5 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index c7ac76d0..a8a3dc45 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -28,7 +28,25 @@ This document provides comprehensive guidelines for AI agents to write and maint ### AI Coding Skill -This repository includes a **Nevermined AI Skill** at `skills/nevermined-payments/SKILL.md` with 7 reference files. This skill provides AI coding assistants (Claude Code, Cursor, Copilot, ChatGPT) with SDK patterns and integration knowledge. When updating SDK code examples, framework integrations, or x402 protocol documentation, also update the corresponding skill reference files in `skills/nevermined-payments/references/` to keep them in sync. IDE-specific files (`.cursorrules`, `.cursor/rules/`, `.github/copilot-instructions.md`) contain condensed versions of the skill and should be updated when core patterns change. +This repository publishes **two** Nevermined AI Skills, pointed in opposite directions: + +- `skills/nevermined-payments/` — **receiving** payments (SDK, middleware, plans, credits), 12 reference files. +- `skills/nevermined-router/` — **spending** at external x402 / MPP services through the Nevermined Router, 5 reference files. + +They give AI coding assistants (Claude Code, Cursor, Copilot, Codex, Windsurf, Cline, Amazon Q) SDK patterns and integration knowledge. When updating SDK code examples, framework integrations, or x402 protocol documentation, also update the matching `references/` files to keep them in sync. + +**IDE-specific files carry condensed versions of a skill and must be updated when core patterns change.** The full set — note there is **no `.cursorrules`** in this repo: + +| Path | Skills present | +| --- | --- | +| `.cursor/rules/.mdc` | payments, router | +| `.windsurf/rules/.md` | payments, router (**6,000-char limit per file**) | +| `.clinerules/.md` | payments, router | +| `.amazonq/rules/.md` | payments, router | +| `.github/copilot-instructions.md` | payments only — single file, no skill segment | +| `AGENTS.md` | payments only — single file, no skill segment | + +`.cursor/` is otherwise gitignored; `.gitignore` re-includes `.cursor/rules/` specifically, because those files are published — users `curl` them from `main`, so one that fails to commit becomes a 404 in their editor. Both `development-guide/build-using-nvm-skill.mdx` (the install page) and its "Supported Tools at a Glance" table must be updated when this set changes. --- diff --git a/development-guide/build-using-nvm-skill.mdx b/development-guide/build-using-nvm-skill.mdx index c14863a9..7abfc489 100644 --- a/development-guide/build-using-nvm-skill.mdx +++ b/development-guide/build-using-nvm-skill.mdx @@ -20,7 +20,8 @@ There are two, and which one you want depends on which side of the payment you'r Installing both is fine — they don't overlap. How you install the Router one depends on your tool: - **Claude Code, Cursor, Windsurf, Cline, Amazon Q** — swap `nevermined-payments` for `nevermined-router` in the paths below, or run the extra command shown in each tab. -- **GitHub Copilot and Codex CLI** — these load a *single* file (`.github/copilot-instructions.md`, `AGENTS.md`) with no skill name in the path, so there is nothing to swap. Both files currently carry the **payments** guidance only. Until that changes, use the [full skill on GitHub](https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router) or paste it in manually. +- **Codex CLI** — its `AGENTS.md` route is a *single* file with no skill name in it, and that file carries the **payments** guidance only. Use the tab's **"install as a Codex skill"** alternative instead: `.agents/skills/` takes a skill directory, so `nevermined-router` works there. +- **GitHub Copilot** — `.github/copilot-instructions.md` is a single file with nothing to swap, and it carries the **payments** guidance only. Until that changes, use the [full skill on GitHub](https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router) or paste it in manually. ## What's Included @@ -174,6 +175,7 @@ Choose your AI coding tool below for step-by-step installation instructions. ```bash mkdir -p .agents/skills cp -r skills/nevermined-payments .agents/skills/ + cp -r skills/nevermined-router .agents/skills/ # buying through the Router ``` Codex CLI detects skills from `.agents/skills/` and loads them when relevant. @@ -240,7 +242,7 @@ Choose your AI coding tool below for step-by-step installation instructions. | **Cursor** | `.cursor/rules/.mdc` | Yes | Yes — on `.ts`, `.js`, `.py` files | | **Windsurf** | `.windsurf/rules/.md` | Yes | Yes — model decides | | **GitHub Copilot** | `.github/copilot-instructions.md` | **No** — single file, payments only | Yes — always | -| **Codex CLI** | `AGENTS.md` or `.agents/skills//` | **No** via `AGENTS.md` — single file, payments only | Yes — always or when relevant | +| **Codex CLI** | `AGENTS.md` or `.agents/skills//` | Yes — via `.agents/skills/`; **not** via `AGENTS.md` (single file, payments only) | Yes — always or when relevant | | **Cline** | `.clinerules/.md` | Yes | Yes — on `.ts`, `.js`, `.py` files | | **Amazon Q** | `.amazonq/rules/.md` | Yes | Yes — always | diff --git a/skills/nevermined-router/SKILL.md b/skills/nevermined-router/SKILL.md index ff3546d2..1d884d06 100644 --- a/skills/nevermined-router/SKILL.md +++ b/skills/nevermined-router/SKILL.md @@ -1,7 +1,7 @@ --- name: nevermined-router version: "0.1.1" -lastUpdated: "2026-08-03" +lastUpdated: "2026-08-04" 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 @@ -28,7 +28,7 @@ metadata: # Nevermined Router — buy from any x402 or MPP service -> **Skill version**: 0.1.1 | **Last updated**: 2026-08-03 | **Canonical source (always latest):** https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router +> **Skill version**: 0.1.1 | **Last updated**: 2026-08-04 | **Canonical source (always latest):** https://github.com/nevermined-io/docs/tree/main/skills/nevermined-router > > **⚠️ Use the latest version.** If you have a cached copy, check its **Last updated** date against the canonical source and refresh if older. >