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

ServicePaid routePriceOne paid call buysProviderPort
LLM inferencePOST /v1/generate200 zatone output chunk of about 32 tokensOperator4031
URL to markdownGET /v1/markdown?url=1,000 zatone pageProvider 24032
Text embeddingsPOST /v1/embeddings100 zatone call of up to about 1,000 tokensOperator4033
Encrypted drop boxPOST /v1/drops, POST /v1/drops/:id/hold500 zat each10 blocks of storage, for an upload or a holdOperator4034

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 /chunk at 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.

ServicePriceCalls one 100,000 zat window pays for
LLM inference200 zat500 chunks
URL to markdown1,000 zat100 pages
Text embeddings100 zat1,000 calls
Encrypted drop box500 zat200 uploads or holds

See Prepaid vouchers for how a window, a deposit and the credit line work together.

How a paid call works

Request402 with the price and termsSame request with a voucherVerifyValidSettleSettled200 with the answerFirst call only, one shielded depositDo the workAgentServiceFacilitatorAgentServiceFacilitator
Request402 with the priceand termsSame request with avoucherVerifyValidSettleSettled200 with the answerFirst callonly, oneshieldeddepositDo theworkAgentServiceFacilitatorAgentServiceFacilitator
  1. The agent sends its request. The service answers 402 with the price, the provider's address and the terms in the PAYMENT-REQUIRED header.
  2. The agent's client makes a deposit if it has none, signs a voucher and retries the same request with it.
  3. The service asks the facilitator to verify the voucher, does the work, and asks it to settle.
  4. The agent gets the answer and a PAYMENT-RESPONSE header 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.

declarelistlistServicesFacilitator BazaarWebsite catalogAgents
declarelistlistServicesFacilitator BazaarWebsite catalogAgents

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.
Find a service and copy a paid call
Search the catalog, open a service and copy its paid call.

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.

ReadWhat it returns
GET /discovery/resourcesThe list, in pages. Filter by type, payTo, scheme, network or extensions, and page with limit (default 20, at most 100) and offset.
GET /discovery/search?query=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.
One item, abridged
{
  "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");
}

The 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();
}
PieceJob
ZalletRpcWalletSends the deposit from a Zallet wallet, with the session's memo. Any object with a sendDeposit method can take its place.
LightwalletdTxStatusReads a deposit's status from lightwalletd or Zaino, so the client knows when it confirms.
PrepaidVoucherSchemeThe client scheme: keeps the session, decides when to deposit and signs each voucher.
x402Client and wrapFetchWithPaymentThe 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 maxTimeoutSeconds to 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:

StatuserrorWhen
405method_not_allowedThe route takes another method. The Allow header lists the right ones.
409payment_in_flightA copy of the same voucher is already being verified or handled. One payment runs the paid handler once.
503not_readyThe 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.

JSON
{
  "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.

Bash
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 it

smoke 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 discovery
  • packages/service-kit/src/service.ts: paid routes, prices, /health and the pre-payment rules
  • packages/service-kit/src/declare.ts: the Bazaar declaration heartbeat
  • packages/service-kit/src/run.ts: the HTTP server's timeouts
  • apps/web/README.md: how the catalog reads the Bazaar and derives status
  • apps/web/src/lib/calls.ts: the snippets the copy buttons paste
  • apps/agent/src/discover.ts: the discover command
  • apps/smoke/README.md: what the smoke test pays and checks
  • compose.yaml: ports, prices and how the services are wired
  • docs/superpowers/specs/2026-10-27-z402-services-design.md: the services' contract
  • docs/superpowers/specs/2026-10-28-z402-multi-provider-design.md: providers, the Bazaar and the heartbeat
  • x402 Bazaar: discover services
  • x402 Bazaar extension spec