checkout402_
Guides

Sell stored data#

You hand us the bytes once. A buyer pays. We hand them over.

That is an inline checkout — the simplest thing this product sells. Nothing of yours is called at purchase time, so there is nothing to host, nothing to keep running, and nothing that can fail after the money moves.

buyer ──pays──▶ checkout402 ──▶ your stored payload, verbatim

What it is for#

  • An API key or a licence key you are issuing to whoever pays.
  • One row of a dataset, or one document.
  • A fixed JSON answer — a price list, a config, a reference table.
  • A download link you are happy for the buyer to hold.

The test is whether the answer is the same for every buyer, every time. If it has to change per buyer, per call, or per day, you want Sell an API instead: relay calls your endpoint after payment and forwards whatever it returned.


Make one#

Create a checkoutWhat kind of thing is it?A fixed payload → paste the JSON and a price.

c402 checkouts create \
  --title "Acme API key, 100 calls" \
  --price 5.00 \
  --payload '{"api_key": "ak_live_9f2c41ab7e05", "calls": 100}'

Sell this JSON for $5: {"api_key": "ak_live_…", "calls": 100}

{"title": "Acme API key, 100 calls", "price_usd": "5.00",
 "payload": {"api_key": "ak_live_9f2c41ab7e05", "calls": 100}}
curl -X POST https://api.checkout402.com/v1/checkouts \
  -H "Authorization: Bearer c402_live_…" \
  -H "content-type: application/json" \
  -d '{"title": "Acme API key, 100 calls",
       "price_usd": "5.00",
       "payload": {"api_key": "ak_live_9f2c41ab7e05", "calls": 100}}'

You get back one URL. That URL is the product.

You did not have to name the mode

Sending payload is the declaration. fulfillment: "inline" is accepted and still wins if you send it, but a seller thinks "I am selling this JSON for five dollars", not "this is an inline checkout".

--payload accepts @file.json. Real goods outgrow a shell argument, and a quoted JSON blob is where the typo lives:

c402 checkouts create --title "Q3 dataset row" --price 0.25 --payload @row.json

The schema buyers inspect writes itself#

An agent decides whether to buy from the unpaid 402, and the field it wants is output_schemawhat will I get back? For every other mode you have to write that yourself. For inline you do not, because the payload is already in our hands and asking you to describe bytes we are holding is absurd.

Send this payload:

{"api_key": "ak_live_9f2c41ab7e05", "calls": 100,
 "expires": "2027-01-01", "scopes": ["read"]}

…and the checkout comes back carrying this, with no offer-card work from you:

"output_schema": {
  "type": "object",
  "properties": {
    "api_key": {"type": "string"},
    "calls":   {"type": "integer"},
    "expires": {"type": "string"},
    "scopes":  {"type": "array", "items": {"type": "string"}}
  }
}

The inference is deliberately shallow. It reports field names and types, and stops there:

Case What is claimed
true / false boolean, never integer
null nothing — a null in an example says nothing about the type
An array the type of the first element only
Nesting past 6 levels nothing below that depth
Formats, enums, required fields never

Guessing "this string is an email" or "these three keys are required" from a single example produces confident claims you never made, and a wrong claim on an offer card is worse than a missing one.

Your own schema is never overwritten

Derivation fills a gap, it does not correct you. Send output_schema and yours is what buyers see — it is a claim you made, ours is a guess from one example.

example_output is not derived — see below.

We derive the shape, never the values

Offer-card fields are published before anyone pays — that is what they are for. So the schema is derived from your payload and the example is not: field names and types tell a buyer whether the thing fits without handing them the thing.

That distinction was briefly the other way round, and it gave the product away: a derived example_output on a checkout selling ak_live_… returned the key to anyone who read the 402. Nothing is derived from your values now.

If you want to show values — a sample dataset row, say — pass your own:

c402 checkouts create --title "Acme API key" --price 5.00 \
  --payload @key.json \
  --example-output '{"api_key": "ak_live_xxxxxxxxxxxx", "calls": 100}'

Redact it first. Whatever you pass is published unpaid, deliberately.


One payment, unlimited re-fetches#

relay and mcp cap repeat deliveries at three by default, because each one calls the seller again and spends their quota. inline is uncapped, and that is retry safety rather than generosity: replaying returns the same stored bytes and costs nobody anything.

Re-presenting the same signed X-PAYMENT redelivers the payload and never charges twice. A dropped connection after settlement is the worst failure in payments — the buyer has paid and has nothing — so asking again is always safe.


It cannot be sold as a package#

calls: N is refused at create, before anyone can pay for something that would not work:

