Starter services
Four paid HTTP services you can call today: LLM inference, URL to markdown, text embeddings and an encrypted drop box.
On this page
The starter services are four small paid HTTP APIs that the project runs, so there is something real to pay and a reference to copy. Each one answers an unpaid request with HTTP 402 and its price, and does the work once a voucherA message the agent signs for each call. It states the agent's running total, so the facilitator can check it against the deposit. arrives.
They are built on the service kit, which adds the payment, the health check and the listing in the catalog. Reading their code is the quickest way to learn how to sell an API.
The four services
| Service | Paid route | Price | One paid call buys | Provider | Port |
|---|---|---|---|---|---|
| LLM inference | POST /v1/generate | 200 zat | one output chunk of about 32 tokens | Operator | 4031 |
| URL to markdown | GET /v1/markdown?url= | 1,000 zat | one page | Provider 2 | 4032 |
| Text embeddings | POST /v1/embeddings | 100 zat | one call of up to about 1,000 tokens | Operator | 4033 |
| Encrypted drop box | POST /v1/drops, POST /v1/drops/:id/hold | 500 zat each | 10 blocks of storage, for an upload or a hold | Operator | 4034 |
The price is each service's own default, in zatThe smallest unit of ZEC. 1 ZEC is 100,000,000 zat. (1 ZEC is 100,000,000 zat), and the Docker demo charges these prices too. A service prices each of its routes itself, from 1 zat up to the facilitatorThe service that checks each voucher and settles it. One facilitator serves many providers.'s windowZat, which is 100,000 zat on the demo terms. The 402 always states the price you will pay.
Who runs them
A provider is one payment address with its own API key and prices. The services belong to two providers:
- The operator runs LLM inference, text embeddings, the drop box and the demo API (
GET /chunkat 500 zat). They share one address and one facilitator key, so one deposit pays for all of them. - Provider 2 is URL to markdown. It is an independent provider with its own address and its own API key, as an outside provider would be. An agent that pays it opens a session of its own and makes a second deposit.
An agent keeps one session per payment address. See Providers and API keys.
One deposit, many calls
An agent does not send a transaction for each call. It makes one shielded deposit for a windowThe size of one deposit, 100,000 zat on the demo terms. One window funds many calls. (100,000 zat on the demo terms), then signs a voucher for each call that raises its running total by the price. The deposit pays the provider's address when it is made, and unused balance is not refunded.
| Service | Price | Calls one 100,000 zat window pays for |
|---|---|---|
| LLM inference | 200 zat | 500 chunks |
| URL to markdown | 1,000 zat | 100 pages |
| Text embeddings | 100 zat | 1,000 calls |
| Encrypted drop box | 500 zat | 200 uploads or holds |
See Prepaid vouchers for how a window, a deposit and the credit line work together.
How a paid call works
- The agent sends its request. The service answers
402with the price, the provider's address and the terms in thePAYMENT-REQUIREDheader. - The agent's client makes a deposit if it has none, signs a voucher and retries the same request with it.
- The service asks the facilitator to verify the voucher, does the work, and asks it to settle.
- The agent gets the answer and a
PAYMENT-RESPONSEheader with its updated session.
How it works follows one call from end to end.
Find a service
Every service lists itself in a public catalog, so an agent can find it without knowing its address in advance.
A service posts its routes to the facilitator's BazaarThe x402 catalog of paid services. The facilitator hosts it. when it is ready and again every 30 seconds. That heartbeat is how the catalog tells a service is up. Nothing probes the services. The kit does this for you. A service that is not ready, for example while its model loads, does not declare, so its status turns stale and then down.
The catalog
The catalog at /services on this site is the same list for people. Each row shows the service, its endpoint, its price and billing unit, its status and how many paid calls it served today. Open a row to see an example request, the response shape and the provider's address. The Copy call button pastes a paid call for that route, and Copy curl pastes the unpaid request that shows its 402.
- Status comes from the age of the service's latest declaration: up under 90 seconds, stale under 10 minutes, down after that. A dash means it never declared and is listed only from its paid calls.
- Today is the number of paid calls settled since 00:00 UTC. It is an aggregate: no session, key or amount per call is ever listed.
The Bazaar
The catalog reads the facilitator's Bazaar, the x402 standard for listing paid resources. The site passes the two public reads through, so its own origin works as a Bazaar endpoint, and any x402 Bazaar client can read it.
| Read | What it returns |
|---|---|
GET /discovery/resources | The list, in pages. Filter by type, payTo, scheme, network or extensions, and page with limit (default 20, at most 100) and offset. |
GET / | The same items matched by a case-insensitive substring of the resource, description, service name or a tag. |
Each item is one paid route. Besides the standard fields it carries two that z402 adds:
lastSeen: when the provider last declared the route. Absent for a route listed only from payments.paidCallsToday: the settled calls for the route since 00:00 UTC.
{
"resource": "http://localhost:4031/v1/generate",
"type": "http",
"x402Version": 2,
"accepts": [{ "scheme": "prepaid-voucher", "network": "zcash:regtest", "amount": "200", "asset": "ZEC", "payTo": "uregtest1..." }],
"serviceName": "LLM inference",
"tags": ["llm", "inference", "text"],
"extensions": { "z402": { "unit": "output chunk" } },
"lastUpdated": "2026-10-28T14:03:10.000Z",
"lastSeen": "2026-10-28T14:03:10.000Z",
"paidCallsToday": 41
}Read the list with the stock x402 client:
import { HTTPFacilitatorClient } from "@x402/core/server";
import { withBazaar } from "@x402/extensions/bazaar";
// The facilitator's URL, or a site that passes its Bazaar reads through
const bazaar = withBazaar(new HTTPFacilitatorClient({ url: "http://localhost:3000" })).extensions.bazaar;
const { items } = await bazaar.listResources({ type: "http", limit: 100 });
for (const item of items) {
const offer = item.accepts[0];
const seen = (item as { lastSeen?: string }).lastSeen;
console.log(item.serviceName, item.resource, `${offer?.amount} zat`, seen ?? "never declared");
}curl -s 'http://localhost:3000/discovery/resources?limit=100' \
| jq '.items[] | {resource, amount: .accepts[0].amount, lastSeen, paidCallsToday}'# No wallet needed. The list is read whole and ranked on your machine.
pnpm --filter @z402/agent discover --discovery http://localhost:3000 --intent "turn a web page into markdown"
# In the Docker demo
docker compose run --rm discoverThe resource's own 402 stays authoritative. A client pays what the 402 says, not what the catalog says. See Discovery.
Make a paid call
Every paid route is called the same way: put the stock x402 client on a wallet, and call paidFetch where you would call fetch. The catalog's Copy call button pastes the call line, which this call.ts shows for URL to markdown.
import { close, paidFetch } from "./paid-fetch.js";
try {
// Pasted from the catalog's "Copy call" button
const res = await paidFetch("http://localhost:4032/v1/markdown?url=https%3A%2F%2Fexample.com");
console.log(res.status, await res.json());
} finally {
await close();
}import { wrapFetchWithPayment, x402Client } from "@x402/fetch";
import { LightwalletdTxStatus, PrepaidVoucherScheme, ZalletRpcWallet } from "@z402-net/client";
import { createLightwalletdClient } from "@z402-net/lightwalletd";
import { NETWORK_REGTEST } from "@z402-net/protocol";
// Zallet pays the deposit and lightwalletd (Zaino) reports its status.
// These are the Z3 regtest defaults.
const wallet = new ZalletRpcWallet({
url: "http://127.0.0.1:50232",
user: "zebra",
password: process.env.ZALLET_PASSWORD ?? "zebra",
});
const lwd = createLightwalletdClient({ url: "http://127.0.0.1:28137" });
const scheme = new PrepaidVoucherScheme({ wallet, chain: new LightwalletdTxStatus(lwd) });
// The one line that adds Zcash to the stock x402 client
export const paidFetch = wrapFetchWithPayment(fetch, new x402Client().register(NETWORK_REGTEST, scheme));
/** Lets a deposit in flight finish, then closes the wallet and the chain client. */
export async function close(): Promise<void> {
await scheme.close();
lwd.close();
}| Piece | Job |
|---|---|
ZalletRpcWallet | Sends the deposit from a Zallet wallet, with the session's memo. Any object with a sendDeposit method can take its place. |
LightwalletdTxStatus | Reads a deposit's status from lightwalletd or Zaino, so the client knows when it confirms. |
PrepaidVoucherScheme | The client scheme: keeps the session, decides when to deposit and signs each voucher. |
x402Client and wrapFetchWithPayment | The stock x402 client and fetch wrapper. register is the only line that adds Zcash. |
The first call to a provider makes a deposit, which can take tens of seconds. Later calls only sign a voucher. The wallet adapter today is Zallet over JSON-RPC, built and tested on the regtest stack. See Pay from an agent and @z402-net/client.
What every service does the same way
The service kit gives every service the same rules, so you can rely on them.
- Refuse before payment. Each service checks everything it can before it asks for money: the body, ids, sizes, formats, rate limits and capacity. A refused call never costs anything.
- Failures after payment are rare. A call that fails once the voucher is sent is never settled, but the stock client cannot tell it from a paid one, so it counts the call and its next voucher pays for it. Each service page lists the few failures that can happen after payment.
- A 60 second promise. The offer sets
maxTimeoutSecondsto 60, and the services are built to answer a paid call within that. - A request arrives whole. The server takes at most 15 seconds for the request headers and 30 for the whole request.
These refusals are the same on every paid route:
| Status | error | When |
|---|---|---|
| 405 | method_not_allowed | The route takes another method. The Allow header lists the right ones. |
| 409 | payment_in_flight | A copy of the same voucher is already being verified or handled. One payment runs the paid handler once. |
| 503 | not_ready | The service cannot serve right now, for example while its model loads or the chain tip is unknown. |
Every service also answers GET /health without payment: 200 when ready and 503 when not.
{
"ok": true,
"service": "Text embeddings",
"ready": true,
"network": "zcash:regtest",
"priceZat": "100",
"callsToday": 1,
"routes": [{ "method": "POST", "path": "/v1/embeddings", "unit": "1k tokens", "priceZat": "100" }]
}priceZat at the top is the service's price, and each route lists its own. callsToday counts paid calls delivered since 00:00 UTC.
Run them
docker compose up -d starts the whole project on one machine, the four services and a local model runtime included. The services are on ports 4031 to 4034. See Deploy with Docker.
docker compose up -d
docker compose run --rm smoke # one paid call on every service
docker compose run --rm discover # the catalog, as an agent sees itsmoke makes one deposit per provider, then runs a short script on each service: an LLM stream of up to three chunks, one page, one embedding, and a drop that is uploaded, held, fetched and deleted. It checks every answer and prints one line per step, then smoke ok or smoke FAILED.
Build your own
A service is its routes written as data. Describe each paid route and its price, put every check that can fail in the route's prepare handlers so it runs before payment, and apply side effects only in onDelivered. The kit declares the routes to the Bazaar by itself.
References
services/README.md: the four services, prices, settings and discoverypackages/service-kit/src/service.ts: paid routes, prices,/healthand the pre-payment rulespackages/service-kit/src/declare.ts: the Bazaar declaration heartbeatpackages/service-kit/src/run.ts: the HTTP server's timeoutsapps/web/README.md: how the catalog reads the Bazaar and derives statusapps/web/src/lib/calls.ts: the snippets the copy buttons pasteapps/agent/src/discover.ts: thediscovercommandapps/smoke/README.md: what the smoke test pays and checkscompose.yaml: ports, prices and how the services are wireddocs/superpowers/specs/2026-10-27-z402-services-design.md: the services' contractdocs/superpowers/specs/2026-10-28-z402-multi-provider-design.md: providers, the Bazaar and the heartbeat- x402 Bazaar: discover services
- x402 Bazaar extension spec