checkout402_
Guides

Sell a file#

A PDF, a dataset, a clip, a zip. Someone pays; they get the bytes.

A file is not a fifth fulfilment mode. It is relay — pointed at the file instead of at an API — plus a content declaration that says what kind of thing is behind the wall. That gives it a browser face at /x/{id} and a download disposition, and costs the object model nothing.

The file stays on your server. We never hold a copy.


Make one#

Create a checkoutA file → paste the URL, name it, set a price.

c402 checkouts create \
  --title "Q3 report" --price 5.00 \
  --origin-url https://yourserver.com/reports/q3.pdf \
  --description "Forty pages of quarterly numbers." \
  --content '{"media": "file", "filename": "q3-report.pdf"}'

Sell https://yourserver.com/reports/q3.pdf for $5 as a file called q3-report.pdf.

curl -X POST https://api.checkout402.com/v1/checkouts \
  -H "Authorization: Bearer c402_live_…" \
  -H "content-type: application/json" \
  -d '{"title": "Q3 report",
       "price_usd": "5.00",
       "origin_url": "https://yourserver.com/reports/q3.pdf",
       "description": "Forty pages of quarterly numbers.",
       "content": {"media": "file", "filename": "q3-report.pdf"}}'

The id comes back cnt_, and the URL to share is /x/{id}.

A file is a listing, not an invoice

Checkouts are single-shot by default, which is right for "pay me for this job" and wrong for "here is a thing I sell" — the first sale would consume it and the second buyer would get a 410 from a store that looked open. So a file defaults to reusable: true and carries no deadline. Send reusable: false if you really do mean one buyer.


What the buyer gets#

A person opens /x/{id} your title, description and price, then a pay button
They pay the bytes, with Content-Disposition: attachment and the name you set
An agent asks for JSON a 402 with the price, then the file base64-enveloped as payload_base64
An <img>/<video>/<audio> element /x/{id}/media, which honours Range so a browser can seek

Unpaid callers to /x/{id}/media get a 402 with a Link: …; rel="payment" header rather than the pay page — the caller is usually a tag, and an HTML document renders as a broken image.

Nothing is cacheable: private, no-store on every paid response, and X-Content-Type-Options: nosniff so a gated image cannot be sniffed into something executable.


Naming the download#

content.filename is what the file is saved as. It is sanitised at declaration — control characters, quotes and path separators are stripped — and sent RFC 5987-encoded, so a non-ASCII name survives intact and a name can never inject a header.

Leave it out and the browser falls back to whatever it derives from the URL.


The 5 MB limit, and what to do above it#

The relay buffers the response, so bodies over 5 MB are refused. That covers a report, a photo, a short clip, a spreadsheet. It does not cover a 200 MB video or a large dataset.

For anything bigger, point origin_url at your own endpoint that returns a signed URL — S3, R2, GCS — instead of at the file. The buyer pays, we call your endpoint, and they get a short-lived link. The bytes never pass through us, so there is no size limit at all, and you keep control of expiry.

c402 checkouts create --title "Full dataset" --price 40.00 \
  --origin-url https://yourserver.com/sign/dataset \
  --content '{"media": "file"}'

Keep the signed URL's lifetime short: it is a bearer link for as long as it lives.


Behind your own credential#

The file can sit behind a key your server checks. Store it once and the buyer never sees it:

c402 checkouts create --title "Q3 report" --price 5.00 \
  --origin-url https://yourserver.com/private/q3.pdf \
  --origin-auth-type bearer --origin-auth-value "$YOUR_KEY" \
  --content '{"media": "file", "filename": "q3-report.pdf"}'

The credential is encrypted at rest, injected server-to-server, and never appears in anything the buyer can see.


Say what it is#

A gated piece needs something free to judge it by: a description, a content.teaser, or a content.preview_url (a cover image). Create is refused without one — a price with nothing behind it does not sell, and the refusal is cheaper than the silence.


What next#