a fixed payload cannot be sold as a package — replaying it returns the same
bytes, so buyers already get unlimited re-fetches and limiting them only breaks
retry after a dropped connection. Drop `calls`, or sell a call to your API with
fulfillment='relay'.

Two reasons, and both matter. A package means something only where a repeat delivery costs somebody something; capping identical bytes sells scarcity of a thing that is not scarce. And the cap would land on exactly the mechanism above, so a buyer whose connection dropped N times would have lost a purchase over data that never changed.

If you want to meter access, meter something that is actually metered — an endpoint of yours, behind relay.


One buyer, or many#

reusable is the other axis, and it is not about calls:

Behaviour Use for
reusable: false one payment, then the checkout goes paid a licence key issued to one buyer
reusable: true many payments, stays open a dataset row anyone may buy

The CLI, the dashboard and the REST API default to single-shot. A checkout created by an agent over MCP defaults to reusable, because a published product should not be spent by its first buyer.

If you are selling one unique secret, say reusable: false and mean it — a reusable checkout hands the same payload to everyone who pays.


A worked example#

A single dataset row, sold to anyone who wants it.

1. Create it.

curl -X POST https://api.checkout402.com/v1/checkouts \
  -H "Authorization: Bearer c402_live_…" \
  -H "content-type: application/json" \
  -d '{"title": "CHE-123.456.789 — filed financials 2025",
       "description": "One company, one year, as filed.",
       "price_usd": "0.25",
       "reusable": true,
       "source": "Zefix commercial register",
       "payload": {"uid": "CHE-123.456.789", "revenue_chf": 4200000,
                   "employees": 84, "filed": "2026-03-30"}}'
{
  "id": "chk_abc123",
  "url": "https://api.checkout402.com/c/chk_abc123",
  "status": "open",
  "fulfillment": "inline",
  "price_usd": "0.250000",
  "reusable": true,
  "output_schema": {"type": "object", "properties": {
    "uid": {"type": "string"}, "revenue_chf": {"type": "integer"},
    "employees": {"type": "integer"}, "filed": {"type": "string"}}},
  "example_output": {"uid": "CHE-123.456.789", "revenue_chf": 4200000,
                     "employees": 84, "filed": "2026-03-30"}
}

2. What a buyer sees before paying.

curl https://api.checkout402.com/c/chk_abc123
{
  "x402Version": 1,
  "accepts": [{
    "scheme": "exact",
    "network": "eip155:8453",
    "maxAmountRequired": "261250",
    "extra": {"providerAmount": "250000", "feeAmount": "11250"}
  }],
  "endpoint": {
    "id": "chk_abc123",
    "title": "CHE-123.456.789 — filed financials 2025",
    "fulfillment": "inline",
    "reusable": true,
    "source": "Zefix commercial register",
    "output_schema": {"type": "object", "properties": {  }},
    "example_output": {  }
  }
}

Amounts are atomic USDC, six decimals. $0.25 to you, $0.01125 in fee — 0.5% of the price plus the fixed $0.01 — so the buyer pays $0.26125 and you receive your full $0.25.

3. What they get for paying.

curl https://api.checkout402.com/c/chk_abc123 \
  -H "X-PAYMENT: <base64 signed authorization>"
{
  "ok": true,
  "checkout_id": "chk_abc123",
  "calls_remaining": null,
  "receipt": {"id": "pay_dVo7SDQsatf4HIFC",
              "url": "https://api.checkout402.com/r/pay_dVo7SDQsatf4HIFC",
              "tx_hash": "0xd84fa280…", "amount_usd": "0.250000"},
  "fulfillment": {"mode": "inline",
                  "payload": {"uid": "CHE-123.456.789", "revenue_chf": 4200000,
                              "employees": 84, "filed": "2026-03-30"}}
}

calls_remaining: null is the unlimited re-fetch, reported rather than left blank. The delivery is recorded delivered — always, for this mode, because nothing was called and nothing could have gone wrong.

Rehearse first

Every checkout has a test face at /c/{id}/test that settles with a synthetic transaction and no real money — forever, and it can never be armed. A checkout an agent creates for you starts unarmed, so even its bare URL rehearses; c402 checkouts go-live chk_abc123 arms it in place — same id, same URL.


What you cannot take back#

Whatever is in the payload, the buyer keeps. Forever.

There is no expiry on it, no revocation, and no way to reach into a buyer's storage. Deleting the checkout stops new sales and does nothing to the copies already handed out — which is the correct behaviour, because they paid for them.

So: do not put anything in a payload that you might later want to withdraw.

If the goods need to be per-buyer, revocable, rotatable, or countable, they are not stored data. Put them behind an endpoint you control and sell that with relay — then the thing you hand over is a call, which you can change, rate-limit or stop.


What next#