⏳ This skill is pending AI review.

Scores will appear once the review pipeline completes.

version unknown

x402-skill-md

@nirholas⭐ 8 stars

The toolkit for `skill.md` — the agent-facing contract file an x402 service ships at its

Choose how to use this skill

You do not need every option. Choose the path your AI client supports. The stable page stays the same; versioned files are immutable.

1. Native installer

This listing has no registered native installer command. Use the complete package or source fallback below, depending on what your client supports.

Do not guess an installer command or replace an existing version without reviewing the diff.

2. Complete package recommended

Download the ZIP when available. It includes SKILL.md plus the references, security notes and version metadata.

No complete ProSkills package is published for this listing yet.

3. Prompt-only

Copy the prompt above when the agent can read the stable page or when you want to adopt the workflow without installing a skill.

Need only the instruction file?

Download SKILL.md only if your client requires a single file. The complete ZIP is safer for a full installation because it preserves the references and release context.

No path installs or executes anything by itself. Your agent still needs access to the project files. Before updating, compare the installed version and review the diff.

—/10

// RATINGS

⭐GitHub Stars
⭐ 8 on GitHubGitHub ↗

New / niche

🟢ProSkills Score
—
📍

Not yet listed on ClawHub or SkillsMP

// README


What a skill.md is

The file an AI agent reads to learn how to use and pay for an HTTP service — in one fetch, with no SDK and no human. It is Markdown, so a model can read it directly; it has a defined section grammar, so a parser can check it.

OpenAPI already describes shapes. It does not describe what a call costs, in what asset, on which chains, to which address — and for an agent with a wallet and no account, that is the load-bearing information.

Read the specification →

Use it three ways

As a CLI

npx x402-skill-md generate openapi.json -o skill.md
npx x402-skill-md validate skill.md
npx x402-skill-md rules
PASS  score 100/100  (0 errors, 0 warnings, 0 infos)
service: x402-domains  endpoints: 2  rails: evm + solana

No findings. This document is conformant.

validate exits 1 on errors, so it drops straight into CI:

- run: npx x402-skill-md validate skill.md --strict

As a library

npm i x402-skill-md
import { openapiToSkillMd, validateSkillMd, RULES } from "x402-skill-md";

const { skillMd, warnings } = openapiToSkillMd(
  JSON.parse(await readFile("openapi.json", "utf8")),
  { baseUrl: "https://api.example.com", contact: "[email protected]" },
);

const report = validateSkillMd(skillMd);
report.valid;                 // true
report.service.dualRail;      // true
report.findings;              // [] — each finding carries rule, detail, fix, line

Zero runtime dependencies in the library path: the parser, validator and generator are plain TypeScript.

As a paid service

RoutePriceWhat lands in the 200 body
POST /generate$0.01The generated skill.md, plus a validation report for it — one call gives you a file you can commit
POST /validate$0.002The full report: every finding with its rule id, location, what is wrong in this document, and the exact fix
GET /rulesfreeThe whole rule catalogue as JSON
GET /specfreeThe format specification
GET / · /health · /skill.md · /.well-known/x402 · /openapi.jsonfreeDiscovery

GET /rules is free on purpose: you should be able to see what you are being judged against before paying to be judged.

Why x402 for this

Validation is the definition of a spiky, per-artifact need — you check a skill.md when you write one, then not again for a month. A subscription is absurd for that, and so is an API key. But the library is also just free on npm, so the honest answer is: the paid service exists for agents. An agent that generated an OpenAPI document and wants a skill.md for it cannot npm install inside its own reasoning loop; it can make an HTTP request and pay a cent. Humans should use the CLI.

The format in one paragraph

Markdown with a defined section grammar: one H1, a summary paragraph, a service card with a base URL, a ## Payment section documenting both an EVM and a Solana rail, one ## `METHOD /path` — $price section per endpoint with a parameter table and a real JSON response example, and a ## Errors section that mentions the 402.

Dual rail is mandatory, not optional

A conformant skill.md documents an EVM rail and a Solana rail (rules SM005/SM006, both error). The reasoning:

  • An agent's wallet lives on exactly one chain. A single-rail service is invisible to every agent on the other one, and it cannot learn that cheaply.
  • The 402 challenge already carries an accepts array. Two entries cost one extra object; the client picks. There is no protocol reason to publish one.
  • Discovery indexers filter on rails. A single-rail listing halves your reachable market for no gain.

The spec also requires the facilitator to be declared per rail, because facilitators are chain-specific: the reference x402.org facilitator settles Base Sepolia only and will not settle a Solana payment. A document that names one facilitator for both rails is telling clients something false.

Rules

23 rules across three severities. valid is true when there are zero errors, independent of score; the score (100 − 12/error − 4/warning − 1/info) is a quality signal.

countmeaning
error10not conformant — an agent may fail to use the service
warning10conformant but lossy — an agent has to guess
info3a suggestion

Every finding carries a fix, not just a verdict:

{
  "rule": "SM006",
  "severity": "error",
  "title": "No Solana payment rail documented",
  "detail": "There is no Payment section, so no Solana rail is documented.",
  "fix": "Name a Solana network (`solana` or `solana-devnet`) and its `payTo` address in the Payment section. Every conformant skill.md is dual-rail: an agent's wallet lives on one chain, and it must be able to tell from this file alone whether it can pay."
}

Full catalogue: npx x402-skill-md rules, GET /rules, or §9 of the spec.

Generating from OpenAPI

Pricing is read from, in order of preference:

  1. x-x402: { price: "$0.001" } on the operation — the exact, declarative way
  2. the maxAmountRequired in a 402 response example, converted from USDC base units
  3. options.defaultPrice
  4. otherwise, an operation with no 402 response is treated as free

Anything guessed is reported in warnings. The generator never invents a price silently — a wrong price in a published contract is worse than a missing one.

openapiToSkillMd(doc, {
  baseUrl: "https://api.example.com",   // overrides servers[0].url
  contact: "[email protected]",           // overrides info.contact.email
  defaultPrice: "$0.001",
  frontmatter: true,                    // emit the YAML block
  rails: [ /* defaults to the suite's dual-rail pair */ ],
});

Quickstart (the service)

git clone https://github.com/nirholas/x402-skill-md.git
cd x402-skill-md
npm install
cp .env.example .env      # already filled with working defaults
npm run dev               # http://localhost:4029
curl -i -s -X POST localhost:4029/validate -H 'content-type: text/markdown' -d '# x'
# HTTP/1.1 402 Payment Required
# { "x402Version": 1, "accepts": [ {…base-sepolia…}, {…solana…} ] }

PRIVATE_KEY=0xyourTestnetKey npm run client

Dual-rail payment: Base or Solana

Every 402 lists two payment requirements. The client picks whichever chain it can sign on; the server settles either through that rail's facilitator and returns the same artifact.

EVM railSolana rail
networkbase-sepolia (default) / basesolana (default) / solana-devnet
assetUSDC 0x036CbD…dCF7e (sepolia)USDC mint EPjFWdd5…TDt1v
payTo0x40252CFDF8B20Ed757D61ff157719F33Ec332402WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW
facilitatorhttps://x402.org/facilitatorhttps://facilitator.payai.network

Those are the suite's public receive addresses and the server runs with them out of the box. Set PAY_TO_ADDRESS / SOLANA_PAY_TO_ADDRESS to be paid yourself. The Solana network fee is sponsored by the facilitator's fee

// HOW IT'S BUILT

// FILES

