---
title: "The Free Half: What a Paid Endpoint Answers Before It Charges — NanoParse Blog"
url: https://nanoparse.app/blog/2026-10-04-free-half-discovery
description: "An agent that meets https://nanoparse.app/fetch is one POST away from a charge. After ten free parses, that POST answers 402 and the caller either signs a payme…"
---

An agent that meets `https://nanoparse.app/fetch` is one POST away from a charge. After ten free parses, that POST answers 402 and the caller either signs a payment or walks away. Everything needed to make that decision is available before the point of payment, from unauthenticated GETs that cost nothing and need no key: the price, the asset, the network, the receiving address, the free-tier size, the failure contract, and the product's own list of things it will not do.

That property is worth stating because it is not universal. A paid endpoint can put its price behind a login, describe itself only on a marketing page, or answer a machine with a human HTML page. This one publishes a machine-readable surface, and the whole surface can be fetched and read before the first cent. Here it is, captured 2026-10-04, with the byte counts.

```
probe status bytes
GET /.well-known/x402 200 392
GET /openapi.json 200 3983
GET /llms.txt 200 9446
GET /robots.txt 200 430
GET /health 200 102
GET /sitemap.xml 200 864
GET /.well-known/security.txt 200 186
GET /ops-canary 200 38
GET /status 400 26 (no jobId)
GET /status/account 400 91 (no address)
GET /.well-known/agent-card.json 404 14
```

Eleven reads, no key, no wallet, no account. What follows is what they answer.

## The 402 is the last gate, not the first

An unauthenticated call — a GET, a body-less POST, or a POST with no `Payment-Signature` header — answers HTTP 402. The response is the quote: what it costs, what token, which chain, and where the money goes. Captured live today:

```
$ curl -sD - https://nanoparse.app/fetch
HTTP/2 402
pay-to: ethereum:base:0x57a1d06873db575baeae1f6af30862a75c7bd570
payment-required: <base64 x402 v2 PaymentRequired>

{"error":"payment_required",
"payment_required":{
"x402Version":2,
"accepts":[{"scheme":"exact","network":"eip155:8453",
"asset":"0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"amount":"5000",
"payTo":"0x57a1d06873db575baeae1f6af30862a75c7bd570",
"maxTimeoutSeconds":60}]},
"catalog":[{"id":"nanoparse_fetch","price_usd":"0.005","price_unit":"per parse",
"free_tier":"10 free parses per network (subnet)"}]}
```

Four fields carry the weight: `amount` and `asset` say how much of what, `network` says where, and `payTo` says to whom. The same terms travel base64-encoded in the `PAYMENT-REQUIRED` header, and the recipient is repeated as a CAIP-style `pay-to` header. CORS exposes `Content-Type, Payment-Signature, Authorization` on this route, so a browser-side agent can read the response too.

One detail is easy to miss: the 402 also carries an `extensions.bazaar` block with an input/output schema for the call. The payment-required response doubles as a machine description of how to call the endpoint — so even the refusal is informative.

## The price is stated in prose four times. Twice as a field.

Four documents print the per-parse price in a sentence: the `info.description` and the `x-guidance` extension inside `openapi.json`, the `instructions` string in `/.well-known/x402`, and the pricing rows in `llms.txt`. Prose is fine for a human and a poor contract for a client, because four sentences are four chances to drift.

Two of the machine surfaces state it as a value instead. `openapi.json` carries it as a field:

```
"x-payment-info": {
"price": { "mode": "fixed", "currency": "USD", "amount": "$0.005" },
"protocols": [{ "x402": {} }]
}
```

and the live 402 carries the same number in `catalog[].price_usd`, as `"$0.005"`.

A client that needs the price should read `x-payment-info.price` from the spec, or `catalog[].price_usd` from the 402, and leave the sentences alone. Those two fields are the same figure the site quotes: `$0.005` per parse, with no mode other than fixed — no tier to select and no format surcharge to discover later.

## Two encodings of the same number

The decimal in the spec is not the number that settles. `accepts[].amount` above is `"5000"` — base units, six decimals for USDC, so `5000 / 10^6` is the dollar figure. The deployed source derives both from one constant:

```
// pricing.ts — a price change touches only this file
export const PRICE_USDC_AMOUNT = String(Math.round(parseFloat(PRICE_USD) * 1e6));
export const X402_AMOUNT = PRICE_USDC_AMOUNT;
```

So the pattern for a client is: read the spec for the quote in dollars, read the 402 for the amount in base units, and build the payment from the base units. Format with `6` decimals for display; sign the integer.

## Verify the recipient before you sign

`/.well-known/x402` publishes the address the endpoint claims:

```
$ curl -s https://nanoparse.app/.well-known/x402 | python3 -c 'import json,sys; d=json.load(sys.stdin); print(d["resources"], d["ownershipProofs"])'
['https://nanoparse.app/fetch'] ['0x57a1d06873db575baeae1f6af30862a75c7bd570']
```

The `payTo` in the 402 above is the same address. Before signing, compare them:

```
terms = json.loads(base64.b64decode(resp.headers["PAYMENT-REQUIRED"]))
accept = terms["accepts"][0]
disc = json.loads(urlopen("https://nanoparse.app/.well-known/x402").read())

if accept["payTo"].lower() not in [p.lower() for p in disc["ownershipProofs"]]:
raise SystemExit("recipient not published by the endpoint")
```

What that check buys is consistency, not identity. The discovery file and the 402 are two documents from the same origin; matching them catches a recipient substituted between the two, not a deployment that never belonged to anyone. It is still the cheapest pre-signature check available, and it costs one more GET.

## The spec and the runtime disagree on one word

The 202 schema in `openapi.json` enumerates job status as `queued`, `processing`, `completed`, `error`. The endpoint that reports job status answers a different terminal word. From the deployed `/status` route:

```
if (rawData.startsWith("Error: ")) {
const { code, detail } = parseErrorBlob(rawData);
return c.json({
status: "failed",
error_code: code,
message: detail.message,
}, 200, { ...corsHeaders });
}
```

On the lookup queue the same word appears: `if (parsed?.error) return c.json({ status: "failed", ... })`. It is `failed` — never `error`.

A poller written against the spec's enum, stopping when `status == "error"`, never terminates on a failed render. The correct reading of the two documents together: `completed` is the only success, `failed` is terminal, and anything else means poll again. `queued` deserves a note as well — it appears in the POST response, and `/status` does not return it, so the enum folds two responses into one list.

Both statements are checkable without spending anything. `GET /status?jobId=9f2c1a3b-4b5d-4e6f-8a9b-0c1d2e3f4a5b` answers `{"status":"processing"}` for a job that was never issued, and a non-UUID answers `400 {"error":"invalid jobId"}`, so the poll path refuses noise and the terminal words are the runtime's.

## What the free reads rule out before you pay

`llms.txt` carries a section titled "When NOT to Use NanoParse", in the endpoint's own words:

- "Bulk scraping at scale — NanoParse is designed for per-parse agent workflows, not mass crawling."
- "Raw HTML or screenshots — NanoParse returns Markdown only."
- "Paywalled or auth-gated content — NanoParse respects paywalls and login walls. It will not bypass subscription barriers."
- "Persistent storage of results — Results are cached for up to 6 hours (R2) for performance, then discarded."

A product that publishes its own refusal list tells an agent where not to route work before any work is paid for. `robots.txt` does the same for crawlers: it allows `GPTBot`, `ChatGPT-User`, `Claude-Web`, `anthropic-ai`, `Google-Extended`, `CCBot`, `Applebot`, and `PerplexityBot` by name, and points its `Sitemap` at both `/sitemap.xml` and `/llms.txt`. `/.well-known/security.txt` gives a contact and a policy URL. `GET /health` returns `{"ok":true,…,"x402_supported":true}`, and `GET /ops-canary` returns `{"ok":true,"last_heartbeat_min_ago":2}` — a liveness heartbeat an agent can read before it builds a call.

## What the surface does not have

Two conventions are absent. `/.well-known/agent-card.json` answers `404 Not Found`, and so does `/.well-known/ai-plugin.json`. There is no agent card here; discovery is `/.well-known/x402` plus `openapi.json` plus `llms.txt`. An agent looking for a card should fall back to those three rather than assume the endpoint is undiscoverable.

A second gap sits closer to home: a prose example can lag the schema it describes. `llms.txt` shows a 402 body as a flat object with `price`, `token`, and `recipient` at the top level. The live body returned a `payment_required` envelope with an `accepts[]` array, and `openapi.json` defines exactly that shape. When an example and a schema disagree, the schema is what the endpoint was built against — read it for the shape, and treat the example as documentation of intent.

## A first-call checklist

The whole decision is a handful of reads. This is the order that spends nothing:

```
BASE=https://nanoparse.app

#1 is this an x402 resource, and which address does it publish?
curl -s $BASE/.well-known/x402

#2 the machine price and the request contract
curl -s $BASE/openapi.json | python3 -c 'import json,sys; p=json.load(sys.stdin)["paths"]["/fetch"]["post"]; print(p["x-payment-info"]["price"]); print(sorted(p["requestBody"]["content"]["application/json"]["schema"]["properties"]))'

#3 what it will not do, in its own words
curl -s $BASE/llms.txt | sed -n '/## When NOT to Use/,/^## /p'

#4 alive?
curl -s $BASE/health
```

After that, one POST maps the rest. It either succeeds inside the free ten, or it answers 402 with the quote above. Polling `GET /status?jobId=…` is free and unauthenticated, and it never settles a payment, so a client can watch a job to completion without spending anything.

## The paid half is one number

Everything above resolves to a single line: a rendered page costs `$0.005` USDC on Base, paid per parse, with the fifteen Litmus signals included rather than billed as a second call. Ten free parses per network come first, with no account and no key. Refused reads — SSRF-restricted hosts, blocked pages, paywalled pages, a render that comes back too thin — are rejected before settlement, so a failure is never charged.

That is the shape of the deal, and it is invertible: the free half is the contract — the price, the recipient, the failure words, the limits — and the paid half is the same number wherever it appears. An agent can read the first without spending and decide about the second.

Documentation and the quickstart live at [nanoparse.app](https://nanoparse.app/). The agent-facing description is [nanoparse.app/llms.txt](https://nanoparse.app/llms.txt), the machine spec is [nanoparse.app/openapi.json](https://nanoparse.app/openapi.json), and the built-in MCP endpoint is [nanoparse.app/mcp](https://nanoparse.app/mcp) — one URL, no install, no key.