47 files shipped with this skill. Inline text is the catalog copy. The upstream link is the source repository.

  • skill.md9.1 KB88382244f732upstream ↗
    ---
    skillMd: "1.0"
    name: x402-skill-md
    baseUrl: http://localhost:4029
    manifest: /.well-known/x402
    openapi: /openapi.json
    contact: [email protected]
    ---
    
    # x402-skill-md
    
    The toolkit for `skill.md` — the agent-facing contract file an x402 service ships at its
    root, so a model can learn what a service does, what each call costs, on which chains it can
    pay, and what comes back, in one fetch. This service generates a conformant `skill.md` from
    an OpenAPI document, and validates an existing one against 23 rules, returning a per-finding
    fix rather than a verdict. It also **defines the format**: the specification lives at
    `GET /spec` and the machine-readable rule catalogue at `GET /rules`, both free.
    
    - **Base URL:** `http://localhost:4029` (self-host) — replace with your deployment's origin.
    - **Manifest:** `GET /.well-known/x402`
    - **OpenAPI:** `GET /openapi.json`
    - **Specification:** `GET /spec` · <https://nirholas.github.io/x402-skill-md/spec>
    - **Also a library and CLI:** `npm i x402-skill-md` · `npx x402-skill-md generate openapi.json`
    - **Contact:** [email protected]
    
    ## Payment
    
    This service speaks **x402** (HTTP 402 Payment Required), protocol version 1, scheme
    `exact`. Every paid route answers an unpaid request with a 402 whose `accepts` array carries
    **two rails — USDC on Base (EVM) and USDC on Solana. Your client picks whichever it can
    sign.**
    
    | rail | network | asset | payTo | facilitator |
    |---|---|---|---|---|
    | evm | `base-sepolia` | USDC `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | `0x40252CFDF8B20Ed757D61ff157719F33Ec332402` | `https://x402.org/facilitator` |
    | solana | `solana` | USDC `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | `WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW` | `https://facilitator.payai.network` |
    
    Facilitators are per rail, not per service: the reference `x402.org` facilitator settles
    Base Sepolia only, so the Solana rail points at PayAI's. Override with `FACILITATOR_URL` and
    `SOLANA_FACILITATOR_URL`.
    
    ```json
    {
      "x402Version": 1,
      "error": "X-PAYMENT header is required",
      "accepts": [
        {
          "scheme": "exact",
          "network": "base-sepolia",
          "maxAmountRequired": "10000",
          "resource": "http://localhost:4029/generate",
          "description": "Generate a conformant skill.md from a posted OpenAPI document",
          "mimeType": "application/json",
          "payTo": "0x40252CFDF8B20Ed757D61ff157719F33Ec332402",
          "maxTimeoutSeconds": 60,
          "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
          "extra": { "name": "USDC", "version": "2" }
        },
        {
          "scheme": "exact",
          "network": "solana",
          "maxAmountRequired": "10000",
          "resource": "http://localhost:4029/generate",
          "description": "Generate a conformant skill.md from a posted OpenAPI document",
          "mimeType": "application/json",
          "payTo": "WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW",
          "maxTimeoutSeconds": 60,
          "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
          "extra": { "name": "USD Coin", "decimals": 6, "feePayer": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4" }
        }
      ]
    }
    ```
    
    - **Asset:** USDC (6 decimals) on both rails. `maxAmountRequired` is in base units —
      `"10000"` is $0.01.
    - **Invocation contract:** every accept also carries `outputSchema.input` (how to build the
      request — method, query/path params, JSON body fields) and `outputSchema.output` (the JSON
      Schema of the 200 body). Both are elided above for readability and both are generated from
      `openapi.json`, so an agent can plan and call the route from the challenge alone.
    - **How to pay:** any x402 client. `x402-fetch` + `viem` on the EVM rail; on Solana build the
      SPL `transferChecked` (the network fee is sponsored by `extra.feePayer`, so you need no
      SOL), sign it, and base64 the envelope into `X-PAYMENT`.
    - **Receipt:** paid responses carry `X-PAYMENT-RESPONSE` (base64 JSON with `rail`, `network`,
      `transaction`, `payer`, `amount`) and repeat it inline as `receipt`.
    
    Payment is per request. No account, no key, no minimum.
    
    ## `POST /generate` — $0.01
    
    Turn an OpenAPI 3.x document into a conformant `skill.md`. The generated file comes back in
    this response body, together with a validation report for it, so one call gives you something
    you can commit.
    
    **Parameters**
    
    | name | in | type | required | notes |
    |---|---|---|---|---|
    | *(body)* | body | object | yes | The OpenAPI document itself, **or** `{ "openapi": <doc>, "options": {…} }` |
    | `options.baseUrl` | body | string | no | Overrides `servers[0].url` |
    | `options.contact` | body | string | no | Overrides `info.contact.email` |
    | `options.defaultPrice` | body | string | no | Price for paid operations with no declared amount, e.g. `"$0.001"` |
    | `options.frontmatter` | body | boolean | no | Emit the YAML frontmatter block. Default `true` |
    | `options.rails` | body | array | no | Rails to document. Defaults to the suite's dual-rail pair |
    
    Pricing is read from, in order: `x-x402.price` on the operation; the `maxAmountRequired` in a
    402 response example, converted from USDC base units; `options.defaultPrice`; otherwise the
    operation is treated as free if it declares no 402. Anything guessed is reported in
    `warnings` — the generator never invents a price silently.
    
    **Response 200**
    
    ```json
    {
      "skillMdVersion": "1.0",
      "skillMd": "---\nskillMd: \"1.0\"\nname: x402-podcasts\n---\n\n# x402-podcasts\n…",
      "service": {
        "name": "x402-podcasts",
        "baseUrl": "http://localhost:4028",
        "endpoints": 6,
        "paid": 2
      },
      "warnings": [],
      "validation": {
        "valid": true,
        "score": 100,
        "summary": { "errors": 0, "warnings": 0, "infos": 0 },
        "findings": []
      },
      "generatedAt": "2026-08-07T12:00:00.000Z",
      "receipt": { "success": true, "rail": "evm", "network": "base-sepolia", "transaction": "0x…" }
    }
    ```
    
    ## `POST /validate` — $0.002
    
    Check a `skill.md` against all 23 rules. The report names every finding, where it is, what
    is wrong *in this document*, and the exact remedy.
    
    **Parameters**
    
    | name | in | type | required | notes |
    |---|---|---|---|---|
    | *(body)* | body | string \| object | yes | The file as `text/markdown`, or JSON `{ "skillMd": "…" }` |
    
    **Response 200**
    
    ```json
    {
      "skillMdVersion": "1.0",
      "valid": false,
      "score": 88,
      "summary": { "errors": 1, "warnings": 0, "infos": 0 },
      "service": {
        "name": "x402-github-bounty",
        "baseUrl": "http://localhost:4027",
        "contact": "[email protected]",
        "endpoints": [{ "method": "POST", "path": "/bounties", "price": "$0.01" }],
        "rails": { "evm": true, "solana": true },
        "dualRail": true
      },
      "findings": [
        {
          "rule": "SM007",
          "severity": "error",
          "title": "No endpoints documented",
          "detail": "No level-2 heading matches the endpoint grammar.",
          "fix": "Document at least one endpoint as `## `METHOD /path` — $price` (or `— free`)."
        }
      ],
      "checkedAt": "2026-08-07T12:00:00.000Z",
      "receipt": { "success": true, "rail": "solana", "network": "solana", "transaction": "5Qm…" }
    }
    ```
    
    `valid` is `true` when there are **zero errors**, independent of `score`. The score (100
    minus 12 per error, 4 per warning, 1 per info) is a quality signal; `valid` is the gate.
    
    ## Free routes
    
    | route | returns |
    |---|---|
    | `GET /` | service card: rails, prices, format version, library and CLI hints |
    | `GET /health` | `{ ok, uptime, skillMdVersion }` |
    | `GET /rules` | the full rule catalogue as JSON — id, severity, title, fix |
    | `GET /spec` | the SKILL-MD format specification, `text/markdown` |
    | `GET /skill.md` | this file |
    | `GET /.well-known/x402` | machine-readable resource manifest |
    | `GET /openapi.json` | OpenAPI 3.1 |
    
    `GET /rules` is deliberately free: you should be able to see what you are being judged
    against before paying to be judged.
    
    ## The format, in one paragraph
    
    A `skill.md` is Markdown with a defined section grammar: one H1, a summary paragraph, a
    service card with a base URL, a `## Payment` section documenting **both an EVM and a Solana
    rail**, one `` ## `METHOD /path` — $price `` section per endpoint with a parameter table and a
    real JSON response example, and a `## Errors` section that mentions the 402. Dual rail is
    mandatory, not optional: an agent's wallet lives on one chain, and it must be able to tell
    from this file alone whether it can pay at all. Full specification: `GET /spec`.
    
    ## Errors
    
    | status | body `error` | meaning |
    |---|---|---|
    | 400 | `BAD_REQUEST` | empty body, or neither markdown nor `{ skillMd }` / an OpenAPI document |
    | 400 | `NOT_AN_OPENAPI_DOCUMENT` | the posted JSON has no `paths` object |
    | 400 | `GENERATE_FAILED` | the document could not be converted; `message` says why |
    | 400 | `VALIDATE_FAILED` | the file could not be parsed at all |
    | 402 | — | payment required or rejected; body is the dual-rail challenge with `error` explaining why |
    
    Note that a *non-conformant* `skill.md` is a **200 with `valid: false`**, not an error — you
    paid for the report and the report is the artifact. Only an unparseable request is a 4xx.
    
    ## Data source
    
    Self-contained. There is no upstream API, no key, and no fixture mode: generation and
    validation are pure functions of what you post, so the same input always produces the same
    output. Nothing you send is stored.
    
  • README.md12 KB22c46a9435efupstream ↗
    <h1 align="center">x402-skill-md</h1>
    
    <p align="center">
      <b>The <code>skill.md</code> toolkit — the format, a generator, a validator.</b><br>
      A library, a CLI, and an x402-paid service. Defines the agent-facing contract file the whole suite ships.
    </p>
    
    <p align="center">
      <a href="LICENSE"><img alt="License: Apache 2.0" src="https://img.shields.io/badge/license-Apache--2.0-blue.svg"></a>
      <a href="https://x402.org"><img alt="x402" src="https://img.shields.io/badge/protocol-x402-0052ff.svg"></a>
      <img alt="rails" src="https://img.shields.io/badge/USDC-Base%20%2B%20Solana-2775ca.svg">
      <img alt="format" src="https://img.shields.io/badge/SKILL--MD-1.0-6b46c1.svg">
      <a href="https://nirholas.github.io/x402-skill-md/"><img alt="docs" src="https://img.shields.io/badge/docs-Pages-24292f.svg"></a>
    </p>
    
    ---
    
    ## What a `skill.md` is
    
    The file an AI agent reads to learn how to **use and pay for** an HTTP service — in one
    fetch, with no SDK and no human. It is Markdown, so a model can read it directly; it has a
    defined section grammar, so a parser can check it.
    
    OpenAPI already describes shapes. It does not describe **what a call costs, in what asset, on
    which chains, to which address** — and for an agent with a wallet and no account, that is the
    load-bearing information.
    
    **[Read the specification →](docs/spec.md)**
    
    ## Use it three ways
    
    ### As a CLI
    
    ```bash
    npx x402-skill-md generate openapi.json -o skill.md
    npx x402-skill-md validate skill.md
    npx x402-skill-md rules
    ```
    
    ```
    PASS  score 100/100  (0 errors, 0 warnings, 0 infos)
    service: x402-domains  endpoints: 2  rails: evm + solana
    
    No findings. This document is conformant.
    ```
    
    `validate` exits 1 on errors, so it drops straight into CI:
    
    ```yaml
    - run: npx x402-skill-md validate skill.md --strict
    ```
    
    ### As a library
    
    ```bash
    npm i x402-skill-md
    ```
    
    ```ts
    import { openapiToSkillMd, validateSkillMd, RULES } from "x402-skill-md";
    
    const { skillMd, warnings } = openapiToSkillMd(
      JSON.parse(await readFile("openapi.json", "utf8")),
      { baseUrl: "https://api.example.com", contact: "[email protected]" },
    );
    
    const report = validateSkillMd(skillMd);
    report.valid;                 // true
    report.service.dualRail;      // true
    report.findings;              // [] — each finding carries rule, detail, fix, line
    ```
    
    Zero runtime dependencies in the library path: the parser, validator and generator are plain
    TypeScript.
    
    ### As a paid service
    
    | Route | Price | What lands in the 200 body |
    |---|---|---|
    | `POST /generate` | **$0.01** | The generated `skill.md`, plus a validation report for it — one call gives you a file you can commit |
    | `POST /validate` | **$0.002** | The full report: every finding with its rule id, location, what is wrong *in this document*, and the exact fix |
    | `GET /rules` | free | The whole rule catalogue as JSON |
    | `GET /spec` | free | The format specification |
    | `GET /` · `/health` · `/skill.md` · `/.well-known/x402` · `/openapi.json` | free | Discovery |
    
    `GET /rules` is free on purpose: you should be able to see what you are being judged against
    before paying to be judged.
    
    ## Why x402 for this
    
    Validation is the definition of a spiky, per-artifact need — you check a `skill.md` when you
    write one, then not again for a month. A subscription is absurd for that, and so is an API
    key. But the library is also just free on npm, so the honest answer is: **the paid service
    exists for agents.** An agent that generated an OpenAPI document and wants a `skill.md` for it
    cannot `npm install` inside its own reasoning loop; it can make an HTTP request and pay a
    cent. Humans should use the CLI.
    
    ## The format in one paragraph
    
    Markdown with a defined section grammar: one H1, a summary paragraph, a service card with a
    base URL, a `## Payment` section documenting **both an EVM and a Solana rail**, one
    `` ## `METHOD /path` — $price `` section per endpoint with a parameter table and a real JSON
    response example, and a `## Errors` section that mentions the 402.
    
    ### Dual rail is mandatory, not optional
    
    A conformant `skill.md` documents an EVM rail **and** a Solana rail (rules SM005/SM006, both
    `error`). The reasoning:
    
    - An agent's wallet lives on exactly **one** chain. A single-rail service is invisible to
      every agent on the other one, and it cannot learn that cheaply.
    - The 402 challenge already carries an `accepts` **array**. Two entries cost one extra
      object; the client picks. There is no protocol reason to publish one.
    - Discovery indexers filter on rails. A single-rail listing halves your reachable market for
      no gain.
    
    The spec also requires the **facilitator to be declared per rail**, because facilitators are
    chain-specific: the reference `x402.org` facilitator settles Base Sepolia only and will not
    settle a Solana payment. A document that names one facilitator for both rails is telling
    clients something false.
    
    ## Rules
    
    23 rules across three severities. `valid` is `true` when there are **zero errors**,
    independent of score; the score (100 − 12/error − 4/warning − 1/info) is a quality signal.
    
    | | count | meaning |
    |---|---|---|
    | `error` | 10 | not conformant — an agent may fail to use the service |
    | `warning` | 10 | conformant but lossy — an agent has to guess |
    | `info` | 3 | a suggestion |
    
    Every finding carries a fix, not just a verdict:
    
    ```json
    {
      "rule": "SM006",
      "severity": "error",
      "title": "No Solana payment rail documented",
      "detail": "There is no Payment section, so no Solana rail is documented.",
      "fix": "Name a Solana network (`solana` or `solana-devnet`) and its `payTo` address in the Payment section. Every conformant skill.md is dual-rail: an agent's wallet lives on one chain, and it must be able to tell from this file alone whether it can pay."
    }
    ```
    
    Full catalogue: `npx x402-skill-md rules`, `GET /rules`, or [§9 of the spec](docs/spec.md).
    
    ## Generating from OpenAPI
    
    Pricing is read from, in order of preference:
    
    1. `x-x402: { price: "$0.001" }` on the operation — the exact, declarative way
    2. the `maxAmountRequired` in a 402 response example, converted from USDC base units
    3. `options.defaultPrice`
    4. otherwise, an operation with no 402 response is treated as **free**
    
    Anything guessed is reported in `warnings`. The generator never invents a price silently —
    a wrong price in a published contract is worse than a missing one.
    
    ```ts
    openapiToSkillMd(doc, {
      baseUrl: "https://api.example.com",   // overrides servers[0].url
      contact: "[email protected]",           // overrides info.contact.email
      defaultPrice: "$0.001",
      frontmatter: true,                    // emit the YAML block
      rails: [ /* defaults to the suite's dual-rail pair */ ],
    });
    ```
    
    ## Quickstart (the service)
    
    ```bash
    git clone https://github.com/nirholas/x402-skill-md.git
    cd x402-skill-md
    npm install
    cp .env.example .env      # already filled with working defaults
    npm run dev               # http://localhost:4029
    ```
    
    ```bash
    curl -i -s -X POST localhost:4029/validate -H 'content-type: text/markdown' -d '# x'
    # HTTP/1.1 402 Payment Required
    # { "x402Version": 1, "accepts": [ {…base-sepolia…}, {…solana…} ] }
    
    PRIVATE_KEY=0xyourTestnetKey npm run client
    ```
    
    ## Dual-rail payment: Base **or** Solana
    
    Every 402 lists **two** payment requirements. The client picks whichever chain it can sign
    on; the server settles either through that rail's facilitator and returns the same artifact.
    
    | | EVM rail | Solana rail |
    |---|---|---|
    | network | `base-sepolia` (default) / `base` | `solana` (default) / `solana-devnet` |
    | asset | USDC `0x036CbD…dCF7e` (sepolia) | USDC mint `EPjFWdd5…TDt1v` |
    | payTo | `0x40252CFDF8B20Ed757D61ff157719F33Ec332402` | `WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW` |
    | facilitator | `https://x402.org/facilitator` | `https://facilitator.payai.network` |
    
    Those are the suite's public receive addresses and the server runs with them out of the box.
    Set `PAY_TO_ADDRESS` / `SOLANA_PAY_TO_ADDRESS` to be paid yourself. The Solana network fee is
    sponsored by the facilitator's fee payer, so a payer needs USDC only — no SOL.
    
    ## Real backend / API keys
    
    **None.** Generation and validation are pure functions of what you post: no upstream API, no
    key, no fixture mode, and nothing you send is stored. The same input always produces the same
    output, which is also why the library and the service cannot disagree.
    
    ## For AI agents
    
    - **[`skill.md`](skill.md)** — this service's own contract file, and a worked example of the
      format. It scores 100/100 against its own validator, which is the least it could do.
    - **`GET /.well-known/x402`** — machine-readable manifest of both paid resources with input
      and output schemas and both rails.
    - **`GET /rules`** — the catalogue, free, so an agent can self-check before paying.
    - **MCP** — [`examples/mcp-tool.md`](examples/mcp-tool.md) exposes `generate_skill_md` and
      `validate_skill_md` as Model Context Protocol tools.
    - **Client** — [`examples/agent-client.ts`](examples/agent-client.ts) runs the full flow:
      rules → 402 → generate → validate a deliberately broken file to show the fixes.
    
    ## Docs
    
    Full site: **<https://nirholas.github.io/x402-skill-md/>**
    
    - **[Specification](docs/spec.md)** — the format itself, normative
    - [Tutorial](docs/tutorial.md) — CLI → library → paid service → CI
    - [API reference](docs/api.md) — every field of every response
    - [For agents](docs/agents.md) — discovery, payment, MCP, listing
    
    ## Support
    
    Questions, bugs, or a spec correction: **[email protected]** or open an issue.
    
    Part of the [x402 Suite](https://github.com/nirholas/x402-suite).
    
    ## License
    
    Apache-2.0 — see [LICENSE](LICENSE).
    
    ## Star History
    
    [![Star History Chart](https://api.star-history.com/svg?repos=nirholas/x402-skill-md&type=Date)](https://www.star-history.com/#nirholas/x402-skill-md&Date)
    
    <!-- three.ws:growth -->
    ## Support the project
    
    If x402-skill-md saves you time, **[star it on GitHub](https://github.com/nirholas/x402-skill-md)**. Stars are how other developers and AI agents find the repositories worth trusting, and they cost you one click.
    
    Know someone who would use it? [Post on X](https://twitter.com/intent/tweet?text=x402-skill-md%3A%20The%20skill.md%20toolkit%3A%20generate%20and%20validate%20agent-discoverable%20skill%20files%20(the%20agentres.dev&url=https%3A%2F%2Fgithub.com%2Fnirholas%2Fx402-skill-md) · [Share on Bluesky](https://bsky.app/intent/compose?text=x402-skill-md%3A%20The%20skill.md%20toolkit%3A%20generate%20and%20validate%20agent-discoverable%20skill%20files%20(the%20agentres.dev%20https%3A%2F%2Fgithub.com%2Fnirholas%2Fx402-skill-md) · [Share on LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fgithub.com%2Fnirholas%2Fx402-skill-md) · [Submit to Hacker News](https://news.ycombinator.com/submitlink?u=https%3A%2F%2Fgithub.com%2Fnirholas%2Fx402-skill-md&t=x402-skill-md%3A%20The%20skill.md%20toolkit%3A%20generate%20and%20validate%20agent-discoverable%20skill%20files%20(the%20agentres.dev) · [Share on Reddit](https://www.reddit.com/submit?url=https%3A%2F%2Fgithub.com%2Fnirholas%2Fx402-skill-md&title=x402-skill-md%3A%20The%20skill.md%20toolkit%3A%20generate%20and%20validate%20agent-discoverable%20skill%20files%20(the%20agentres.dev)
    
    ## Built for AI agents too
    
    Coding agents and LLM tooling can read this repo directly: [AGENTS.md](./AGENTS.md), [llms.txt](./llms.txt), [llms-full.txt](./llms-full.txt). Point an agent at `https://github.com/nirholas/x402-skill-md` and it has the context it needs.
    
    ## More from the same author
    
    - [All repositories by nirholas](https://github.com/nirholas/nirholas#readme): the full catalog, grouped by topic
    - [three.ws](https://three.ws): the platform for 3D AI agents with Solana wallets, a skill marketplace and x402 payments
    - Questions or ideas: [open an issue](https://github.com/nirholas/x402-skill-md/issues) or [start a discussion](https://github.com/nirholas/x402-skill-md/discussions)
    
    ## Contributors
    
    [![Contributors](https://contrib.rocks/image?repo=nirholas/x402-skill-md)](https://github.com/nirholas/x402-skill-md/graphs/contributors)
    
    <!-- /three.ws:growth -->
    
  • .env.example2.9 KBb561b36ca3b4upstream ↗
    # ── Payment rails (dual-rail x402: USDC on Base *and* Solana) ───────────────
    # Every paid route advertises BOTH rails in its 402 challenge; the paying
    # client picks one. These are the suite's public receive addresses — the
    # server runs with them out of the box. Replace them to get paid yourself.
    
    # EVM (Base / Base Sepolia) USDC receive address
    PAY_TO_ADDRESS=0x40252CFDF8B20Ed757D61ff157719F33Ec332402
    # Solana USDC receive address
    SOLANA_PAY_TO_ADDRESS=WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW
    
    # ── Networks ────────────────────────────────────────────────────────────────
    # EVM rail: base-sepolia (testnet, default) or base (mainnet).
    #NETWORK=base-sepolia
    # Solana rail: mainnet-beta (default) or devnet.
    #SOLANA_NETWORK=mainnet-beta
    # Solana RPC used by the browser checkout helper.
    #SOLANA_RPC_URL=https://api.mainnet-beta.solana.com
    
    # ── Facilitator ─────────────────────────────────────────────────────────────
    # The x402 facilitator that verifies + settles payments on the EVM rail.
    #FACILITATOR_URL=https://x402.org/facilitator
    # Facilitator for the Solana rail. The reference x402.org facilitator only
    # settles base-sepolia, so the Solana rail defaults to PayAI's public
    # facilitator — no API key required.
    #SOLANA_FACILITATOR_URL=https://facilitator.payai.network
    # Facilitator sponsor account that pays the SOL network fee, so payers need
    # only USDC (no SOL). Override if your facilitator uses a different sponsor.
    #SOLANA_FEE_PAYER=2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4
    
    # ── Server ──────────────────────────────────────────────────────────────────
    # HTTP port (default 4029).
    #PORT=4029
    # Public base URL, used to build the absolute `resource` in 402 challenges.
    # Set this when running behind a proxy or on a public host.
    #PUBLIC_BASE_URL=https://skill-md.example.com
    
    # ── Upstream data ───────────────────────────────────────────────────────────
    # None. Generation and validation are pure functions of what you post: no
    # upstream API, no key, no fixture mode, and nothing you send is stored.
    
    # ── Client examples only (never read by the server) ─────────────────────────
    # Wallet private key used by examples/agent-client.ts to PAY for calls.
    # Fund it with Base Sepolia USDC: https://faucet.circle.com
    #PRIVATE_KEY=0x...
    # Base URL the example client talks to.
    #BASE_URL=http://localhost:4029
    
  • .github/FUNDING.yml106 Bfda9433edd19upstream ↗
    # Adds a Sponsor button to the repo. Links to the product, not to any token.
    custom:
      - https://three.ws
    
  • .github/ISSUE_TEMPLATE/bug.yml613 B3d2ff4483e49upstream ↗
    name: Bug report
    description: Something does not work as documented
    labels: [bug]
    body:
      - type: input
        id: version
        attributes:
          label: x402-skill-md version or commit
          placeholder: v1.0.0 or a commit SHA
        validations:
          required: true
      - type: input
        id: env
        attributes:
          label: Runtime and OS
          placeholder: Node 22, macOS 15
      - type: textarea
        id: what
        attributes:
          label: What happened, and what did you expect?
        validations:
          required: true
      - type: textarea
        id: repro
        attributes:
          label: Smallest reproduction
          render: shell
    
  • .github/ISSUE_TEMPLATE/config.yml494 B284a4bbba4b7upstream ↗
    blank_issues_enabled: false
    contact_links:
      - name: Ask a question or share what you built
        url: https://github.com/nirholas/x402-skill-md/discussions
        about: Open-ended questions belong in Discussions.
      - name: three.ws platform support
        url: https://three.ws
        about: Questions about the hosted platform rather than this repository.
      - name: Security report
        url: https://github.com/nirholas/x402-skill-md/security/advisories/new
        about: Report a vulnerability privately.
    
  • .github/ISSUE_TEMPLATE/feature.yml325 Bd7d4f2974d3bupstream ↗
    name: Feature request
    description: Suggest a change or addition
    labels: [enhancement]
    body:
      - type: textarea
        id: problem
        attributes:
          label: What problem are you trying to solve?
        validations:
          required: true
      - type: textarea
        id: proposal
        attributes:
          label: What would you like to see?
    
  • .github/PULL_REQUEST_TEMPLATE.md153 Bfcbde8255921upstream ↗
    ## What changed and why
    
    ## How it was checked
    
    - [ ] Tests pass locally (or the change is documentation only)
    - [ ] Docs updated where behavior changed
    
  • .gitignore43 B8e2ff1069432upstream ↗
    node_modules/
    dist/
    .env
    *.log
    data/*.json
    
  • AGENTS.md2.1 KBb1e17184ea7cupstream ↗
    # AGENTS.md
    
    Operating notes for AI coding agents (Claude Code, Codex, Cursor, Copilot and others) working in this repository. Everything here is derived from the files actually in the tree, so trust it over guesses, and update it when the facts change.
    
    ## What this repository is
    
    The skill.md toolkit: generate and validate agent-discoverable skill files (the agentres.dev pattern) from OpenAPI specs. Defines the SKILL-MD format used across the x402 Suite.
    
    - Homepage: https://nirholas.github.io/x402-skill-md/
    - Source: https://github.com/nirholas/x402-skill-md
    - Primary language: TypeScript
    - License: Apache License 2.0 (see the LICENSE file)
    
    ## Repository layout
    
    - `docs/`
    - `examples/`
    - `public/`
    - `src/`
    - `README.md`
    - `LICENSE`
    - `package.json`
    
    ## Setup
    
    ```bash
    npm install
    ```
    
    ## Commands
    
    | Task | Command |
    |---|---|
    | dev | `npm run dev` |
    | start | `npm start` |
    | build | `npm run build` |
    
    Run the test and lint commands above before you consider a change finished. If a command fails on code you did not touch, say so in your report instead of silently skipping it.
    
    ## Conventions
    
    - TypeScript runs in strict mode; do not loosen `tsconfig.json` to make an error go away.
    - `.env` files are gitignored; never commit credentials, and read configuration from environment variables.
    - Commit messages follow Conventional Commits (`type(scope): summary`), matching the existing history.
    - Read the surrounding code before adding to it, and match its naming, file organisation and error-handling style.
    - Keep `README.md` accurate: if a change alters behaviour, commands or configuration, update the docs in the same commit.
    - Do not leave TODO comments, stub functions, placeholder data or commented-out code behind. Finish what you start or leave it out.
    - Small, focused commits with a subject line that describes the change, not the act of committing.
    
    ## Where to raise things
    
    - Bugs and feature requests: https://github.com/nirholas/x402-skill-md/issues
    - Questions and ideas: https://github.com/nirholas/x402-skill-md/discussions
    - Security issues: report privately at https://github.com/nirholas/x402-skill-md/security/advisories/new, never in a public issue.
    
  • CITATION.cff457 B3ae455d5209fupstream ↗
    cff-version: 1.2.0
    message: If you use this software, please cite it as below.
    title: "x402-skill-md"
    abstract: "The skill.md toolkit: generate and validate agent-discoverable skill files (the agentres.dev pattern) from OpenAPI specs. Defines the SKILL-MD format used across the x402 Suite."
    license: Apache-2.0
    repository-code: "https://github.com/nirholas/x402-skill-md"
    url: "https://three.ws"
    authors:
      - name: three.ws
        website: "https://three.ws"
    
  • CLAUDE.md11 B43c994c2d361upstream ↗
    @AGENTS.md
    
  • CODE_OF_CONDUCT.md453 B4954bc6c1094upstream ↗
    # Code of Conduct
    
    This project follows the [Contributor Covenant, version 2.1](https://www.contributor-covenant.org/version/2/1/code_of_conduct/).
    
    In short: be respectful, assume good faith, keep feedback about the work, and do not harass anyone. Maintainers may remove comments, commits or contributions that break these rules and may block repeat offenders.
    
    Report conduct concerns privately to [email protected]. Reports are handled in confidence.
    
  • CONTRIBUTING.md1.1 KBbddcf037cef6upstream ↗
    # Contributing to x402-skill-md
    
    Thanks for helping. Small, focused changes land fastest.
    
    ## Ways to contribute
    
    - **Report a bug** with the [bug form](https://github.com/nirholas/x402-skill-md/issues/new?template=bug.yml). Include what you ran, what you expected and the smallest reproduction you can.
    - **Propose a feature** with the [feature form](https://github.com/nirholas/x402-skill-md/issues/new?template=feature.yml). Describe the problem first, then the change.
    - **Ask a question or share what you built** in [Discussions](https://github.com/nirholas/x402-skill-md/discussions).
    - **Fix something** with a pull request.
    
    ## Local setup
    
    ```bash
    git clone https://github.com/nirholas/x402-skill-md.git
    cd x402-skill-md
    npm install
    ```
    
    ## Pull requests
    
    - One change per pull request, with a test where the change is testable.
    - Describe what changed and why in plain language.
    - Do not add dependencies for something a few lines of code can do.
    - Security-sensitive reports go through [SECURITY.md](SECURITY.md), not a public issue.
    
    By contributing you agree that your work is licensed under Apache-2.0, the license of this project.
    
  • docs/.nojekyll0 Be69de29bb2d1upstream ↗
  • docs/agents.md8.5 KBa48d8142c3c0upstream ↗
    # For AI agents
    
    This service is about the file *other* services publish for you. If you have ever fetched a
    `skill.md` and been able to use a paid API without reading its docs, this is the format that
    made that work — and this is the tooling that keeps those files honest.
    
    ## 1. Discover
    
    **`GET /rules`** — free. The whole rule catalogue: 23 rules, each with an id, a severity and
    a fix. Fetch this *before* writing or judging a `skill.md`; it turns validation from a
    diagnosis into a checklist.
    
    **`GET /spec`** — free. The [normative format specification](spec.md), as Markdown. Give it
    to a model and it can write a conformant file from scratch.
    
    **`GET /skill.md`** — this service's own contract file, which is also a worked example of the
    format. It scores 100/100 against its own validator.
    
    **`GET /.well-known/x402`** — the machine-readable manifest:
    
    ```jsonc
    {
      "x402Version": 1,
      "name": "x402-skill-md",
      "skillMdVersion": "1.0",
      "spec": "/spec",
      "rules": "/rules",
      "rails": [
        { "rail": "evm",    "network": "base-sepolia", "payTo": "0x40252CF…2402", "facilitator": "https://x402.org/facilitator" },
        { "rail": "solana", "network": "solana",       "payTo": "WwwuGbqH…T3WwW", "facilitator": "https://facilitator.payai.network" }
      ],
      "resources": [
        { "resource": "POST /generate", "price": "$0.01", "inputSchema": {…}, "outputSchema": {…} },
        { "resource": "POST /validate", "price": "$0.002", "inputSchema": {…}, "outputSchema": {…} }
      ],
      "freeResources": [
        { "resource": "GET /rules", "price": "free" },
        { "resource": "GET /spec",  "price": "free" }
      ]
    }
    ```
    
    ## 2. Pay
    
    Every paid route answers an unpaid request with 402 and a **dual-rail** `accepts` array:
    USDC on Base, USDC on Solana, same price, same artifact. Pick whichever chain your wallet
    lives on.
    
    **EVM rail:**
    
    ```ts
    import { wrapFetchWithPayment } from "x402-fetch";
    const pay = wrapFetchWithPayment(fetch, wallet);       // viem wallet client
    
    const res = await pay(`${BASE}/generate`, {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ openapi: myOpenApiDoc }),
    });
    const { skillMd, warnings, validation } = await res.json();
    ```
    
    **Solana rail:**
    
    ```ts
    import { prepareSolanaCheckout, encodeX402Payment } from "@three-ws/x402-payment-modal/server";
    
    const accept = challenge.accepts.find((a) => a.network.startsWith("solana"));
    const { tx_base64 } = await prepareSolanaCheckout({ accept, buyer: pubkey });
    const { x_payment } = encodeX402Payment({
      accept,
      signedTxBase64: await wallet.signTransaction(tx_base64),
      resourceUrl: url,
    });
    await fetch(url, { method: "POST", headers: { "X-PAYMENT": x_payment, … }, body });
    ```
    
    Those helpers only *build and encode* the payment client-side. Verification and settlement
    happen server-side through the rail's own facilitator — and the facilitators differ per rail,
    which is exactly the kind of thing the format makes services declare.
    `accept.extra.feePayer` sponsors the SOL network fee, so an agent holding only USDC can pay.
    
    ### Reading the contract before you pay
    
    Every entry in `accepts` carries an `outputSchema` with two halves, so an agent can judge
    whether a call is worth its price and then make it correctly — without fetching the OpenAPI
    document first:
    
    - **`outputSchema.input`** — `{ type: "http", method, queryParams?, pathParams?, bodyType?,
      bodyFields? }`. Each value is the JSON Schema for that query parameter, path segment or
      request-body field.
    - **`outputSchema.output`** — the JSON Schema of the 200 body you receive once payment
      settles.
    
    Both halves are generated from `openapi.json`, so the runtime challenge and the published
    spec cannot drift apart. Both rails advertise the identical contract: which wallet you pay
    with never changes what the endpoint takes or returns.
    
    You can probe a paid route safely: the paywall answers before any validation or existence
    check, so an unpaid request with a synthetic id or an empty body still returns the full
    challenge rather than a 404 or a 400. Read the price and the contract first, decide, then pay.
    
    ### Protocol version
    
    This service speaks **x402 v1** (`x402Version: 1`) — the version every client shipped in this
    repo, and in the examples above, is written against. v2 relocates the invocation contract to
    `extensions.bazaar.schema` and identifies networks with CAIP-2 ids; agentcash prefers it, and
    moving is a planned upgrade once the clients here can speak both. Until then, read `accepts[]`
    and ignore `extensions`.
    
    ## 3. What you get
    
    The 200 body **is** the purchase:
    
    - `POST /generate` → the `skill.md` text, plus a validation report for it
    - `POST /validate` → the report: findings, fixes, score, and what the validator understood
      about the service
    
    Plus `X-PAYMENT-RESPONSE`, a base64 receipt naming the rail, network and transaction.
    
    ### Rules worth encoding in your agent
    
    - **`valid: false` is a 200, not an error.** You bought the report. Read `findings`, fix the
      file, do not retry the call. This is the single most common agent mistake here.
    - **Fetch `/rules` first — it is free.** Most non-conformance is avoidable rather than
      diagnosable. Paying to be told you forgot a Payment section is a waste of a cent.
    - **`valid` is the gate, `score` is a signal.** `valid` is true at zero errors regardless of
      score. Do not block on a score threshold.
    - **Read `fix`, not just `title`.** Every finding carries the exact remedy. `detail` says
      what is wrong in *this* document; `fix` says what a conformant one does instead.
    - **Check `warnings` after generating.** That is where the generator admits what the OpenAPI
      document did not carry — a missing summary, an undeterminable price. A generated file is a
      good 80%; the warnings tell you which 20% you have to write.
    - **Never publish a generated file unchecked.** `generate` returns `validation` precisely so
      you do not have to pay twice to find out.
    - **Prefer the library if you can.** `npm i x402-skill-md` does all of this for free,
      offline, deterministically. The paid routes exist because an agent cannot always install
      a package mid-task.
    
    ## 4. Reading someone else's skill.md
    
    The reverse direction, and the reason the format exists. When you find a service's
    `skill.md`, the validator's `service` block tells you in one call whether it is usable:
    
    ```ts
    const report = validateSkillMd(await (await fetch("https://svc.example/skill.md")).text());
    
    report.service.dualRail;          // can I pay at all, on either chain?
    report.service.rails.solana;      // specifically: my chain?
    report.service.endpoints;         // [{ method, path, price }] — what does it cost?
    report.valid;                     // is this file trustworthy enough to act on?
    ```
    
    A file that fails SM006 tells you the service takes EVM only — which, if your wallet is on
    Solana, means stop here rather than discovering it after a round trip.
    
    ## 5. MCP integration
    
    [`examples/mcp-tool.md`](https://github.com/nirholas/x402-skill-md/blob/main/examples/mcp-tool.md)
    is a complete Model Context Protocol server exposing `skill_md_rules` (free),
    `generate_skill_md` and `validate_skill_md`. The wallet lives in the MCP process, so its
    balance is the agent's spending cap.
    
    ```json
    {
      "mcpServers": {
        "x402-skill-md": {
          "command": "npx",
          "args": ["tsx", "/path/to/mcp-skill-md.ts"],
          "env": { "PRIVATE_KEY": "0x…", "X402_SKILL_MD_URL": "https://your-host" }
        }
      }
    }
    ```
    
    Expose the free `skill_md_rules` tool too. A model that has read the rules writes a better
    file than one that pays to be corrected.
    
    ## 6. Getting listed
    
    Deploy publicly, then submit the origin to the x402 discovery surfaces. Each reads
    `/.well-known/x402`:
    
    | where | what it does | how |
    |---|---|---|
    | [x402scan.com](https://x402scan.com) | indexes live x402 endpoints and their settlement volume | submit your origin; it crawls `/.well-known/x402` |
    | **x402 Bazaar** | the protocol's own resource directory, queried by agents at runtime | register through the facilitator's `list` API |
    | [agentic.market](https://agentic.market) | marketplace of agent-payable services | submit the origin plus your `skill.md` URL |
    | [x402-skill-registry](https://github.com/nirholas/x402-skill-registry) | the suite's own searchable registry of paid agent skills | `POST /register` with a signed listing |
    
    Before submitting anything, validate your own `skill.md`:
    
    ```bash
    npx x402-skill-md validate skill.md --strict
    ```
    
    An indexer that finds a non-conformant file will list you badly, or not at all — and an agent
    that finds one will not know whether it can pay you.
    
    Questions or a spec correction: **[email protected]**.
    
  • docs/api.md11 KBc58b2c988c65upstream ↗
    # API reference
    
    Base URL: `http://localhost:4029` when self-hosting. Machine-readable equivalents:
    [`openapi.json`](https://github.com/nirholas/x402-skill-md/blob/main/openapi.json) and
    [`/.well-known/x402`](https://github.com/nirholas/x402-skill-md/blob/main/public/.well-known/x402).
    The format itself is specified in [spec.md](spec.md).
    
    Prices are USDC. Every paid route accepts **both** rails — Base (EVM) and Solana — and
    returns the purchased artifact in the 200 body.
    
    Everything here is also available for free as a library and CLI: `npm i x402-skill-md`,
    `npx x402-skill-md`. The HTTP service exists for agents that cannot install anything.
    
    ---
    
    ## `POST /generate`
    
    **Price:** $0.01 · **Returns:** the generated `skill.md` plus a validation report for it
    
    ### Request body
    
    Either the OpenAPI document itself, or a wrapper:
    
    ```json
    {
      "openapi": { "openapi": "3.1.0", "info": { … }, "paths": { … } },
      "options": { "baseUrl": "https://api.example.com", "contact": "[email protected]" }
    }
    ```
    
    | option | type | default | effect |
    |---|---|---|---|
    | `baseUrl` | string | `servers[0].url`, else `http://localhost:4021` | the base URL written into the document |
    | `contact` | string | `info.contact.email` / `info.contact.url` | the contact line |
    | `defaultPrice` | string | — | price for paid operations with no determinable amount |
    | `frontmatter` | boolean | `true` | emit the YAML frontmatter block |
    | `rails` | array | the suite's dual-rail pair | rails to document; see [spec §5.3](spec.md) |
    | `manifestPath` | string | `/.well-known/x402` | manifest path in the service card |
    | `openapiPath` | string | `/openapi.json` | OpenAPI path in the service card |
    
    ### How pricing is determined
    
    In order of preference:
    
    1. `x-x402.price` (or `x-402.price`) on the operation — `"$0.001"`
    2. the `maxAmountRequired` in a 402 response **example**, converted from USDC base units
    3. `options.defaultPrice`
    4. otherwise: an operation with **no 402 response** is treated as `free`
    
    Every guess produces an entry in `warnings`. The generator never invents a price silently.
    
    ### Response 200
    
    ```json
    {
      "skillMdVersion": "1.0",
      "skillMd": "---\nskillMd: \"1.0\"\nname: x402-podcasts\n---\n\n# x402-podcasts\n…",
      "service": {
        "name": "x402-podcasts",
        "baseUrl": "http://localhost:4028",
        "endpoints": 6,
        "paid": 2
      },
      "warnings": [],
      "validation": {
        "valid": true,
        "score": 100,
        "summary": { "errors": 0, "warnings": 0, "infos": 0 },
        "findings": []
      },
      "generatedAt": "2026-08-07T12:00:00.000Z",
      "receipt": { "success": true, "rail": "evm", "network": "base-sepolia", "transaction": "0x…" }
    }
    ```
    
    | field | meaning |
    |---|---|
    | `skillMd` | the file, ready to write to disk and commit |
    | `service.endpoints` / `service.paid` | how many operations were found, and how many are paid |
    | `warnings` | anything guessed or missing from the source document |
    | `validation` | the generator's own output run through the validator — a generated file should be conformant, and if it is not, the OpenAPI was thin |
    
    ### Errors
    
    | status | `error` | cause |
    |---|---|---|
    | 400 | `BAD_REQUEST` | body is not a JSON object |
    | 400 | `NOT_AN_OPENAPI_DOCUMENT` | the posted JSON has no `paths` object |
    | 400 | `GENERATE_FAILED` | conversion threw; `message` says why |
    | 402 | — | payment required or rejected |
    
    ---
    
    ## `POST /validate`
    
    **Price:** $0.002 · **Returns:** the full validation report
    
    ### Request body
    
    Either raw markdown with `content-type: text/markdown` (or `text/plain`), or JSON:
    
    ```json
    { "skillMd": "# my-service\n\nDoes a thing.\n\n## Payment\n…" }
    ```
    
    Limit 2 MB.
    
    ### Response 200
    
    **A non-conformant document is a 200, not an error.** You paid for the report, and the report
    is the artifact. Only an unparseable *request* is a 4xx.
    
    ```json
    {
      "skillMdVersion": "1.0",
      "valid": false,
      "score": 0,
      "summary": { "errors": 8, "warnings": 3, "infos": 2 },
      "service": {
        "name": "broken-service",
        "baseUrl": null,
        "contact": null,
        "endpoints": [],
        "rails": { "evm": false, "solana": false },
        "dualRail": false
      },
      "findings": [
        {
          "rule": "SM002",
          "severity": "error",
          "title": "Missing summary paragraph",
          "detail": "The text under the title is only 10 characters — too short to tell an agent what this service does.",
          "fix": "Put one prose paragraph directly under the H1 saying what the service does and what an agent gets from it.",
          "line": 1
        },
        {
          "rule": "SM006",
          "severity": "error",
          "title": "No Solana payment rail documented",
          "detail": "There is no Payment section, so no Solana rail is documented.",
          "fix": "Name a Solana network (`solana` or `solana-devnet`) and its `payTo` address in the Payment section. Every conformant skill.md is dual-rail: an agent's wallet lives on one chain, and it must be able to tell from this file alone whether it can pay."
        }
      ],
      "checkedAt": "2026-08-07T12:00:00.000Z",
      "receipt": { "success": true, "rail": "solana", "network": "solana", "transaction": "5Qm…" }
    }
    ```
    
    ### Fields
    
    | field | type | meaning |
    |---|---|---|
    | `valid` | boolean | **zero errors.** Independent of `score` — this is the gate |
    | `score` | 0–100 | 100 − 12 per error − 4 per warning − 1 per info, floored at 0. A quality signal |
    | `summary` | object | counts by severity |
    | `service.name` | string \| null | the H1 |
    | `service.baseUrl` | string \| null | the URL found on a "Base URL" line |
    | `service.contact` | string \| null | the first email in the document |
    | `service.endpoints` | array | `{ method, path, price }` for every conformant endpoint heading |
    | `service.rails` | `{ evm, solana }` | whether each rail was found in the Payment section |
    | `service.dualRail` | boolean | both — the thing rules SM005/SM006 exist to enforce |
    | `findings` | `Finding[]` | see below |
    | `checkedAt` | ISO 8601 | when the check ran |
    
    #### `Finding`
    
    | field | type | meaning |
    |---|---|---|
    | `rule` | string | stable rule id, e.g. `SM006`. Never reused, even if a rule is retired |
    | `severity` | `error` \| `warning` \| `info` | see [spec §9](spec.md) |
    | `title` | string | the rule's name |
    | `detail` | string | what is wrong **in this document** |
    | `fix` | string | the remedy, verbatim from the catalogue |
    | `line` | integer | 1-based, when the problem has a location |
    
    ### Errors
    
    | status | `error` | cause |
    |---|---|---|
    | 400 | `BAD_REQUEST` | empty body, or neither markdown nor `{ skillMd }` |
    | 400 | `VALIDATE_FAILED` | the file could not be parsed at all |
    | 402 | — | payment required or rejected |
    
    ---
    
    ## `GET /rules` — free
    
    The whole catalogue. Free on purpose: you should be able to see what you are being judged
    against before paying to be judged, and most problems are cheaper to avoid than to diagnose.
    
    ```json
    {
      "skillMdVersion": "1.0",
      "count": 23,
      "rules": [
        {
          "id": "SM006",
          "severity": "error",
          "title": "No Solana payment rail documented",
          "fix": "Name a Solana network (`solana` or `solana-devnet`) and its `payTo` address in the Payment section…"
        }
      ]
    }
    ```
    
    Same data as `npx x402-skill-md rules --json`.
    
    ## `GET /spec` — free
    
    The [format specification](spec.md), as `text/markdown`.
    
    ## Other free routes
    
    | route | returns |
    |---|---|
    | `GET /` | service card: rails, prices, format version, library and CLI hints |
    | `GET /health` | `{ ok: true, uptime: <seconds>, skillMdVersion }` |
    | `GET /skill.md` | this service's own contract file — a worked example of the format |
    | `GET /.well-known/x402` | resource manifest with prices, rails and schemas |
    | `GET /openapi.json` | OpenAPI 3.1 |
    
    ---
    
    ## Library API
    
    The HTTP routes are thin wrappers over exported functions:
    
    ```ts
    import {
      openapiToSkillMd,   // POST /generate
      validateSkillMd,    // POST /validate
      parseSkillMd,       // the structure behind the report
      formatReport,       // the CLI's human rendering
      RULES,              // GET /rules
      DEFAULT_RAILS,
      SKILL_MD_VERSION,
    } from "x402-skill-md";
    ```
    
    | export | signature |
    |---|---|
    | `openapiToSkillMd` | `(doc, options?) => { skillMd, service, warnings }` |
    | `validateSkillMd` | `(raw: string) => ValidationReport` |
    | `validateParsed` | `(doc: ParsedSkill) => ValidationReport` |
    | `parseSkillMd` | `(raw: string) => ParsedSkill` |
    | `findSection` | `(doc, title) => ParsedSection \| undefined` |
    | `formatReport` | `(report) => string` |
    
    `ParsedSkill` carries `frontmatter`, `title`, `intro`, `sections`, `endpoints`,
    `malformedEndpointHeadings` and `codeBlocks` — enough to build your own tooling without
    re-parsing Markdown.
    
    ---
    
    ## Payment
    
    ### The 402 body
    
    ```jsonc
    {
      "x402Version": 1,
      "error": "X-PAYMENT header is required",
      "accepts": [ /* one PaymentRequirements per rail */ ]
    }
    ```
    
    `PaymentRequirements`:
    
    | field | example | notes |
    |---|---|---|
    | `scheme` | `"exact"` | the only scheme accepted |
    | `network` | `"base-sepolia"` / `"solana"` | switched by `NETWORK` and `SOLANA_NETWORK` |
    | `maxAmountRequired` | `"10000"` | base units; USDC has 6 decimals, so this is $0.01 |
    | `resource` | `"http://localhost:4029/generate"` | absolute URL being purchased |
    | `description` | `"Generate a conformant skill.md…"` | shown by wallets and the checkout modal |
    | `mimeType` | `"application/json"` | what the 200 will be |
    | `payTo` | `0x40252CF…` / `WwwuGbqH…` | receive address for that rail |
    | `maxTimeoutSeconds` | `60` | how long the authorisation stays valid |
    | `asset` | USDC address / SPL mint | the token you pay in |
    | `extra` | `{ name, version }` / `{ name, decimals, feePayer }` | EIP-712 domain on EVM; the fee sponsor on Solana |
    
    Facilitators are **per rail** — `FACILITATOR_URL` for EVM (default `https://x402.org/facilitator`,
    which settles Base Sepolia only) and `SOLANA_FACILITATOR_URL` for Solana (default
    `https://facilitator.payai.network`).
    
    ### The receipt
    
    Paid responses set `X-PAYMENT-RESPONSE` to base64 JSON and repeat it inline as `receipt`:
    
    ```json
    {
      "success": true,
      "rail": "evm",
      "network": "base-sepolia",
      "transaction": "0xabc…",
      "payer": "0xYourWallet",
      "amount": "10000",
      "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
      "resource": "http://localhost:4029/generate"
    }
    ```
    
    ### 402 reasons
    
    | `error` | meaning |
    |---|---|
    | `X-PAYMENT header is required` | first, unpaid attempt — normal |
    | `invalid X-PAYMENT header: …` | not base64, or not a valid x402 payload |
    | `unsupported rail: this endpoint does not accept exact on <network>` | you signed on a rail this server does not take |
    | `payment rejected: <reason>` | the facilitator's `invalidReason` |
    | `settlement failed: <reason>` | verified but could not be broadcast |
    
    A facilitator outage returns `502 facilitator_unreachable` or `502 settlement_error` rather
    than a 402, so a retry loop cannot mistake an outage for a price.
    
  • docs/favicon.svg423 B61d08fcdcc6fupstream ↗
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img" aria-label="x402">
      <defs>
        <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0" stop-color="#0052ff"/>
          <stop offset="1" stop-color="#14a37f"/>
        </linearGradient>
      </defs>
      <rect width="100" height="100" rx="22" fill="url(#g)"/>
      <path d="M56 16 L74 16 L74 84 L56 84 L56 68 L20 68 L20 52 Z" fill="#fff"/>
    </svg>
    
  • docs/index.html14 KB09596613e04bupstream ↗
    <!DOCTYPE html>
    <html lang="en">
    <head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>x402-skill-md — the skill.md format, generator and validator</title>
    <meta name="description" content="The SKILL-MD format specification, plus a library, CLI and x402-paid service to generate agent-discoverable skill.md files from OpenAPI and validate them against 23 rules.">
    <style>
    :root{
      --accent:#0052ff;--accent-2:#14f195;--bg:#ffffff;--fg:#0a0c10;--muted:#57606a;
      --card:#f6f8fa;--border:#d0d7de;--code-bg:#0d1117;--code-fg:#e6edf3;
    }
    @media (prefers-color-scheme:dark){
      :root{--bg:#0d1117;--fg:#e6edf3;--muted:#9198a1;--card:#161b22;--border:#30363d}
    }
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif;background:var(--bg);color:var(--fg);line-height:1.6;-webkit-text-size-adjust:100%}
    .wrap{max-width:900px;margin:0 auto;padding:0 20px}
    header{padding:72px 0 40px;text-align:center}
    .badge{display:inline-block;font-size:13px;font-weight:600;color:var(--accent);border:1px solid var(--accent);border-radius:999px;padding:2px 12px;margin-bottom:16px}
    h1{font-size:44px;letter-spacing:-1px;margin-bottom:12px}
    @media (max-width:560px){h1{font-size:32px}}
    .tagline{font-size:19px;color:var(--muted);max-width:660px;margin:0 auto 24px}
    .install{display:inline-block;max-width:100%;overflow-x:auto;background:var(--code-bg);color:var(--code-fg);font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:14px;padding:10px 18px;border-radius:8px;margin-bottom:24px;white-space:nowrap}
    .btns a{display:inline-block;margin:4px 6px;padding:10px 20px;border-radius:8px;font-weight:600;text-decoration:none;font-size:15px}
    .btn-primary{background:var(--accent);color:#fff}
    .btn-ghost{border:1px solid var(--border);color:var(--fg)}
    section{padding:32px 0}
    h2{font-size:26px;margin-bottom:14px;letter-spacing:-.5px}
    h3{font-size:18px;margin:20px 0 8px}
    p{margin-bottom:12px}
    ul{margin:0 0 12px 22px}
    li{margin-bottom:6px}
    .tablewrap{overflow-x:auto}
    table{width:100%;border-collapse:collapse;font-size:15px;min-width:520px}
    th,td{text-align:left;padding:10px 12px;border-bottom:1px solid var(--border);vertical-align:top}
    th{color:var(--muted);font-size:13px;text-transform:uppercase;letter-spacing:.05em}
    td code,p code,li code,th code{background:var(--card);border:1px solid var(--border);border-radius:4px;padding:1px 6px;font-size:13px;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
    .price{color:var(--accent);font-weight:700;white-space:nowrap}
    pre{background:var(--code-bg);color:var(--code-fg);border-radius:10px;padding:18px;overflow-x:auto;font-size:13.5px;line-height:1.55;font-family:ui-monospace,SFMono-Regular,Menlo,monospace}
    .rails{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:14px}
    .rail{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px 18px}
    .rail b{display:block;font-size:16px;margin-bottom:6px}
    .rail .k{color:var(--muted);font-size:13px}
    .rail code{font-size:12px;word-break:break-all}
    figure{margin:0}
    figcaption{color:var(--muted);font-size:13px;text-align:center;margin-top:8px}
    svg{max-width:100%;height:auto;display:block;margin:0 auto}
    footer{padding:40px 0;color:var(--muted);font-size:14px;text-align:center;border-top:1px solid var(--border);margin-top:48px}
    footer a{color:var(--accent);text-decoration:none}
    a{color:var(--accent)}
    </style>
    </head>
    <body>
    <header class="wrap">
      <div class="badge">x402 · USDC on Base + Solana</div>
      <h1>x402-skill-md</h1>
      <p class="tagline">The <code>skill.md</code> toolkit: the <b>format specification</b>, a generator, and a validator that returns a fix per finding. Ships as a library, a CLI, and an x402-paid service.</p>
      <div class="install">npx x402-skill-md generate openapi.json -o skill.md</div>
      <div class="btns">
        <a class="btn-primary" href="https://github.com/nirholas/x402-skill-md">GitHub</a>
        <a class="btn-ghost" href="tutorial.md">Tutorial</a>
        <a class="btn-ghost" href="api.md">API Reference</a>
        <a class="btn-ghost" href="agents.md">For AI Agents</a>
        <a class="btn-ghost" href="spec.md"><b>Specification</b></a>
      </div>
    </header>
    
    <section class="wrap">
      <h2>Pricing</h2>
      <div class="tablewrap"><table>
        <tr><th>Route</th><th>Price</th><th>What lands in the 200 body</th></tr>
        <tr><td><code>POST /generate</code></td><td class="price">$0.01</td><td>The generated <code>skill.md</code>, plus a validation report for it — one call gives you a file you can commit</td></tr>
        <tr><td><code>GET /rules</code></td><td class="price">free</td><td>The whole rule catalogue as JSON: id, severity, title and fix for each of the 23 rules</td></tr>
        <tr><td><code>GET /spec</code></td><td class="price">free</td><td>The SKILL-MD format specification</td></tr>
        <tr><td><code>POST /validate</code></td><td class="price">$0.002</td><td>The full report: every finding with its rule id, line, what is wrong <i>in this document</i>, and the exact fix</td></tr>
        <tr><td><code>GET /</code> · <code>/health</code> · <code>/skill.md</code> · <code>/.well-known/x402</code> · <code>/openapi.json</code></td><td class="price">free</td><td>Discovery</td></tr>
      </table></div>
    </section>
    
    <section class="wrap">
      <h2>How a paid call works</h2>
      <figure>
      <svg viewBox="0 0 880 250" role="img" aria-label="Flow: the agent requests a resource, the server answers 402 with two payment rails, the agent signs on Base or Solana, the facilitator verifies and settles, and the server returns 200 with the artifact.">
        <defs>
          <marker id="ah" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
            <path d="M0,0 L10,5 L0,10 z" fill="var(--accent)"/>
          </marker>
        </defs>
        <g font-family="-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif" text-anchor="middle">
          <!-- 1. agent -->
          <rect x="8" y="93" width="118" height="64" rx="10" fill="var(--card)" stroke="var(--border)"/>
          <text x="67" y="118" font-size="14" font-weight="600" fill="var(--fg)">Agent</text>
          <text x="67" y="137" font-size="11" fill="var(--muted)">GET /resource</text>
      
          <!-- 2. the 402 -->
          <rect x="166" y="93" width="170" height="64" rx="10" fill="var(--card)" stroke="var(--accent)"/>
          <text x="251" y="116" font-size="14" font-weight="600" fill="var(--accent)">402 Payment Required</text>
          <text x="251" y="136" font-size="11" fill="var(--muted)">accepts: [ two rails ]</text>
      
          <!-- 3a. EVM rail -->
          <rect x="376" y="25" width="164" height="58" rx="10" fill="var(--card)" stroke="var(--border)"/>
          <text x="458" y="47" font-size="13" font-weight="600" fill="var(--fg)">USDC on Base</text>
          <text x="458" y="65" font-size="11" fill="var(--muted)">EIP-3009 signature</text>
      
          <!-- 3b. Solana rail -->
          <rect x="376" y="167" width="164" height="58" rx="10" fill="var(--card)" stroke="var(--border)"/>
          <text x="458" y="189" font-size="13" font-weight="600" fill="var(--fg)">USDC on Solana</text>
          <text x="458" y="207" font-size="11" fill="var(--muted)">signed SPL transfer</text>
      
          <text x="458" y="130" font-size="12" font-style="italic" fill="var(--muted)">the client picks</text>
      
          <!-- 4. settle -->
          <rect x="576" y="93" width="170" height="64" rx="10" fill="var(--card)" stroke="var(--border)"/>
          <text x="661" y="116" font-size="13" font-weight="600" fill="var(--fg)">X-PAYMENT</text>
          <text x="661" y="136" font-size="11" fill="var(--muted)">facilitator: verify → settle</text>
      
          <!-- 5. the goods -->
          <rect x="786" y="93" width="86" height="64" rx="10" fill="var(--accent)"/>
          <text x="829" y="118" font-size="14" font-weight="700" fill="#fff">200</text>
          <text x="829" y="136" font-size="11" fill="#dbe6ff">artifact</text>
      
          <g fill="none" stroke="var(--accent)" stroke-width="2" marker-end="url(#ah)">
            <path d="M126,125 L158,125"/>
            <path d="M336,116 C356,116 356,54 368,54"/>
            <path d="M336,134 C356,134 356,196 368,196"/>
            <path d="M540,54 C556,54 556,116 568,116"/>
            <path d="M540,196 C556,196 556,134 568,134"/>
            <path d="M746,125 L778,125"/>
          </g>
        </g>
      </svg>
        <figcaption>One round trip of overhead. Every 402 quotes both rails; the client signs whichever chain it holds USDC on.</figcaption>
      </figure>
    </section>
    
    <section class="wrap">
      <h2>Two rails, one price</h2>
      <div class="rails">
        <div class="rail">
          <b>USDC on Base</b>
          <div class="k">network <code>base-sepolia</code> / <code>base</code></div>
          <div class="k">scheme <code>exact</code> · EIP-3009 <code>transferWithAuthorization</code></div>
          <div class="k">payTo <code>0x40252CFDF8B20Ed757D61ff157719F33Ec332402</code></div>
        </div>
        <div class="rail">
          <b>USDC on Solana</b>
          <div class="k">network <code>solana</code> / <code>solana-devnet</code></div>
          <div class="k">scheme <code>exact</code> · SPL <code>transferChecked</code>, fee sponsored</div>
          <div class="k">payTo <code>WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW</code></div>
        </div>
      </div>
      <p style="margin-top:14px">Both rails appear in the <code>accepts</code> array of every 402. Settlement runs through
      the <a href="https://x402.org/facilitator">x402 facilitator</a>, and the 200 carries an
      <code>X-PAYMENT-RESPONSE</code> receipt naming the rail and the transaction.</p>
    </section>
    
    <section class="wrap">
      <h2>Quickstart</h2>
      <pre># as a CLI — free, no wallet needed
    npx x402-skill-md generate openapi.json -o skill.md
    npx x402-skill-md validate skill.md
    
    #   PASS  score 100/100  (0 errors, 0 warnings, 0 infos)
    #   service: x402-domains  endpoints: 2  rails: evm + solana
    #   No findings. This document is conformant.
    
    # as a library
    npm i x402-skill-md
    
    import { openapiToSkillMd, validateSkillMd } from "x402-skill-md";
    const { skillMd, warnings } = openapiToSkillMd(spec, { baseUrl: "https://api.example.com" });
    validateSkillMd(skillMd).valid;          // true
    validateSkillMd(skillMd).service.dualRail; // true
    
    # in CI — exits 1 on errors
    - run: npx x402-skill-md validate skill.md --strict</pre>
    </section>
    
    <section class="wrap">
      <h2>What a skill.md is</h2>
      <p>The file an AI agent reads to learn how to <b>use and pay for</b> an HTTP service — in one fetch, with no SDK and no human. It is Markdown, so a model can read it directly; it has a defined section grammar, so a parser can check it.</p>
      <p>OpenAPI already describes shapes. It does not describe <b>what a call costs, in what asset, on which chains, to which address</b> — and for an agent with a wallet and no account, that is the load-bearing information. An agent has to answer three questions before it spends anything: can this service do what I need, what will it cost, and <i>can I pay at all</i>.</p>
      <p><a href="spec.md"><b>Read the specification →</b></a></p>
    </section>
    
    <section class="wrap">
      <h2>Dual rail is mandatory, not optional</h2>
      <p>A conformant <code>skill.md</code> documents an EVM rail <b>and</b> a Solana rail — rules SM005 and SM006, both <code>error</code> severity. Three reasons:</p>
      <ul>
        <li>An agent's wallet lives on exactly <b>one</b> chain. A single-rail service is invisible to every agent on the other one, and it cannot learn that cheaply.</li>
        <li>The 402 challenge already carries an <code>accepts</code> <b>array</b>. Two entries cost one extra object; the client picks. There is no protocol reason to publish one.</li>
        <li>Discovery indexers filter on rails. A single-rail listing halves your reachable market for no gain.</li>
      </ul>
      <p>The spec also requires the <b>facilitator to be declared per rail</b>, because facilitators are chain-specific: the reference <code>x402.org</code> facilitator settles Base Sepolia only and will not settle a Solana payment. A document naming one facilitator for both rails is telling clients something false.</p>
    </section>
    
    <section class="wrap">
      <h2>Findings carry fixes</h2>
      <p>A validator that only says “invalid” is not worth paying for. Every finding names the rule, where it is, what is wrong <i>in this document</i>, and the remedy:</p>
      <pre>{
      "rule": "SM006",
      "severity": "error",
      "title": "No Solana payment rail documented",
      "detail": "There is no Payment section, so no Solana rail is documented.",
      "fix": "Name a Solana network (`solana` or `solana-devnet`) and its `payTo`
              address in the Payment section. Every conformant skill.md is dual-rail:
              an agent's wallet lives on one chain, and it must be able to tell from
              this file alone whether it can pay."
    }</pre>
      <p>23 rules: 10 errors, 10 warnings, 3 infos. <code>valid</code> is true when there are <b>zero errors</b>, independent of score; the score (100 − 12/error − 4/warning − 1/info) is a quality signal, <code>valid</code> is the gate.</p>
    </section>
    
    <section class="wrap">
      <h2>Why a paid service at all</h2>
      <p>The library is free on npm and the CLI does the same work locally, so the honest answer is: <b>the paid routes exist for agents.</b> An agent that has just written an OpenAPI document cannot <code>npm install</code> inside its own reasoning loop; it can make an HTTP request and pay a cent. Humans should use the CLI. <code>GET /rules</code> and <code>GET /spec</code> are free on both paths, because you should be able to see what you are being judged against before paying to be judged.</p>
    </section>
    
    <footer class="wrap">
      <p>
        <a href="https://github.com/nirholas/x402-skill-md">GitHub</a> ·
        <a href="https://github.com/nirholas/x402-skill-md/blob/main/skill.md">skill.md</a> ·
        <a href="https://github.com/nirholas/x402-skill-md/blob/main/public/.well-known/x402">.well-known/x402</a> ·
        <a href="https://github.com/nirholas/x402-skill-md/blob/main/openapi.json">openapi.json</a> ·
        <a href="https://x402.org">x402 protocol</a> ·
        <a href="https://www.npmjs.com/package/x402-skill-md">npm</a>
      </p>
      <p>Part of the <a href="https://github.com/nirholas/x402-suite">x402 Suite</a> · Apache-2.0 · <a href="mailto:[email protected]">[email protected]</a></p>
    </footer>
    </body>
    </html>
    
  • docs/og.png35 KB40e3e4381e69upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • docs/spec.md24 KB722329e3e5b6upstream ↗
    # The SKILL-MD format, version 1.0
    
    **Status:** stable · **Version:** 1.0 · **Media type:** `text/markdown` · **Conventional
    location:** `/skill.md` at a service's root, and `skill.md` at a repository's root
    
    A `skill.md` is the file an AI agent reads to learn how to *use and pay for* an HTTP service,
    in one fetch, with no SDK and no human. It is Markdown, so a language model can read it
    directly; it has a defined section grammar, so a parser can check it. This document
    specifies that grammar.
    
    The reference implementation is [`x402-skill-md`](https://github.com/nirholas/x402-skill-md),
    which generates conformant documents from OpenAPI and validates existing ones against every
    rule below.
    
    ---
    
    ## 1. Why this exists
    
    OpenAPI already describes *shapes*. It does not describe **what a call costs, in what asset,
    on which chains, to which address** — and for an agent with a wallet and no account, that is
    the load-bearing information. An agent must be able to answer three questions before it
    spends anything:
    
    1. Can this service do what I need?
    2. What will it cost?
    3. **Can I pay at all** — is there a rail my wallet lives on?
    
    Question 3 is why this format makes **dual-rail payment mandatory** (§5). A service that
    only takes USDC on Base is unusable to an agent holding USDC on Solana, and an agent that
    cannot discover that fact until it has already made a request has wasted a round trip and a
    planning step. The file states both rails, or it is not conformant.
    
    ### Relationship to other artifacts
    
    | artifact | audience | answers |
    |---|---|---|
    | `skill.md` | language models, humans skimming | what, how much, how to pay, what comes back |
    | `/.well-known/x402` | indexers, machine planners | the same, as JSON, per resource |
    | `openapi.json` | code generators | exact request/response shapes |
    
    They are three renderings of one contract and should never disagree. `x402-skill-md
    generate` derives the first from the third, which is the cheapest way to keep them honest.
    
    ---
    
    ## 2. Terminology
    
    **MUST**, **SHOULD**, **MAY** are used as in RFC 2119.
    
    - **Document** — one `skill.md` file.
    - **Endpoint section** — a level-2 heading matching §6 plus everything until the next
      level-2 heading.
    - **Rail** — one chain on which a service accepts payment: a network, an asset, a `payTo`
      address and a facilitator.
    - **Conformant** — the document triggers no rule of severity `error` (§9).
    
    ---
    
    ## 3. Document structure
    
    A conformant document is, in order:
    
    ```
    [frontmatter]          optional, §4
    # <name>               required, exactly one H1
    <summary paragraph>    required, ≥ 40 characters of prose
    <service card>         required, must include a Base URL
    ## Payment             required, §5
    ## `M /path` — $price  one or more, §6
    ## Free routes         optional but recommended, §7
    ## Errors              required, §8
    ## Data source         required *if* responses may be non-live, §7.3
    <any other sections>   allowed anywhere after the summary
    ```
    
    Sections other than the ones named above are permitted and ignored by the validator. Order
    is not enforced beyond "H1 first" — but the order above is what readers expect, and the
    generator emits it.
    
    ### 3.1 Encoding
    
    UTF-8. `\n` or `\r\n` line endings (both are accepted; `\r\n` is normalised on parse). No
    byte-order mark.
    
    ### 3.2 Code fences
    
    Fenced blocks are opaque to the section grammar: a `#` inside a fence is never a heading. Use
    ` ```json ` for response and challenge examples — the validator looks for that fence
    specifically when checking rules SM009 and SM018. ` ```jsonc ` is accepted where you want
    comments in an example.
    
    ---
    
    ## 4. Frontmatter (optional)
    
    A document MAY begin with a `---`-delimited block of `key: value` lines. It exists so
    indexers can read the essentials without parsing prose.
    
    ```yaml
    ---
    skillMd: "1.0"
    name: x402-domains
    baseUrl: https://domains.example.com
    manifest: /.well-known/x402
    openapi: /openapi.json
    contact: [email protected]
    ---
    ```
    
    | key | required if frontmatter present | meaning |
    |---|---|---|
    | `skillMd` | yes | format version, currently `"1.0"` |
    | `name` | no | service identifier; SHOULD match the H1 |
    | `baseUrl` | no | absolute origin |
    | `manifest` | no | path to the x402 manifest |
    | `openapi` | no | path to the OpenAPI document |
    | `contact` | no | email or URL |
    
    This is intentionally *not* full YAML: values are plain strings, quotes are stripped, and
    nesting is not supported. A malformed block raises **SM022** (warning) — never an error,
    because frontmatter is optional and an indexer that cannot read it can still read the prose.
    
    ---
    
    ## 5. The Payment section
    
    A conformant document MUST have a level-2 section whose title begins with `Payment`. It is
    the part an agent needs before it can do anything else.
    
    ### 5.1 Required content
    
    | requirement | rule |
    |---|---|
    | An EVM network (`base` or `base-sepolia`) **and** a `0x…` payTo address | SM005 (error) |
    | A Solana network (`solana` or `solana-devnet`) **and** a base58 payTo address | SM006 (error) |
    | The x402 protocol version and scheme | SM016 (warning) |
    | A fenced JSON example of a real 402 body containing `accepts` | SM018 (warning) |
    
    ### 5.2 Dual rail is mandatory
    
    **A conformant `skill.md` documents at least one EVM rail and at least one Solana rail.**
    Not "may"; not "if convenient". The reasoning:
    
    - An agent's wallet lives on exactly **one** chain. A single-rail service is invisible to
      every agent on the other chain, and it cannot learn that cheaply.
    - The 402 challenge already carries an `accepts` **array**. Two entries cost one extra
      object; the client picks. There is no protocol reason to publish one.
    - Discovery indexers filter on rails. A single-rail listing halves your reachable market for
      no gain.
    
    If a service genuinely cannot settle on one of the two chains, it is not conformant to 1.0
    and should say so plainly rather than imply coverage it lacks.
    
    ### 5.3 Declaring rails
    
    The recommended rendering is a table — dense, parseable, and readable by a model:
    
    ```markdown
    | rail | network | asset | payTo | facilitator |
    |---|---|---|---|---|
    | evm | `base-sepolia` | USDC `0x036CbD53842c5426634e7929541eC2318f3dCF7e` | `0x40252CFDF8B20Ed757D61ff157719F33Ec332402` | `https://x402.org/facilitator` |
    | solana | `solana` | USDC `EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v` | `WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW` | `https://facilitator.payai.network` |
    ```
    
    Prose is also accepted — the validator looks for a network name and an address of the right
    shape within the section, not for a specific layout.
    
    #### The facilitator column is per rail, not per service
    
    This matters and is easy to get wrong. A facilitator is the component that verifies a signed
    payment and broadcasts settlement, and **facilitators are chain-specific**. The reference
    `https://x402.org/facilitator` settles Base Sepolia only; it will not settle a Solana
    payment. A service documenting both rails therefore has **two** facilitator endpoints, and
    the document MUST show which belongs to which network. A client that assumes one facilitator
    for the whole service will send a valid Solana payment to an endpoint that cannot settle it.
    
    The suite's convention, which implementations SHOULD follow:
    
    | rail | default facilitator | env override |
    |---|---|---|
    | evm | `https://x402.org/facilitator` | `FACILITATOR_URL` |
    | solana | `https://facilitator.payai.network` | `SOLANA_FACILITATOR_URL` |
    
    ### 5.4 The challenge example
    
    Include a real 402 body. This is the single highest-value block in the file, because a client
    can be written against it with no guessing:
    
    ```json
    {
      "x402Version": 1,
      "error": "X-PAYMENT header is required",
      "accepts": [
        {
          "scheme": "exact",
          "network": "base-sepolia",
          "maxAmountRequired": "1000",
          "resource": "https://domains.example.com/check/example.com",
          "description": "Live RDAP lookup for one domain",
          "mimeType": "application/json",
          "payTo": "0x40252CFDF8B20Ed757D61ff157719F33Ec332402",
          "maxTimeoutSeconds": 60,
          "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
          "outputSchema": { "input": { "…": "§5.5" }, "output": { "…": "§5.5" } },
          "extra": { "name": "USDC", "version": "2" }
        },
        {
          "scheme": "exact",
          "network": "solana",
          "maxAmountRequired": "1000",
          "resource": "https://domains.example.com/check/example.com",
          "description": "Live RDAP lookup for one domain",
          "mimeType": "application/json",
          "payTo": "WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW",
          "maxTimeoutSeconds": 60,
          "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
          "outputSchema": { "input": { "…": "§5.5" }, "output": { "…": "§5.5" } },
          "extra": {
            "name": "USD Coin",
            "decimals": 6,
            "feePayer": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4"
          }
        }
      ]
    }
    ```
    
    Notes a document SHOULD make explicit, because clients get them wrong:
    
    - **`maxAmountRequired` is in the asset's base units.** USDC has six decimals, so `"1000"`
      is $0.001 and `"10000"` is $0.01. It is not dollars and it is not a float.
    - **`extra` differs per rail.** On EVM it is the EIP-712 domain the wallet needs to build the
      `transferWithAuthorization` signature. On Solana it carries `decimals` and `feePayer` — the
      facilitator's sponsor account, which pays the SOL network fee so a buyer holding only USDC
      can still transact.
    - **`resource` is the absolute URL being purchased**, path only. If the price does not vary
      with the query string, say so.
    - **`outputSchema` is elided above only to keep the example readable.** In a real challenge it
      is spelled out in full on every entry — see §5.5.
    
    ### 5.5 The invocation contract (`outputSchema`)
    
    Every accept entry SHOULD carry an `outputSchema` — the machine-readable half of what the rest
    of this document says in prose. It is what lets an agent that has never seen the service
    decide whether the call is worth the price and then make it correctly, from the challenge
    alone, with no second fetch:
    
    ```json
    "outputSchema": {
      "input": {
        "type": "http",
        "method": "GET",
        "queryParams": {
          "max": { "type": "integer", "minimum": 1, "maximum": 20, "default": 5 }
        },
        "pathParams": {
          "domain": { "type": "string", "description": "Registrable domain; IDNs punycode", "x-required": true }
        }
      },
      "output": {
        "type": "object",
        "required": ["domain", "status", "checkedAt"],
        "properties": {
          "domain": { "type": "string" },
          "status": { "type": "string", "enum": ["registered", "available", "unknown"] },
          "checkedAt": { "type": "string", "format": "date-time" }
        }
      }
    }
    ```
    
    - **`input`** describes how to build the request. `type` is `"http"` and `method` is the
      route's verb. Parameters are split by where they go: `queryParams` for the query string,
      `pathParams` for `:segments` in the path. A route with a body instead sets
      `bodyType: "json"` and lists its top-level fields in `bodyFields`. Every value is a JSON
      Schema for that one parameter or field; mark the mandatory ones `"x-required": true`, since
      a per-field schema has nowhere to put JSON Schema's document-level `required` array.
    - **`output`** is the JSON Schema of the 200 body — the artifact being sold (§6.3), not a
      wrapper around it.
    
    Two rules keep this honest:
    
    1. **Derive both halves from the OpenAPI document, do not hand-write them.** The endpoint's
       `parameters` / `requestBody` become `input`; its `200` response schema becomes `output`,
       with `$ref`s resolved so the challenge stands alone. Discovery crawlers treat the runtime
       402 as authoritative, so a hand-maintained copy that drifts from the spec is worse than no
       copy at all.
    2. **Every accept entry carries the same contract.** Which rail a buyer pays on cannot change
       what the endpoint accepts or returns, so `outputSchema` MUST be identical across the EVM
       and Solana entries.
    
    Omitting `input` or `output` is what the x402scan discovery audit reports as
    `SCHEMA_INPUT_MISSING` / `SCHEMA_OUTPUT_MISSING`, both **errors** — it is the difference
    between a service an agent can find and one it can actually use.
    
    ### 5.6 The paywall answers first
    
    **A paid route MUST return its 402 challenge before it validates the request or checks that
    the thing being asked for exists.** An unpaid `GET /verify/no-such-id-123` or an unpaid
    `POST /register` with an empty body is a *quote request*, not a malformed call, and the only
    correct answer to it is the challenge.
    
    A document SHOULD say so explicitly, because it is what makes a route probe-able:
    
    ```markdown
    Every paid route answers an unpaid request with 402 regardless of what you send. A synthetic
    id or an empty body still returns the challenge, so you can price a call before making it.
    ```
    
    This is not a nicety. Discovery crawlers — and any agent deciding whether a service is worth
    using — probe paid routes without paying to read the price and the contract. A route that
    answers `404 NOT_FOUND` or `400 BAD_REQUEST` first looks broken rather than paid: x402scan
    skips it, and the service can end up passing an audit while being unregisterable. It is also
    an information leak, since a 404-before-402 tells an unpaying caller which ids exist.
    
    In practice this is an ordering rule about middleware. The paywall must sit *above* every
    paid handler, and the handler must assume it only ever runs on a settled request:
    
    ```ts
    app.use(paywall(PAID_ROUTES, { … }));      // 402 for anything unpaid
    app.get("/verify/:id", (req, res) => {      // only reached after settlement
      const record = store.get(req.params.id);
      if (!record) return res.status(404).json({ error: "NOT_FOUND" });
      …
    });
    ```
    
    A 404 for a genuinely unknown id is correct — *after* payment. Services that cannot charge for
    a miss should validate cheaply and refund, or price the lookup as the search it is.
    
    ### 5.7 The receipt
    
    A document SHOULD state that paid responses carry `X-PAYMENT-RESPONSE`: base64 JSON naming
    the rail, network, transaction and payer. Agents use it to reconcile spend.
    
    ---
    
    ## 6. Endpoint sections
    
    ### 6.1 Heading grammar
    
    ```
    ## `METHOD /path` — price
    ```
    
    Formally:
    
    ```
    heading   = "##" WS "`" method WS path "`" WS dash WS price
    method    = 1*UPPER                       ; GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
    path      = "/" *VCHAR                    ; ":param" for path parameters
    dash      = "—" / "–" / "-"
    price     = "free" / "$" 1*DIGIT [ "." 1*DIGIT ]
    ```
    
    Regex, as implemented:
    
    ```
    /^##\s+`([A-Z]+)\s+(\/\S*)`\s*[—–-]\s*(free|\$\d+(?:\.\d+)?)\s*$/
    ```
    
    Conformant:
    
    ```markdown
    ## `GET /check/:domain` — $0.001
    ## `POST /bounties` — $0.01
    ## `GET /skills.json` — free
    ```
    
    Not conformant (each raises **SM008**):
    
    ```markdown
    ### POST /bounties — $0.01          ← level 3, and no backticks
    ## POST /bounties ($0.01)           ← price not after a dash
    ## `POST /bounties` — 1 cent        ← price not a literal
    ## `POST /bounties` — from $0.01    ← ranges are not prices (SM017)
    ```
    
    Path parameters use `:name`, not `{name}`. A generator converting from OpenAPI MUST rewrite
    `/check/{domain}` to `/check/:domain`.
    
    ### 6.2 Required content per endpoint
    
    | requirement | rule |
    |---|---|
    | ≥ 20 characters of prose saying what the 200 body contains | SM010 (warning) |
    | A fenced ` ```json ` block showing a real 200 body | SM009 (warning) |
    | A parameter table, if the path has `:params` or examples show a query string | SM011 (warning) |
    | A price matching the literal grammar | SM017 (warning) |
    
    Parameter tables SHOULD use these columns:
    
    ```markdown
    | name | in | type | required | notes |
    |---|---|---|---|---|
    | `domain` | path | string | yes | Registrable domain; IDNs must be punycode |
    | `max` | query | integer | no | 1–20, default 5. Clamped, not rejected |
    | `amount` | body | number | yes | Pledged bounty in USD |
    ```
    
    `in` is one of `path`, `query`, `body`, `header`.
    
    ### 6.3 The 200 body is the product
    
    The x402 suite's rule, which this format assumes: **every paid route returns the purchased
    artifact in the 200 response body.** No job ids, no callbacks, nothing to poll. If a document
    describes a paid route that returns only an acknowledgement, the design is wrong, not the
    document — restructure it as pay-per-poll (return a snapshot now) or return a signed claim
    instrument immediately.
    
    The response example is what an agent plans against, so make it a *real* one. Paste actual
    server output, do not hand-write plausible-looking values, and regenerate examples when the
    shape changes.
    
    ---
    
    ## 7. Other sections
    
    ### 7.1 Free routes
    
    A table of unpaid routes. Recommended, not required (**SM021**, info). It is how an agent
    orients itself for nothing:
    
    ```markdown
    | route | returns |
    |---|---|
    | `GET /` | service card: rails, prices, doc links |
    | `GET /health` | `{ ok, uptime }` |
    | `GET /skill.md` | this file |
    | `GET /.well-known/x402` | machine-readable resource manifest |
    | `GET /openapi.json` | OpenAPI 3.1 |
    ```
    
    Free routes MAY instead be written as endpoint sections with price `free`.
    
    ### 7.2 Discovery links
    
    A document SHOULD reference `/.well-known/x402` (**SM014**, warning) and `/openapi.json`
    (**SM019**, info), and give a contact address (**SM015**, warning).
    
    ### 7.3 Data source
    
    **If any response can be something other than live upstream data — a fixture, a cache, a
    stub — the document MUST say so**, and responses SHOULD carry a `source` field naming which.
    A document mentioning fixtures without a `## Data source` section raises **SM023**.
    
    ```markdown
    | `source` | meaning |
    |---|---|
    | `podcastindex-live` | fetched from the upstream API at request time |
    | `fixture` | this deployment has no credentials and served a deterministic sample |
    ```
    
    This exists because a paid response that quietly returns canned data is fraud, and because
    env-gated demos are otherwise genuinely useful — the honest version is to ship fixtures *and*
    label them in every response.
    
    ---
    
    ## 8. Errors
    
    A conformant document MUST have a section whose title begins with `Error` (**SM012**), and it
    SHOULD document the 402 (**SM013**) — the response every paying client meets first.
    
    ```markdown
    | status | body `error` | meaning |
    |---|---|---|
    | 400 | `INVALID_DOMAIN` | the path segment is not a valid domain |
    | 402 | — | payment required or rejected; body is the dual-rail challenge |
    | 502 | `RDAP_UPSTREAM_ERROR` | upstream failed |
    ```
    
    A document SHOULD also list the 402 sub-reasons an agent can act on differently — an
    unpaid first attempt, a malformed header, a rail the service does not take, and a
    facilitator rejection are four different situations with four different retries.
    
    Implementations SHOULD return a 5xx rather than a 402 when the *facilitator* is unreachable,
    so a retry loop cannot mistake an outage for a price.
    
    ---
    
    ## 9. Rule catalogue
    
    Severity: **error** — not conformant, an agent may fail to use the service. **warning** —
    conformant but lossy, an agent has to guess. **info** — a suggestion.
    
    Ids are stable forever. A rule may be retired; its id is never reused.
    
    | id | severity | title |
    |---|---|---|
    | SM001 | error | Missing H1 title |
    | SM002 | error | Missing summary paragraph |
    | SM003 | error | Missing base URL |
    | SM004 | error | Missing `## Payment` section |
    | SM005 | error | No EVM payment rail documented |
    | SM006 | error | No Solana payment rail documented |
    | SM007 | error | No endpoints documented |
    | SM008 | error | Endpoint heading does not match the grammar |
    | SM009 | warning | Endpoint has no response example |
    | SM010 | warning | Endpoint has no description |
    | SM011 | warning | Endpoint takes parameters but does not document them |
    | SM012 | error | Missing `## Errors` section |
    | SM013 | warning | Errors section does not mention 402 |
    | SM014 | warning | No link to the x402 manifest |
    | SM015 | warning | No contact address |
    | SM016 | warning | No `x402Version` / protocol version stated |
    | SM017 | warning | Price is not a well-formed literal |
    | SM018 | warning | No 402 challenge example |
    | SM019 | info | No OpenAPI reference |
    | SM020 | error | Placeholder text left in the document |
    | SM021 | info | No free routes documented |
    | SM022 | warning | Frontmatter is present but malformed |
    | SM023 | info | Data provenance not documented |
    
    Machine-readable, with the fix text for each: `GET /rules` on any deployment, or
    `npx x402-skill-md rules --json`.
    
    ### 9.1 Scoring
    
    A document scores out of 100, losing 12 per error, 4 per warning, 1 per info, floored at 0.
    `valid` is `true` when there are **zero errors**, independent of score — the score is a
    quality signal, `valid` is the gate.
    
    ---
    
    ## 10. Conformance
    
    A **conformant SKILL-MD 1.0 document** triggers no rule of severity `error`.
    
    A **conformant validator** implements every rule in §9 with the stated severities and reports
    each finding with its rule id, a document-specific detail, and the remedy.
    
    A **conformant generator** emits documents that its own validator passes, and warns —
    without failing — when the source material lacks something the format wants (no summary, no
    price, no response schema). Silently inventing a price would be worse than a warning.
    
    ---
    
    ## 11. Versioning
    
    The version is `major.minor`, declared in frontmatter as `skillMd`.
    
    - **Minor** bumps add rules or optional structure. A 1.0 document stays conformant under
      1.1; new rules arrive at `warning` or `info` severity, never `error`.
    - **Major** bumps may change the section grammar or promote a rule to `error`.
    
    A document with no frontmatter is assumed to be 1.0. Validators SHOULD accept documents
    declaring a *newer* minor version and validate them against the rules they know.
    
    ---
    
    ## 12. A minimal conformant document
    
    Every rule satisfied, nothing extra:
    
    ````markdown
    ---
    skillMd: "1.0"
    name: example-service
    ---
    
    # example-service
    
    Returns the current UTC time as a signed timestamp, so an agent can prove it asked at a
    particular moment. One paid route, one free route, no account required.
    
    - **Base URL:** `https://example.com`
    - **Manifest:** `GET /.well-known/x402`
    - **OpenAPI:** `GET /openapi.json`
    - **Contact:** [email protected]
    
    ## Payment
    
    x402 v1, scheme `exact`. Unpaid requests get a 402 whose `accepts` array lists both rails —
    USDC on Base and USDC on Solana. Your client picks.
    
    | rail | network | payTo | facilitator |
    |---|---|---|---|
    | evm | `base-sepolia` | `0x40252CFDF8B20Ed757D61ff157719F33Ec332402` | `https://x402.org/facilitator` |
    | solana | `solana` | `WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW` | `https://facilitator.payai.network` |
    
    ```json
    {
      "x402Version": 1,
      "error": "X-PAYMENT header is required",
      "accepts": [
        { "scheme": "exact", "network": "base-sepolia", "maxAmountRequired": "1000",
          "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
          "payTo": "0x40252CFDF8B20Ed757D61ff157719F33Ec332402",
          "resource": "https://example.com/now", "mimeType": "application/json",
          "maxTimeoutSeconds": 60, "extra": { "name": "USDC", "version": "2" } },
        { "scheme": "exact", "network": "solana", "maxAmountRequired": "1000",
          "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
          "payTo": "WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW",
          "resource": "https://example.com/now", "mimeType": "application/json",
          "maxTimeoutSeconds": 60,
          "extra": { "name": "USD Coin", "decimals": 6, "feePayer": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4" } }
      ]
    }
    ```
    
    Amounts are USDC base units — `"1000"` is $0.001. Paid responses carry an
    `X-PAYMENT-RESPONSE` receipt.
    
    ## `GET /now` — $0.001
    
    The current UTC time with an HMAC signature over it, in this response body.
    
    ```json
    { "now": "2026-08-07T12:00:00.000Z", "signature": "9f2c…" }
    ```
    
    ## Free routes
    
    | route | returns |
    |---|---|
    | `GET /health` | `{ ok, uptime }` |
    | `GET /.well-known/x402` | machine-readable resource manifest |
    | `GET /openapi.json` | OpenAPI 3.1 |
    
    ## Errors
    
    | status | meaning |
    |---|---|
    | 402 | payment required or rejected; body is the dual-rail challenge |
    | 500 | signing failed |
    ````
    
    Check any document against this specification:
    
    ```bash
    npx x402-skill-md validate skill.md
    npx x402-skill-md validate skill.md --json --strict
    ```
    
    ---
    
    *SKILL-MD 1.0 · maintained alongside the [x402 Suite](https://github.com/nirholas/x402-suite)
    · corrections to [email protected]*
    
  • docs/tutorial.md10 KB66c9b69023abupstream ↗
    # Tutorial — write a skill.md that agents can actually use
    
    Three ways in, in increasing order of ceremony: the CLI (free, local), the library (free,
    embedded), and the paid service (for agents that cannot install anything). Start with the
    CLI.
    
    ## 1. Validate something you already have
    
    No install, no clone, no wallet:
    
    ```bash
    npx x402-skill-md validate skill.md
    ```
    
    ```
    PASS  score 100/100  (0 errors, 0 warnings, 0 infos)
    service: x402-domains  endpoints: 2  rails: evm + solana
    
    No findings. This document is conformant.
    ```
    
    A file with problems tells you what they are and how to fix them:
    
    ```bash
    printf '# broken-service\n\nToo short.\n\n## `GET /thing` — 1 cent\n' > broken.md
    npx x402-skill-md validate broken.md
    ```
    
    ```
    FAIL  score 0/100  (8 errors, 3 warnings, 2 infos)
    service: broken-service  endpoints: 0  rails: none
    
    ✗ SM002 (line 1)  Missing summary paragraph
        The text under the title is only 10 characters — too short to tell an agent what this service does.
        fix: Put one prose paragraph directly under the H1 saying what the service does and what an agent gets from it.
    ✗ SM003  Missing base URL
        No line documents a base URL with a resolvable URL on it.
        fix: Document the base URL, e.g. `- **Base URL:** https://your-host` (a localhost placeholder is fine for a self-hosted service).
    ✗ SM006  No Solana payment rail documented
        There is no Payment section, so no Solana rail is documented.
        fix: Name a Solana network (`solana` or `solana-devnet`) and its `payTo` address in the Payment section. Every conformant skill.md is dual-rail: an agent's wallet lives on one chain, and it must be able to tell from this file alone whether it can pay.
    ✗ SM008 (line 5)  Endpoint heading does not match the grammar
        `## `GET /thing` — 1 cent` names an HTTP method but does not match the endpoint grammar.
        fix: Use ``## `METHOD /path` — $0.001`` : backticked `METHOD /path`, a dash, then `$amount` or `free`.
    …
    ```
    
    `--json` gives the same thing as a machine-readable report. `--strict` also fails on
    warnings.
    
    ## 2. See the rules before you write anything
    
    ```bash
    npx x402-skill-md rules
    npx x402-skill-md rules --json | jq '.rules | to_entries[] | select(.value.severity=="error") | .key'
    ```
    
    23 rules: 10 errors, 10 warnings, 3 infos. Reading them first turns validation from a
    diagnosis into a checklist. The full normative text is the [specification](spec.md).
    
    ## 3. Generate from OpenAPI
    
    If you already have an OpenAPI document, do not write the file by hand:
    
    ```bash
    npx x402-skill-md generate openapi.json -o skill.md \
      --base-url https://api.example.com \
      --contact [email protected]
    ```
    
    ```
    wrote skill.md — x402-podcasts, 2 paid of 6 endpoints
    ```
    
    Then check it:
    
    ```bash
    npx x402-skill-md validate skill.md
    # PASS  score 100/100
    ```
    
    ### Declaring prices
    
    The generator reads a price from, in order of preference:
    
    1. **`x-x402` on the operation** — the exact, declarative way:
    
       ```json
       "/search": {
         "get": {
           "summary": "Search shows",
           "x-x402": { "price": "$0.002" },
           "responses": { "200": {…}, "402": {…} }
         }
       }
       ```
    
    2. The `maxAmountRequired` in a **402 response example**, converted from USDC base units.
    3. `--default-price` from the command line.
    4. Otherwise: an operation with **no 402 response** is treated as free.
    
    Anything guessed comes back as a warning on stderr:
    
    ```
    warning: GET /reports: declares a 402 response but no price could be determined.
             Add `x-x402: { price: "$0.001" }` to the operation, or pass defaultPrice.
    ```
    
    The generator never invents a price silently. A wrong price in a published contract is worse
    than a missing one.
    
    ### What the generator cannot invent
    
    It will warn, not guess, when the OpenAPI document is thin:
    
    | missing in OpenAPI | effect | fix |
    |---|---|---|
    | `info.summary` / `info.description` | the summary paragraph is a stub | write one — it is the first thing a model reads |
    | a 200 example or schema | the endpoint section has no response example (SM009) | add `content.application/json.example` |
    | operation `description` | the section says "Returns the resource in the 200 body" | write one sentence saying what the body contains |
    
    A generated file is a good 80%. The last 20% — the sentence explaining what `status: unknown`
    means, the note that fixtures exist — is the part that makes an agent behave correctly, and
    only you can write it.
    
    ## 4. Use it as a library
    
    ```bash
    npm i x402-skill-md
    ```
    
    ```ts
    import { readFile, writeFile } from "node:fs/promises";
    import { openapiToSkillMd, validateSkillMd, RULES } from "x402-skill-md";
    
    const spec = JSON.parse(await readFile("openapi.json", "utf8"));
    
    const { skillMd, service, warnings } = openapiToSkillMd(spec, {
      baseUrl: "https://api.example.com",
      contact: "[email protected]",
      defaultPrice: "$0.001",
      frontmatter: true,
    });
    
    for (const w of warnings) console.warn(w);
    await writeFile("skill.md", skillMd);
    
    const report = validateSkillMd(skillMd);
    console.log(report.valid, report.score);          // true 100
    console.log(report.service.dualRail);             // true
    console.log(report.service.endpoints);            // [{ method, path, price }, …]
    
    // The rule catalogue is exported too — build your own reporting on it.
    console.log(RULES.SM006.fix);
    ```
    
    The parser is also exported if you want to do something else with the structure:
    
    ```ts
    import { parseSkillMd } from "x402-skill-md";
    const doc = parseSkillMd(await readFile("skill.md", "utf8"));
    doc.endpoints;      // [{ method, path, price, line, hasJsonExample, … }]
    doc.frontmatter;    // { skillMd: "1.0", name: "…", … } | null
    doc.sections;       // every level-2 section that is not an endpoint
    ```
    
    ## 5. Put it in CI
    
    `validate` exits 1 when there are errors, so it needs no wrapper:
    
    ```yaml
    - name: skill.md is conformant
      run: npx x402-skill-md validate skill.md --strict
    ```
    
    Add `--strict` once you are at zero warnings, so the file cannot quietly degrade. If you
    generate the file, regenerate and diff instead — a `skill.md` that has drifted from its
    OpenAPI is worse than none, because an agent will trust it:
    
    ```yaml
    - run: npx x402-skill-md generate openapi.json > /tmp/skill.md
    - run: diff -u skill.md /tmp/skill.md
    ```
    
    ## 6. Run the service
    
    Everything above is free and local. The HTTP service exists for agents that cannot install
    anything:
    
    ```bash
    git clone https://github.com/nirholas/x402-skill-md.git
    cd x402-skill-md
    npm install
    cp .env.example .env      # already filled with working defaults
    npm run dev               # http://localhost:4029
    ```
    
    ```
    x402-skill-md listening on http://localhost:4029
    SKILL-MD format version 1.0 · 23 rules
    Payment rails (USDC — the client picks):
      evm     base-sepolia   → 0x40252CFDF8B20Ed757D61ff157719F33Ec332402  via https://x402.org/facilitator
      solana  solana         → WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW  via https://facilitator.payai.network
    Routes:
      POST /generate         $0.01
      POST /validate         $0.002
      GET /rules             free
      GET /spec              free
    ```
    
    The free routes work immediately:
    
    ```bash
    curl -s localhost:4029/rules | jq '{skillMdVersion, count}'
    # { "skillMdVersion": "1.0", "count": 23 }
    curl -s localhost:4029/spec | head -3
    ```
    
    ## 7. Your first 402
    
    ```bash
    curl -i -s -X POST localhost:4029/validate -H 'content-type: text/markdown' -d '# x'
    ```
    
    ```jsonc
    {
      "x402Version": 1,
      "error": "X-PAYMENT header is required",
      "accepts": [
        { "scheme": "exact", "network": "base-sepolia", "maxAmountRequired": "2000",
          "payTo": "0x40252CFDF8B20Ed757D61ff157719F33Ec332402",
          "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e", … },
        { "scheme": "exact", "network": "solana", "maxAmountRequired": "2000",
          "payTo": "WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW",
          "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", … }
      ]
    }
    ```
    
    `maxAmountRequired` is in USDC base units — six decimals, so `"2000"` is $0.002.
    
    ## 8. Pay for one
    
    Get a throwaway Base Sepolia wallet and a little testnet USDC from the
    [Circle faucet](https://faucet.circle.com), then:
    
    ```bash
    PRIVATE_KEY=0xyourTestnetKey npm run client
    ```
    
    ```
    SKILL-MD 1.0 — 23 rules, catalogue is free at GET /rules
    errors: 10, warnings: 10, infos: 3
    
    ① Unpaid request → expect a dual-rail 402
       accepts: base-sepolia   10000 base units USDC → 0x40252CF…
       accepts: solana         10000 base units USDC → WwwuGbqH…
    
    ② Generating from 0xYourWallet on Base Sepolia — $0.01
    
    ③ 200 — example-service: 1 paid of 2 endpoints
       generated file scores 100/100, valid: true
    
    --- skill.md ---
    …the whole file…
    ```
    
    ## 9. The one thing to understand about `/validate`
    
    A non-conformant document is a **200 with `valid: false`**, not a 4xx. You paid for the
    report and the report is the artifact — findings, fixes and all. Only an unparseable request
    (empty body, wrong content type) is an error.
    
    Agents get this wrong and retry. Say so in your tool description.
    
    ## 10. Going to mainnet
    
    ```bash
    # EVM rail → Base mainnet
    NETWORK=base
    FACILITATOR_URL=https://your-mainnet-facilitator.example
    
    # Solana rail → already mainnet by default
    SOLANA_NETWORK=mainnet-beta
    SOLANA_FACILITATOR_URL=https://facilitator.payai.network
    
    # your own addresses, or you are donating to the suite
    PAY_TO_ADDRESS=0xYourMainnetAddress
    SOLANA_PAY_TO_ADDRESS=YourSolanaAddress
    
    # so 402 challenges quote absolute public URLs
    PUBLIC_BASE_URL=https://skill-md.yourdomain.com
    ```
    
    Facilitators are **per rail**. `https://x402.org/facilitator` settles Base Sepolia only; the
    Solana rail needs its own, which is why `SOLANA_FACILITATOR_URL` exists and defaults to
    PayAI's public one. Pointing both rails at one facilitator will silently fail to settle one
    of them.
    
    Then list the deployment: submit your origin to [x402scan.com](https://x402scan.com), the
    x402 Bazaar, and [agentic.market](https://agentic.market). They read `/.well-known/x402`,
    which this server already serves.
    
    ## Next
    
    - **[Specification](spec.md)** — the format itself, normative
    - [API reference](api.md) — every field of every response
    - [For AI agents](agents.md) — discovery, MCP, listing
    - [`examples/curl.md`](https://github.com/nirholas/x402-skill-md/blob/main/examples/curl.md) — the protocol by hand
    
  • examples/agent-client.ts7.8 KB1387e225770eupstream ↗
    /**
     * examples/agent-client.ts — generate a skill.md, then validate it, paid.
     *
     *   PRIVATE_KEY=0x… npm run client
     *   PRIVATE_KEY=0x… OPENAPI=./path/to/openapi.json npm run client
     *
     * `wrapFetchWithPayment` catches each 402, picks the EVM requirement out of the
     * dual-rail `accepts` array, signs an EIP-3009 authorisation for the exact
     * amount, and retries with `X-PAYMENT`. The Solana rail is shown at the bottom.
     */
    import { readFileSync } from "node:fs";
    import { createWalletClient, http, publicActions } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { base, baseSepolia } from "viem/chains";
    import { wrapFetchWithPayment, decodeXPaymentResponse } from "x402-fetch";
    
    const BASE_URL = process.env.BASE_URL ?? "http://localhost:4029";
    const PRIVATE_KEY = process.env.PRIVATE_KEY as `0x${string}` | undefined;
    
    /** A tiny but complete OpenAPI document, used when none is supplied. */
    const SAMPLE_OPENAPI = {
      openapi: "3.1.0",
      info: {
        title: "example-service",
        summary:
          "Returns the current UTC time as a signed timestamp, so an agent can prove it asked at a particular moment.",
        contact: { email: "[email protected]" },
      },
      servers: [{ url: "https://example.com" }],
      paths: {
        "/now": {
          get: {
            operationId: "getNow",
            summary: "Current UTC time, signed",
            description: "The current UTC time with an HMAC signature over it, in this response body.",
            "x-x402": { price: "$0.001" },
            responses: {
              "200": {
                description: "The signed timestamp.",
                content: {
                  "application/json": {
                    example: { now: "2026-08-07T12:00:00.000Z", signature: "9f2c…" },
                  },
                },
              },
              "402": { description: "Payment required." },
            },
          },
        },
        "/health": {
          get: {
            operationId: "getHealth",
            summary: "Liveness probe (free)",
            responses: { "200": { description: "OK" } },
          },
        },
      },
    };
    
    function loadOpenapi(): Record<string, unknown> {
      const path = process.env.OPENAPI;
      if (!path) return SAMPLE_OPENAPI;
      return JSON.parse(readFileSync(path, "utf8"));
    }
    
    async function showChallenge(): Promise<void> {
      console.log(`\n① Unpaid request → expect a dual-rail 402\n   POST ${BASE_URL}/generate`);
      const res = await fetch(`${BASE_URL}/generate`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify(SAMPLE_OPENAPI),
      });
      const body = await res.json();
      console.log(`   HTTP ${res.status}`);
      for (const a of body.accepts ?? []) {
        console.log(
          `   accepts: ${String(a.network).padEnd(14)} ${a.maxAmountRequired} base units USDC → ${a.payTo}`,
        );
      }
    }
    
    async function main(): Promise<void> {
      // The rule catalogue is free — see what you'll be judged against first.
      const rules = await (await fetch(`${BASE_URL}/rules`)).json();
      console.log(`SKILL-MD ${rules.skillMdVersion} — ${rules.count} rules, catalogue is free at GET /rules`);
      console.log(
        `errors: ${rules.rules.filter((r: { severity: string }) => r.severity === "error").length}, ` +
          `warnings: ${rules.rules.filter((r: { severity: string }) => r.severity === "warning").length}, ` +
          `infos: ${rules.rules.filter((r: { severity: string }) => r.severity === "info").length}`,
      );
    
      await showChallenge();
    
      if (!PRIVATE_KEY) {
        console.log(
          "\nSet PRIVATE_KEY to a funded Base Sepolia wallet to complete the purchase." +
            "\nTestnet USDC faucet: https://faucet.circle.com\n" +
            "\nOr skip payment entirely and use the library:  npx x402-skill-md generate openapi.json\n",
        );
        return;
      }
    
      const chain = process.env.NETWORK === "base" ? base : baseSepolia;
      const account = privateKeyToAccount(PRIVATE_KEY);
      const wallet = createWalletClient({ account, chain, transport: http() }).extend(publicActions);
      // eslint-disable-next-line @typescript-eslint/no-explicit-any
      const pay = wrapFetchWithPayment(fetch, wallet as any);
    
      console.log(`\n② Generating from ${account.address} on ${chain.name} — $0.01`);
      const generated = await pay(`${BASE_URL}/generate`, {
        method: "POST",
        headers: { "content-type": "application/json" },
        body: JSON.stringify({ openapi: loadOpenapi() }),
      });
      const result = await generated.json();
      if (!generated.ok) throw new Error(`generate failed: ${generated.status} ${JSON.stringify(result)}`);
    
      console.log(
        `\n③ 200 — ${result.service.name}: ${result.service.paid} paid of ${result.service.endpoints} endpoints`,
      );
      console.log(
        `   generated file scores ${result.validation.score}/100, valid: ${result.validation.valid}`,
      );
      for (const w of result.warnings ?? []) console.log(`   warning: ${w}`);
      console.log("\n--- skill.md ---");
      console.log(result.skillMd);
      console.log("--- end ---");
    
      const r1 = generated.headers.get("x-payment-response");
      if (r1) console.log("\n   X-PAYMENT-RESPONSE:", decodeXPaymentResponse(r1));
    
      // --- validate something deliberately broken, to see the fixes -----------
      const broken = "# broken-service\n\nToo short.\n\n## `GET /thing` — 1 cent\n";
      console.log(`\n④ Validating a deliberately broken document — $0.002`);
      const checked = await pay(`${BASE_URL}/validate`, {
        method: "POST",
        headers: { "content-type": "text/markdown" },
        body: broken,
      });
      const report = await checked.json();
      console.log(
        `   valid: ${report.valid}  score: ${report.score}/100  ` +
          `(${report.summary.errors} errors, ${report.summary.warnings} warnings)`,
      );
      for (const f of report.findings) {
        console.log(`   ${f.severity === "error" ? "✗" : f.severity === "warning" ? "!" : "·"} ${f.rule} ${f.title}`);
        console.log(`       ${f.detail}`);
        console.log(`       fix: ${f.fix}`);
      }
      console.log(
        "\n   Note this was a 200, not a 4xx. A non-conformant document is a successful report —" +
          "\n   you bought the findings, and the findings are the artifact.",
      );
    }
    
    main().catch((err) => {
      console.error("\nfailed:", err instanceof Error ? err.message : err);
      process.exit(1);
    });
    
    /* ---------------------------------------------------------------------------
     * Paying on the SOLANA rail instead
     * ---------------------------------------------------------------------------
     *   import {
     *     prepareSolanaCheckout,
     *     encodeX402Payment,
     *   } from "@three-ws/x402-payment-modal/server";
     *
     *   const url       = `${BASE_URL}/validate`;
     *   const challenge = await (await fetch(url, { method: "POST", body: "# x" })).json();
     *   const accept    = challenge.accepts.find((a: any) => a.network.startsWith("solana"));
     *
     *   const { tx_base64 } = await prepareSolanaCheckout({ accept, buyer: myPubkey });
     *   const signed        = await wallet.signTransaction(tx_base64);   // Phantom, Solflare, a keypair
     *   const { x_payment } = encodeX402Payment({ accept, signedTxBase64: signed, resourceUrl: url });
     *
     *   await fetch(url, {
     *     method: "POST",
     *     headers: { "content-type": "text/markdown", "X-PAYMENT": x_payment },
     *     body: mySkillMd,
     *   });
     *
     * Those helpers only build and encode the payment client-side; verification and
     * settlement happen server-side through the rail's facilitator.
     * `accept.extra.feePayer` sponsors the SOL network fee, so the buyer spends USDC only.
     *
     * ---------------------------------------------------------------------------
     * No wallet? The same logic ships as a library and a CLI.
     * ---------------------------------------------------------------------------
     *   npm i x402-skill-md
     *
     *   import { openapiToSkillMd, validateSkillMd } from "x402-skill-md";
     *   const { skillMd } = openapiToSkillMd(JSON.parse(readFileSync("openapi.json", "utf8")));
     *   console.log(validateSkillMd(skillMd).valid);   // true
     *
     *   npx x402-skill-md generate openapi.json -o skill.md
     *   npx x402-skill-md validate skill.md --strict
     */
    
  • examples/curl.md7.1 KBaf4ccfbe22a7upstream ↗
    # The raw 402 → pay → 200 walkthrough
    
    No SDK — just HTTP, so you can see exactly what the protocol does.
    
    ```bash
    npm install && npm run dev     # http://localhost:4029
    ```
    
    ## 0. Discover the service, and the rules, for free
    
    ```bash
    curl -s localhost:4029/ | jq '{name, skillMdVersion, endpoints}'
    curl -s localhost:4029/rules | jq '{skillMdVersion, count}'
    curl -s localhost:4029/spec | head -5
    ```
    
    ```
    { "skillMdVersion": "1.0", "count": 23 }
    ```
    
    The whole rule catalogue and the specification are free. You should be able to see what you
    are being judged against before paying to be judged — and in most cases self-check first and
    never need the paid route at all.
    
    ```bash
    curl -s localhost:4029/rules | jq '.rules[] | select(.severity=="error") | {id, title}'
    ```
    
    ## 1. Validate without paying → 402 with **both** rails
    
    ```bash
    curl -i -s -X POST localhost:4029/validate \
      -H 'content-type: text/markdown' \
      --data-binary @skill.md
    ```
    
    ```http
    HTTP/1.1 402 Payment Required
    Content-Type: application/json
    ```
    
    ```jsonc
    {
      "x402Version": 1,
      "error": "X-PAYMENT header is required",
      "accepts": [
        {
          "scheme": "exact",
          "network": "base-sepolia",
          "maxAmountRequired": "2000",              // 2000 base units = $0.002 USDC (6 dp)
          "resource": "http://localhost:4029/validate",
          "description": "Validate a posted skill.md and return a report with per-finding fixes",
          "mimeType": "application/json",
          "payTo": "0x40252CFDF8B20Ed757D61ff157719F33Ec332402",
          "maxTimeoutSeconds": 60,
          "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
          "extra": { "name": "USDC", "version": "2" }
        },
        {
          "scheme": "exact",
          "network": "solana",
          "maxAmountRequired": "2000",
          "resource": "http://localhost:4029/validate",
          "description": "Validate a posted skill.md and return a report with per-finding fixes",
          "mimeType": "application/json",
          "payTo": "WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW",
          "maxTimeoutSeconds": 60,
          "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
          "extra": { "name": "USD Coin", "decimals": 6, "feePayer": "2wKupLR9q6wXYppw8Gr2NvWxKBUqm4PPJKkQfoxHDBg4" }
        }
      ]
    }
    ```
    
    Note the two facilitators behind those rails: `x402.org` settles the Base Sepolia entry,
    PayAI settles the Solana one. They are not interchangeable.
    
    ## 2. Build the payment
    
    `X-PAYMENT` is base64 of a JSON payload proving you authorised exactly
    `maxAmountRequired` to `payTo` on the rail you chose.
    
    **EVM rail** — an EIP-3009 `transferWithAuthorization` signature:
    
    ```jsonc
    {
      "x402Version": 1,
      "scheme": "exact",
      "network": "base-sepolia",
      "payload": {
        "signature": "0x…",
        "authorization": {
          "from": "0xYourWallet",
          "to": "0x40252CFDF8B20Ed757D61ff157719F33Ec332402",
          "value": "2000",
          "validAfter": "0",
          "validBefore": "1791234567",
          "nonce": "0x…"
        }
      }
    }
    ```
    
    **Solana rail** — a signed SPL `transferChecked` transaction, base64, in the same envelope.
    `@three-ws/x402-payment-modal/server` builds and encodes it client-side
    (`prepareSolanaCheckout` → sign → `encodeX402Payment`); verification and settlement still
    happen server-side through the facilitator.
    
    ```bash
    X_PAYMENT=$(printf '%s' "$PAYLOAD_JSON" | base64 -w0)
    ```
    
    ## 3. Retry with the header → 200 + the report
    
    ```bash
    curl -i -s -X POST localhost:4029/validate \
      -H 'content-type: text/markdown' \
      -H "X-PAYMENT: $X_PAYMENT" \
      --data-binary @broken.md
    ```
    
    Where `broken.md` is:
    
    ```markdown
    # broken-service
    
    Too short.
    
    ## `GET /thing` — 1 cent
    ```
    
    ```http
    HTTP/1.1 200 OK
    X-PAYMENT-RESPONSE: eyJzdWNjZXNzIjp0cnVlLCJyYWlsIjoiZXZtIiwi…
    ```
    
    ```json
    {
      "skillMdVersion": "1.0",
      "valid": false,
      "score": 0,
      "summary": { "errors": 8, "warnings": 3, "infos": 2 },
      "service": {
        "name": "broken-service",
        "baseUrl": null,
        "contact": null,
        "endpoints": [],
        "rails": { "evm": false, "solana": false },
        "dualRail": false
      },
      "findings": [
        {
          "rule": "SM002",
          "severity": "error",
          "title": "Missing summary paragraph",
          "detail": "The text under the title is only 10 characters — too short to tell an agent what this service does.",
          "fix": "Put one prose paragraph directly under the H1 saying what the service does and what an agent gets from it.",
          "line": 1
        },
        {
          "rule": "SM006",
          "severity": "error",
          "title": "No Solana payment rail documented",
          "detail": "There is no Payment section, so no Solana rail is documented.",
          "fix": "Name a Solana network (`solana` or `solana-devnet`) and its `payTo` address in the Payment section. Every conformant skill.md is dual-rail: an agent's wallet lives on one chain, and it must be able to tell from this file alone whether it can pay."
        },
        {
          "rule": "SM008",
          "severity": "error",
          "title": "Endpoint heading does not match the grammar",
          "detail": "`## `GET /thing` — 1 cent` names an HTTP method but does not match the endpoint grammar.",
          "fix": "Use ``## `METHOD /path` — $0.001`` : backticked `METHOD /path`, a dash, then `$amount` or `free`.",
          "line": 5
        }
      ],
      "checkedAt": "2026-08-07T12:00:00.000Z"
    }
    ```
    
    **That 200 is the point.** A non-conformant document is a *successful report*, not an error —
    you bought the findings, and the findings are the artifact. Only an unparseable request
    (empty body, wrong content type) is a 4xx.
    
    That exact output, minus the payment, is what you get for free from the CLI:
    
    ```bash
    npx x402-skill-md validate broken.md --json
    ```
    
    ## 4. Generate — $0.01
    
    ```bash
    curl -s -X POST localhost:4029/generate \
      -H 'content-type: application/json' \
      -H "X-PAYMENT: $X_PAYMENT_10000" \
      -d '{"openapi": '"$(cat openapi.json)"', "options": {"baseUrl": "https://api.example.com"}}' \
      | jq -r .skillMd > skill.md
    ```
    
    The response also carries a validation report for what it just generated:
    
    ```bash
    … | jq '{service, warnings, validation: .validation | {valid, score}}'
    ```
    
    ```json
    {
      "service": { "name": "x402-podcasts", "baseUrl": "http://localhost:4028", "endpoints": 6, "paid": 2 },
      "warnings": [],
      "validation": { "valid": true, "score": 100 }
    }
    ```
    
    `warnings` is where the generator admits what it had to guess — a missing summary, a price it
    could not determine. It never invents a price silently.
    
    ## 5. Prices are separate payments
    
    `$0.01` for generate, `$0.002` for validate. `X-PAYMENT` is not a session: each paid call
    needs its own signed payment for its own amount. Get the challenge from an unpaid call to the
    route you want.
    
    ## Errors you may hit
    
    | what you sent | you get |
    |---|---|
    | no header on a paid route | 402, `error: "X-PAYMENT header is required"` |
    | garbage header | 402, `error: "invalid X-PAYMENT header: …"` |
    | a rail we don't take | 402, `error: "unsupported rail: …"` |
    | short-paid or expired authorisation | 402, `error:` the facilitator's `invalidReason` |
    | an empty body | 400, `BAD_REQUEST` |
    | JSON with no `paths` to `/generate` | 400, `NOT_AN_OPENAPI_DOCUMENT` |
    | a *non-conformant* skill.md | **200**, `valid: false` — that is a report, not an error |
    
  • examples/mcp-tool.md5.4 KB315c1335553bupstream ↗
    # Exposing x402-skill-md as an MCP tool
    
    [Model Context Protocol](https://modelcontextprotocol.io) servers give Claude tools it can
    call mid-conversation. This one is unusually well suited to it: an agent that has just
    written or discovered an OpenAPI document cannot `npm install` inside its own reasoning loop,
    but it can make an HTTP request and pay a cent.
    
    ## The server
    
    ```bash
    npm install @modelcontextprotocol/sdk x402-fetch viem zod
    ```
    
    `mcp-skill-md.ts`:
    
    ```ts
    import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
    import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
    import { createWalletClient, http, publicActions } from "viem";
    import { privateKeyToAccount } from "viem/accounts";
    import { baseSepolia } from "viem/chains";
    import { wrapFetchWithPayment } from "x402-fetch";
    import { z } from "zod";
    
    const BASE_URL = process.env.X402_SKILL_MD_URL ?? "http://localhost:4029";
    
    // One wallet, reused for every purchase. Its balance IS the spending cap.
    const account = privateKeyToAccount(process.env.PRIVATE_KEY as `0x${string}`);
    const wallet = createWalletClient({ account, chain: baseSepolia, transport: http() })
      .extend(publicActions);
    const pay = wrapFetchWithPayment(fetch, wallet as never);
    
    const server = new McpServer({ name: "x402-skill-md", version: "0.1.0" });
    
    // Free: the rule catalogue. Expose it so the model can self-check before spending.
    server.tool(
      "skill_md_rules",
      "Get the SKILL-MD rule catalogue — every rule id, severity, title and fix. FREE. " +
        "Call this before writing a skill.md by hand; most problems can be avoided rather than " +
        "diagnosed.",
      {},
      async () => {
        const res = await fetch(`${BASE_URL}/rules`);
        return { content: [{ type: "text", text: JSON.stringify(await res.json(), null, 2) }] };
      },
    );
    
    server.tool(
      "generate_skill_md",
      "Turn an OpenAPI 3.x document into a conformant skill.md — the agent-facing contract file " +
        "an x402 service ships at its root. Returns the file plus a validation report for it. " +
        "$0.01 USDC, paid automatically. Declare prices as `x-x402: { price: \"$0.001\" }` on each " +
        "paid operation; anything the generator has to guess comes back in `warnings`.",
      {
        openapi: z.record(z.unknown()).describe("The OpenAPI 3.x document, parsed"),
        baseUrl: z.string().optional().describe("Overrides servers[0].url"),
        contact: z.string().optional().describe("Overrides info.contact.email"),
        defaultPrice: z.string().optional().describe('Price for paid ops with no amount, e.g. "$0.001"'),
      },
      async ({ openapi, ...options }) => {
        const res = await pay(`${BASE_URL}/generate`, {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({ openapi, options }),
        });
        return { content: [{ type: "text", text: JSON.stringify(await res.json(), null, 2) }] };
      },
    );
    
    server.tool(
      "validate_skill_md",
      "Check a skill.md against all 23 SKILL-MD rules. Returns a report naming every finding, " +
        "where it is, what is wrong, and the exact fix. $0.002 USDC. A non-conformant document " +
        "is a successful report with `valid: false` — not an error.",
      { skillMd: z.string().describe("The full contents of the skill.md file") },
      async ({ skillMd }) => {
        const res = await pay(`${BASE_URL}/validate`, {
          method: "POST",
          headers: { "content-type": "application/json" },
          body: JSON.stringify({ skillMd }),
        });
        return { content: [{ type: "text", text: JSON.stringify(await res.json(), null, 2) }] };
      },
    );
    
    await server.connect(new StdioServerTransport());
    ```
    
    ## Wiring it into Claude Desktop / Claude Code
    
    `claude_desktop_config.json` (or `.mcp.json` for Claude Code):
    
    ```json
    {
      "mcpServers": {
        "x402-skill-md": {
          "command": "npx",
          "args": ["tsx", "/absolute/path/to/mcp-skill-md.ts"],
          "env": {
            "PRIVATE_KEY": "0xYourFundedTestnetKey",
            "X402_SKILL_MD_URL": "https://your-deployment.example.com"
          }
        }
      }
    }
    ```
    
    Then:
    
    > **You:** I just shipped an API. Here's the OpenAPI spec — write me a skill.md for it.
    >
    > **Claude:** *(calls `skill_md_rules` — free — to see what conformance requires, then
    > `generate_skill_md` — $0.01 — and reports: 4 paid endpoints, scores 100/100, one warning
    > that `/reports` had no 200 example so the section has no response block)*
    >
    > **You:** Fix that and check it again.
    >
    > **Claude:** *(adds the example, calls `validate_skill_md` — $0.002 — confirms 100/100)*
    
    ## Notes
    
    - **Call `skill_md_rules` first.** It is free, and it turns validation from a diagnosis into
      a checklist. The paid `validate` route is worth it *after* a draft exists, not before.
    - **Budget the wallet, not the tool.** The MCP process holds the key; its balance is the
      agent's spending cap.
    - **For humans, use the CLI instead.** `npx x402-skill-md validate skill.md` does the same
      work for free. The paid service exists because an agent cannot `npm install` mid-thought.
    - **Teach the model about the 200.** `valid: false` is a successful report; it should read
      the `findings` and fix the file, not retry the call.
    - **Solana rail.** Swap `wrapFetchWithPayment` for a Solana x402 client if the agent's wallet
      lives on Solana; the service accepts either and the tool code is unchanged.
    - **Discovery.** An agent that can read [`skill.md`](../skill.md) or `GET /.well-known/x402`
      can write this wrapper itself — which is, recursively, the entire point of the format.
    
  • LICENSE11 KBd64569567334upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • llms-full.txt9.8 KBa2feb4777563upstream ↗
    # x402-skill-md
    
    > The skill.md toolkit: generate and validate agent-discoverable skill files (the agentres.dev pattern) from OpenAPI specs. Defines the SKILL-MD format used across the x402 Suite.
    
    <h1 align="center">x402-skill-md</h1>
    
    <p align="center">
      <b>The <code>skill.md</code> toolkit: the format, a generator, a validator.</b><br>
      A library, a CLI, and an x402-paid service. Defines the agent-facing contract file the whole suite ships.
    </p>
    
    <p align="center">
      <a href="LICENSE"><img alt="License: Apache 2.0" src="https://img.shields.io/badge/license-Apache--2.0-blue.svg"></a>
      <a href="https://x402.org"><img alt="x402" src="https://img.shields.io/badge/protocol-x402-0052ff.svg"></a>
      <img alt="rails" src="https://img.shields.io/badge/USDC-Base%20%2B%20Solana-2775ca.svg">
      <img alt="format" src="https://img.shields.io/badge/SKILL--MD-1.0-6b46c1.svg">
      <a href="https://nirholas.github.io/x402-skill-md/"><img alt="docs" src="https://img.shields.io/badge/docs-Pages-24292f.svg"></a>
    </p>
    
    ---
    
    ## What a `skill.md` is
    
    The file an AI agent reads to learn how to **use and pay for** an HTTP service: in one
    fetch, with no SDK and no human. It is Markdown, so a model can read it directly; it has a
    defined section grammar, so a parser can check it.
    
    OpenAPI already describes shapes. It does not describe **what a call costs, in what asset, on
    which chains, to which address**: and for an agent with a wallet and no account, that is the
    load-bearing information.
    
    **[Read the specification →](docs/spec.md)**
    
    ## Use it three ways
    
    ### As a CLI
    
    ```bash
    npx x402-skill-md generate openapi.json -o skill.md
    npx x402-skill-md validate skill.md
    npx x402-skill-md rules
    ```
    
    ```
    PASS  score 100/100  (0 errors, 0 warnings, 0 infos)
    service: x402-domains  endpoints: 2  rails: evm + solana
    
    No findings. This document is conformant.
    ```
    
    `validate` exits 1 on errors, so it drops straight into CI:
    
    ```yaml
    - run: npx x402-skill-md validate skill.md --strict
    ```
    
    ### As a library
    
    ```bash
    npm i x402-skill-md
    ```
    
    ```ts
    import { openapiToSkillMd, validateSkillMd, RULES } from "x402-skill-md";
    
    const { skillMd, warnings } = openapiToSkillMd(
      JSON.parse(await readFile("openapi.json", "utf8")),
      { baseUrl: "https://api.example.com", contact: "[email protected]" },
    );
    
    const report = validateSkillMd(skillMd);
    report.valid;                 // true
    report.service.dualRail;      // true
    report.findings;              // []: each finding carries rule, detail, fix, line
    ```
    
    Zero runtime dependencies in the library path: the parser, validator and generator are plain
    TypeScript.
    
    ### As a paid service
    
    | Route | Price | What lands in the 200 body |
    |---|---|---|
    | `POST /generate` | **$0.01** | The generated `skill.md`, plus a validation report for it: one call gives you a file you can commit |
    | `POST /validate` | **$0.002** | The full report: every finding with its rule id, location, what is wrong *in this document*, and the exact fix |
    | `GET /rules` | free | The whole rule catalogue as JSON |
    | `GET /spec` | free | The format specification |
    | `GET /` · `/health` · `/skill.md` · `/.well-known/x402` · `/openapi.json` | free | Discovery |
    
    `GET /rules` is free on purpose: you should be able to see what you are being judged against
    before paying to be judged.
    
    ## Why x402 for this
    
    Validation is the definition of a spiky, per-artifact need: you check a `skill.md` when you
    write one, then not again for a month. A subscription is absurd for that, and so is an API
    key. But the library is also just free on npm, so the honest answer is: **the paid service
    exists for agents.** An agent that generated an OpenAPI document and wants a `skill.md` for it
    cannot `npm install` inside its own reasoning loop; it can make an HTTP request and pay a
    cent. Humans should use the CLI.
    
    ## The format in one paragraph
    
    Markdown with a defined section grammar: one H1, a summary paragraph, a service card with a
    base URL, a `## Payment` section documenting **both an EVM and a Solana rail**, one
    `` ## `METHOD /path`: $price `` section per endpoint with a parameter table and a real JSON
    response example, and a `## Errors` section that mentions the 402.
    
    ### Dual rail is mandatory, not optional
    
    A conformant `skill.md` documents an EVM rail **and** a Solana rail (rules SM005/SM006, both
    `error`). The reasoning:
    
    - An agent's wallet lives on exactly **one** chain. A single-rail service is invisible to
      every agent on the other one, and it cannot learn that cheaply.
    - The 402 challenge already carries an `accepts` **array**. Two entries cost one extra
      object; the client picks. There is no protocol reason to publish one.
    - Discovery indexers filter on rails. A single-rail listing halves your reachable market for
      no gain.
    
    The spec also requires the **facilitator to be declared per rail**, because facilitators are
    chain-specific: the reference `x402.org` facilitator settles Base Sepolia only and will not
    settle a Solana payment. A document that names one facilitator for both rails is telling
    clients something false.
    
    ## Rules
    
    23 rules across three severities. `valid` is `true` when there are **zero errors**,
    independent of score; the score (100 − 12/error − 4/warning − 1/info) is a quality signal.
    
    | | count | meaning |
    |---|---|---|
    | `error` | 10 | not conformant: an agent may fail to use the service |
    | `warning` | 10 | conformant but lossy: an agent has to guess |
    | `info` | 3 | a suggestion |
    
    Every finding carries a fix, not just a verdict:
    
    ```json
    {
      "rule": "SM006",
      "severity": "error",
      "title": "No Solana payment rail documented",
      "detail": "There is no Payment section, so no Solana rail is documented.",
      "fix": "Name a Solana network (`solana` or `solana-devnet`) and its `payTo` address in the Payment section. Every conformant skill.md is dual-rail: an agent's wallet lives on one chain, and it must be able to tell from this file alone whether it can pay."
    }
    ```
    
    Full catalogue: `npx x402-skill-md rules`, `GET /rules`, or [§9 of the spec](docs/spec.md).
    
    ## Generating from OpenAPI
    
    Pricing is read from, in order of preference:
    
    1. `x-x402: { price: "$0.001" }` on the operation: the exact, declarative way
    2. the `maxAmountRequired` in a 402 response example, converted from USDC base units
    3. `options.defaultPrice`
    4. otherwise, an operation with no 402 response is treated as **free**
    
    Anything guessed is reported in `warnings`. The generator never invents a price silently: a wrong price in a published contract is worse than a missing one.
    
    ```ts
    openapiToSkillMd(doc, {
      baseUrl: "https://api.example.com",   // overrides servers[0].url
      contact: "[email protected]",           // overrides info.contact.email
      defaultPrice: "$0.001",
      frontmatter: true,                    // emit the YAML block
      rails: [ /* defaults to the suite's dual-rail pair */ ],
    });
    ```
    
    ## Quickstart (the service)
    
    ```bash
    git clone https://github.com/nirholas/x402-skill-md.git
    cd x402-skill-md
    npm install
    cp .env.example .env      # already filled with working defaults
    npm run dev               # http://localhost:4029
    ```
    
    ```bash
    curl -i -s -X POST localhost:4029/validate -H 'content-type: text/markdown' -d '# x'
    # HTTP/1.1 402 Payment Required
    # { "x402Version": 1, "accepts": [ {…base-sepolia…}, {…solana…} ] }
    
    PRIVATE_KEY=0xyourTestnetKey npm run client
    ```
    
    ## Dual-rail payment: Base **or** Solana
    
    Every 402 lists **two** payment requirements. The client picks whichever chain it can sign
    on; the server settles either through that rail's facilitator and returns the same artifact.
    
    | | EVM rail | Solana rail |
    |---|---|---|
    | network | `base-sepolia` (default) / `base` | `solana` (default) / `solana-devnet` |
    | asset | USDC `0x036CbD…dCF7e` (sepolia) | USDC mint `EPjFWdd5…TDt1v` |
    | payTo | `0x40252CFDF8B20Ed757D61ff157719F33Ec332402` | `WwwuGbqHrwF5RG89KhUbmRWEvjnRH9k5kVM5p7T3WwW` |
    | facilitator | `https://x402.org/facilitator` | `https://facilitator.payai.network` |
    
    Those are the suite's public receive addresses and the server runs with them out of the box.
    Set `PAY_TO_ADDRESS` / `SOLANA_PAY_TO_ADDRESS` to be paid yourself. The Solana network fee is
    sponsored by the facilitator's fee payer, so a payer needs USDC only: no SOL.
    
    ## Real backend / API keys
    
    **None.** Generation and validation are pure functions of what you post: no upstream API, no
    key, no fixture mode, and nothing you send is stored. The same input always produces the same
    output, which is also why the library and the service cannot disagree.
    
    ## For AI agents
    
    - **[`skill.md`](skill.md)**: this service's own contract file, and a worked example of the
      format. It scores 100/100 against its own validator, which is the least it could do.
    - **`GET /.well-known/x402`**: machine-readable manifest of both paid resources with input
      and output schemas and both rails.
    - **`GET /rules`**: the catalogue, free, so an agent can self-check before paying.
    - **MCP**: [`examples/mcp-tool.md`](examples/mcp-tool.md) exposes `generate_skill_md` and
      `validate_skill_md` as Model Context Protocol tools.
    - **Client**: [`examples/agent-client.ts`](examples/agent-client.ts) runs the full flow:
      rules → 402 → generate → validate a deliberately broken file to show the fixes.
    
    ## Docs
    
    Full site: **<https://nirholas.github.io/x402-skill-md/>**
    
    - **[Specification](docs/spec.md)**: the format itself, normative
    - [Tutorial](docs/tutorial.md): CLI → library → paid service → CI
    - [API reference](docs/api.md): every field of every response
    - [For agents](docs/agents.md): discovery, payment, MCP, listing
    
    ## Support
    
    Questions, bugs, or a spec correction: **[email protected]** or open an issue.
    
    Part of the [x402 Suite](https://github.com/nirholas/x402-suite).
    
    ## License
    
    Apache-2.0: see [LICENSE](LICENSE).
    
    ## Star History
    
    [![Star History Chart](https://api.star-history.com/svg?repos=nirholas/x402-skill-md&type=Date)](https://www.star-history.com/#nirholas/x402-skill-md&Date)
    
  • llms.txt1.8 KBb7224019771bupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • openapi.json22 KBa62b2ca67337upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • package-lock.json287 KBcfe7223ae312upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • package.json1.5 KB89c4b5003f44upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • public/.well-known/x4026.9 KBce4e39bb5bb3upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • public/favicon.svg423 B61d08fcdcc6fupstream ↗
    <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" role="img" aria-label="x402">
      <defs>
        <linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
          <stop offset="0" stop-color="#0052ff"/>
          <stop offset="1" stop-color="#14a37f"/>
        </linearGradient>
      </defs>
      <rect width="100" height="100" rx="22" fill="url(#g)"/>
      <path d="M56 16 L74 16 L74 84 L56 84 L56 68 L20 68 L20 52 Z" fill="#fff"/>
    </svg>
    
  • public/index.html8.1 KBbef5a11ec6d2upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • public/og.png35 KB40e3e4381e69upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • public/openapi.json22 KBa62b2ca67337upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • SECURITY.md676 B0d07c8d8a91bupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • src/cli.ts4.7 KB64ad519559a4upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • src/generate.ts16 KB1f0ea5223cd2upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • src/index.ts1.2 KB90e19e2754d2upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • src/parse.ts6.0 KB6ee88bc05f7eupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • src/payments.ts16 KB16aa23085fefupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • src/schemas.ts8.4 KB87debac34d89upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • src/server.ts6.5 KB9126f323144aupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • src/spec.ts8.5 KB6a0fe1d9cd27upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • src/validate.ts9.9 KBc00bda47539fupstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

  • tsconfig.json382 B66c707edd738upstream ↗

    Contents are not inlined in the catalog. Open the upstream file.

Plain-text copy, no login: /skill-bundles/nirholas-x402-skill-md.md

// REPO STATS

8 stars