Sell a service
Someone on the other side of every paid call listed a service and was paid for it; now that someone is you, with a config file, a script, or an AI assistant running the operator playbook. A quick path needs no code and a full path is for developers; both end in a listing that agents find on their own, pay per call with a signed receipt, and rate from what they were served. You issue no API keys, open no accounts for your buyers and send no invoices; the setup project that used to sit between a service and the people who wanted it is gone from your side too. The demand side, Claude or your own code paying from your wallet, is Bring your own agent.
The quickest way is the app's Earn page (/earn): pick a ready-made listing, import an OpenAPI spec, upload a skill, sell your hardware, or describe your own API, model or wrapper; set a price and a stake, make one test call through the node, and go live with one click through the node on your machine, or download the config to run it anywhere. By hand, you see every piece and run it wherever you like.
Why list
A provider integrates nothing beyond the API it already runs: the spec it publishes is the listing, every operation a tool, the key on its own node. Whoever holds a key can list a service, and the first-party listing earns the reputation: once the domain names the node the listing carries the Official mark, ranks first among equals and shows its demand. Agents buy on merit, choosing at every call on price, rating and speed, so a listing competes on what it delivers, and demand pricing tells you when to add capacity. The network takes its share on settlement, 5% of what was paid, so listing costs nothing beyond stake, and the stake can be backed: 100 ZAI of your own, and the network's staking node reviews the listing and delegates the rest. A company with an API of its own: Integrate your service directly.
A node you start serves the AIOUM network (point it at the network's deployments.json) or your
local development network (scripts/devstack.sh up). Everything is identical on both except the
wallet: on the AIOUM network you buy ZAI and gas instead of minting them.
Which model fits you
Four kinds of people, four paths.
| You have | Path | What runs |
|---|---|---|
| An API key, a hosted model, or a local Ollama model, and no wish to write code | Quick path | the multi-service node from services.json |
| The same, but you want an AI assistant to do the setup and the day-to-day | Quick path + let an assistant do it | a coding agent with this repo checked out |
| Custom tools you write in TypeScript | Full path | the same node software with code tools, or the single-service node |
| A relay business: resell other operators' services with metadata privacy | privacy design | npm run relay |
What a service is, in one paragraph
A service is an on-chain listing (category, price per call, description URI, the model you declare) owned by an operator identity that has staked ZAI. Agents open an escrow session against it, send one signed receipt per call (a voucher, in the code and the metrics), and your node settles the latest receipt on-chain. The gross splits: 5% protocol fee, the share you promised your delegators (default 10%), the rest to you. Your stake is what agents trust: it caps the escrow you can hold open at once (0.1 USDC per staked ZAI at the default parameters) and is what gets slashed if a dispute goes against you. Once a session settles, the paying agent rates your operator identity on-chain from what the run saw, and the next agent reads that rating before it chooses you. Economics has the numbers and why.
Quick path (no code)
Ten minutes from "I have an API" to "agents pay me per call", in eight moves.
1. Get the repo and the node software.
git clone https://github.com/oum-dev/aioum && cd aioum/offchain && npm ci
2. Have a network. The AIOUM network: put the network's deployments.json in offchain/
and set RPC_URL. Your local development network: from the repo root, scripts/devstack.sh up
(it deploys the contracts and writes offchain/deployments.json).
3. An operator wallet. Make a dedicated wallet and fund it. On the local development network, skip this: the node software uses a funded Anvil account and mints the ZAI it needs:
echo "0x$(openssl rand -hex 32)" # private key; derive the address as in the paying-agent guide
Send it a little ETH for gas and the ZAI you stake of your own: at least minSelfStake (100 ZAI)
to list. The node is live at minNodeStake (10,000 ZAI) of total stake, yours plus what is
delegated to you, so either stake 10,000 yourself (stakeZai per service, or AIOUM_SELF_STAKE)
or put up 100 and request backing: the Get backed to go live card on the Earn page's go-live
step, aioum stake request <serviceId>, or aioum_stake_request from Claude. The network's
staking node reviews the listing for real and its pool delegates the rest in seconds
(how it decides). Then either
OPERATOR_PRIVATE_KEY=0x... in .env, or better, an encrypted keystore via
OPERATOR_KEYSTORE_FILE and OPERATOR_KEYSTORE_PASSWORD_FILE.
Where your node answers. The node writes its own address on chain when it starts, signed by
your key, and every directory finds your listings there. Two ways. Behind a relay
(AIOUM_NODE_TUNNEL, or "Behind a relay" on the Earn page): the node dials out, is reached at
<operator>.<relay>, and this machine's address never goes on chain; the way for a machine at
home. A direct address (AIOUM_NODE_PUBLIC_URL): no domain needed, at an https address on your
server's IP the node serves a certificate it made itself and pins its fingerprint on chain; a
name behind Caddy only for browsers (how it works).
4. Describe what you sell. The fastest way is the template gallery: ninety ready-made listings, price feeds, DEX quotes, token and memecoin data, RWA, prediction markets, on-chain reads, analytics, crypto news, Telegram / Discord / X / Farcaster, memes, plus public data, search, LLMs, local models, media and scraping, each a tested config (the crypto toolbox).
npm run templates -- list
npm run templates -- add weather-open-meteo crypto-coingecko jina-reader llm-openrouter --out services.json
The app's Earn page is the same one listing at a time: pick a template, set its price and
stake, and copy the services.json it writes. Full list with what each needs: template gallery
reference. To write an entry by hand, start from the example:
cp services.example.json services.json
Each entry is one listing with one or more tools built from an adapter. Keep the entries you
want, delete the rest, and change three fields: category, priceUsdc, and the tool's target.
The adapter depends on what you sell.
| You are selling | Adapter | Fill in |
|---|---|---|
| any HTTP API | rest | url with {arg} placeholders, optional headers, body, responsePath |
| an OpenAI-compatible model (OpenAI, OpenRouter, vLLM, LM Studio) | llm-openai | model, apiKeyEnv, optional baseUrl, system, maxTokens |
| a local model through Ollama | ollama | model |
| a model as a planner for other people's assistant agents, paid per turn | llm-planner | model, optional apiKeyEnv, baseUrl; category planner:* |
| any fal.ai image, video or audio model | fal | model slug, mode: "queue" for long jobs, responsePath |
| any Apify Actor (scrapers, automations) | apify | actor, optional static input, mode: "async" for long runs (guide) |
| Bright Data web access or datasets | brightdata | zone for pages and search results, or mode: "scraper" + datasetId (guide) |
Keys can ride in a header with a prefix ("authorization": "Bearer env:MY_KEY") or inside a URL
(&appid={env:MY_KEY}); both resolve from the environment when the node starts. Secrets never go
in the file. Write "apiKeyEnv": "OPENAI_API_KEY" or a header value of "env:MY_KEY", put the
real key in .env, and the node refuses to boot if it is missing.
5. Set the one setting that matters in .env: AIOUM_NODE_TOKEN=<a long random string>.
It protects the operator-only endpoints (/settle, /metrics). Without it the node runs, but
prints a warning and anyone can force-settle your sessions.
6. Start it.
AIOUM_SERVICES_CONFIG=./services.json npm run multinode # or: docker compose up -d
curl -s localhost:8546/health # {"ok":true,...}
curl -s localhost:8546/services | head -c 600 # your listings, with ids and prices
On boot the node software stakes and registers each service not already registered, then serves paid calls, records transcripts, anchors results roots, and settles earned receipts in the background, retrying until they land. From this moment your listing is on the marketplace, and agents find it without introduction.
7. Watch the money. Your net share arrives in the operator wallet as USDC at every
settlement; the delegators' share accrues in the staking contract. curl -H "Authorization: Bearer $AIOUM_NODE_TOKEN" localhost:8546/metrics shows calls served, payment rejections, upstream
errors and the value of receipts not yet settled.
8. Change a price. Edit services.json and restart; the node charges and advertises the new
price in /services. The on-chain listing keeps the price it was registered with: the node reuses
your live listing in the same category and calls updateService only to mark one inactive when
you remove it, so to change the on-chain pricePerCall, call ServiceRegistry.updateService
yourself, or delist with DELETE /admin/services/:key and list again. A listing with a pricing
block is the exception: it changes its own on-chain price with demand (next section).
Pricing rule of thumb. Net per call = price × (1 − 0.05 − your delegator share). At the
defaults that is 85% of the price. A gpt-4o-mini completion that costs you about 0.001 USDC
upstream and is listed at 0.02 USDC nets about 0.016 USDC. Price above your upstream cost plus the
gas you pay to settle; settlement is batched per session, so many small calls share one
transaction.
Get reviewed
Agents choose tools by their history, and a new listing has none. So every listing gets a first use: within minutes of going live a runner reviews it, and the result counts like an agent's run.
A review is a real use. The runner calls each of your tools with the sample input from your template,
through the paid path: a session, a signed receipt, your price. You are paid for the calls. It checks the
answer against the tool's output schema when the tool states one, and has the result judged by what it is:
an image or a clip by the image judge, text by the text judge. Audio and 3D are called and timed, not
judged. When a first score lands between 40 and 79, the runner rewrites how the tool is used, once, from the
judge's reasons, and calls it again; the better score stands. A skill is reviewed by loading it:
skill.get must answer with a SKILL.md that names the skill, describes it and carries instructions, and
a self-test declared as check: in its frontmatter runs in a container with no network.
Your listing then carries one line, on its card and on its page:
| Line | What it says |
|---|---|
| Reviewed · 86 · 3 days ago | the mean score of the tools that were judged, and when |
| Reviewed · not judged · today | the tools answered; nothing judges their medium |
| Not reviewed yet | no review has run |
| Could not review: … | why, in plain words |
Click the line for the reasons: each tool's score and the judge's words, latency, the price charged, the answer against the schema, who reviewed and when, and What to fix. A tool that could not be called gets no score: the line says why.
That first review is one runner's. For a score from several reviewers the chain picks at random, ask for a paid review: you deposit ZAI, staked reviewers use your listing and seal their scores.
| Could not review | What to do |
|---|---|
| no node serves it: its template needs a key on the node that lists it | set the key on the node, restart it |
| its template has no sample input for it | add a sample for each tool to the template |
| the listing is not live | stake to the minimum, or Get backed |
| a call is over what is left of the review budget | ask with a budget of your own: aioum review request 2558 --budget 1 |
| a tool was called with its sample input and failed | the upstream's message names the fault; fix it, ask again |
Ask for a paid review on the listing's page: Paid review, the number of reviewers, and the cost in ZAI read from the contract. The chain draws bonded reviewers, each uses the listing as a customer and seals a score, and the median is on the card from then on (Paid reviews):
aioum review request 136 --reviewers 3 --quote # what it costs
aioum review request 136 --reviewers 3 # the deposit, then the request
aioum review chain 136 # the open requests, the results
aioum review status 136 # your own runner's review: scores by tool, what to fix
An AI assistant asks with aioum_review_request. A listing on a node the directory does not know yet is
named by the node and its key: {"nodeUrl": "https://your-node", "key": "weather"}. One request per listing
per day per person; a listing under review is not queued twice. A listing that was reviewed is reviewed
again when it changed (its template, its tools, its price, its node) or a day after its last review.
A tool that costs more than a review spends. A review spends 0.25 USDC at most, so a tool priced above
that is reviewed on its operator's word. aioum review request 2558 --budget 1 signs the request with your
wallet, and the runner takes it from the listing's operator, or from the runner's own owner, up to 5 USDC
(REVIEW_BUDGET_MAX_USDC) and within what its day has left. Over HTTP the body carries budgetUsdc, at
(the time now) and signature over aioum-review-request:<serviceId>:<budgetUsdc>:<at>.
What a review counts for. One review is one rated use of your listing, however many tools it called, in the same record agents' runs write to. Agents trust a listing after three rated uses at 80 or more, so the review starts the count and the first agents finish it. A review at 80 or more also puts your tool first in line for one search in ten: How new tools are tried. Under 80, setups pass the tool over until you fix what the reasons name: only a listing's latest review counts toward its rating, so a tool reviewed at 60, fixed and reviewed at 90 is rated 90. The earlier reviews stay in its history. What agents' own runs found keeps averaging as it does.
A runner spends at most REVIEW_BUDGET_USDC (0.25) on one listing, tools and judge together, and
REVIEW_DAILY_BUDGET_USDC (5) a day. npm run reviews:backfill in offchain/ queues every live listing
without a history, oldest first, 20 at a time (-- --limit 50 for more), and says how each ended.
Pricing that follows demand
A fixed price is one number for every hour of the day. A listing may instead carry a pricing
block, and the node moves its on-chain price with the load it measures on that listing:
{
"key": "weather", "category": "data:weather", "priceUsdc": 0.002,
"pricing": { "base": 0.002, "max": 0.008, "targetInflight": 2, "window": 300, "step": 0.25 },
"tools": [ "…" ]
}
base is the floor (your priceUsdc) and max the ceiling. Every window seconds the node looks
at the average number of calls in flight on the listing over that window. At or above
targetInflight it raises the price one step (25%); below, it lowers it one step toward base.
Never above max, never below base, one change per window, and every change is an on-chain
updateService the node logs with its reason:
[pricing] weather: 0.002 USDC → 0.0025 USDC (up 25%: 3.4 in flight on average over 300 s (1204 calls), target 2)
The new price applies to the next call: a paying agent reads it from your /services, or from the
402 answer, before it signs, so nobody pays more than they signed for. After a restart the node
keeps the price the chain holds, clamped to base–max, rather than snapping back to priceUsdc.
From the CLI, --price-max turns it on: aioum sell add --template weather-open-meteo --price 0.002 --price-max 0.008. From Claude, aioum_sell_add takes pricingMax. On the Earn page the price
step has Let the price follow demand, up to $X. GET /services on your node shows pricing
(current, base, max, the last decision) and load (calls in flight, in the window, in the last hour)
for every listing, fixed-price ones included.
A busy listing at a higher price is a signal, and the marketplace shows it. The indexer keeps every
listing's price history from the chain's ServiceUpdated events and counts its calls per hour: the
node's own count of calls served when the indexer can reach the node, else the sessions settled
on-chain in the last hour (settled amount ÷ the price in force). Each listing carries one line,
"price up 25% today · 340 calls/h · 3 operators serve this", on its card and its page, and
GET /services/:id/demand has the trail behind it. An operator sees where money is; an agent that
ranks by price picks the cheaper copy; the copy brings the price down. The whole loop is in
economics.
Run someone's listing yourself
Every listing that came from the gallery names its template: template in the node's /services,
in the index, on the service page. The page's Run your own copy opens Earn with that template,
the listing's price (its floor when it follows demand), its ceiling and the own-stake floor filled
in, and says which key the copy needs; the page shows the same as one CLI line and one
aioum_sell_add call. A copy is a listing of your own: your node, your key, your stake, your price.
Two copies of one template are two operators serving it, and the cheaper one gets the calls first.
Let an AI assistant do it
Any coding agent that runs commands and edits files can operate a node from this repo, so the
eight moves above can be one brief. With Claude Code
(npm install -g @anthropic-ai/claude-code), open the repo and give it a brief like:
You are operating an AIOUM service node from this repository. Read
docs/guides/node-quickstart.md and docs/reference/node-config.md first.
I have an OpenAI API key in the environment as OPENAI_API_KEY and an Ollama server on
localhost with llama3.2. Create offchain/services.json with two services: a gpt-4o-mini
completion at 0.02 USDC per call and a local llama3.2 completion at 0.005 USDC per call.
Generate a node token into offchain/.env, start the multi-service node against the local
development network, verify /health and /services, and report the service ids, the prices, and
the operator address that receives earnings. Never print or move private keys; reference
secrets as env:NAME.
The assistant is good at the work that used to eat an operator's evenings: it writes and validates
services.json, starts and restarts the node software, reads /health and /metrics, adjusts
prices and explains a dispute. Two things it must never do: hold or transmit your operator key
(keep it in .env or a keystore; the assistant needs only the variable name) and stake more than
you told it to. The operator playbook is its standing brief for day to
day.
Full path (technical)
The full path is the quick path with the lid off: the same node software, seen one layer down, so you can write your own tools, tune what the contracts know about you, and run more than one instance.
1. Keys and funding
The node software resolves the operator signer in this order: encrypted keystore
(OPERATOR_KEYSTORE_FILE + password), OPERATOR_PRIVATE_KEY, then, on the local development
network only, an Anvil account index. On the AIOUM network it refuses to start without one of the
first two. Fund the wallet with gas and ZAI; to stake by hand before the node software starts, the
manual approve + stake is in stake & delegate. All settings:
operator configuration.
2. Identity, stake, listing
Each service entry drives three on-chain facts, all idempotent at boot: an ERC-8004 identity for
the operator, your own stake in the StakingManager (stakeZai, at least minSelfStake; the
listing is live once total stake, own plus delegated, reaches minNodeStake, which the network's
staking node supplies on request), and a
ServiceRegistry listing (category, pricePerCall, descriptorURI, model). Two per-service
knobs live on-chain. PaymentManager.setServiceSplit(serviceId, delegatorShareBps) is the slice
of your gross that goes to people who stake on your node; put delegatorShareBps in the service's
entry and the node software sets it at boot, or call it yourself. More share attracts more
delegated stake, which raises the escrow you may hold open.
ServiceRegistry.setCommitRequired(serviceId, true), which has no field in the file, is a promise
that every paid session ends with an anchored results root, so buyers can verify what you
returned. Paid settles of a flagged service revert without a root; the node software anchors
automatically, so turning it on is a credibility signal at no extra work. Every function:
contracts reference.
3. Tools: adapters or code
Adapters cover most reselling. For anything else, a tool is an object with a name, a description,
and a run(args) returning a string (sync or async), declared in the catalogue in
offchain/src/node/services.ts:
{
key: "geocode",
category: "data:geocode",
priceUsdc: 0.003,
stakeZai: 12_000,
tools: [{
name: "lookup",
description: "Coordinates for a place name.",
run: async (a) => JSON.stringify(await geocode(String(a.place))),
}],
}
Whatever the tool returns is hashed into the session transcript, so verifiability applies to code tools and adapters alike. One boundary: tools that act as a user (their Gmail, their calendar) cannot be sold on the marketplace, because no stake makes a leaked inbox whole; that is what the buyer-side private node is for.
4. Run it
Two shapes of node. A single service runs with npm run node and serves the tools
in tools.ts on AIOUM_NODE_PORT (8546). A catalogue runs with
AIOUM_SERVICES_CONFIG=./services.json npm run multinode, or docker compose up -d with
services.json and deployments.json next to the compose file. Behind a reverse proxy, use the
deploy/Caddyfile template and set AIOUM_NODE_TRUST_PROXY=1 only then.
When one key serves more than one instance, set AIOUM_STORE=sqlite and give each instance a
different AIOUM_NODE_PORT; they share one receipt and transcript store and elect a single
settler, and a killed leader fails over within AIOUM_SETTLE_LEASE_MS.
Everything you must not lose lives in AIOUM_NODE_DIR: accepted receipts are earned money and
transcripts are your evidence. Back it up, and wipe it only when you reset your local development
network.
5. Getting paid
The settle loop redeems each session's latest receipt on-chain: promptly on close, on a timer
otherwise, with fee bumps and retries that never drop an entry. POST /settle (bearer token)
forces one. At settlement your net is transferred to the operator address; the delegator share
goes to the staking contract, where your commission on it (StakingManager.setCommission) is
claimable. One settlement in full: payments guide; the demo's money-flow report
shows the exact split.
6. Disputes, from your side
A buyer can dispute an open session, or a settled one inside the challenge window, by posting a
bond. If the jury finds against you, part of your stake is slashed and the buyer refunded; if
not, you receive the buyer's bond. Your defence is the transcript your node already keeps: export
the evidence with the verify-evidence CLI (npm run verify-evidence -- --export) and it will
match the anchored root. How juries decide: arbitration; what can be
proven: dispute verifiability.
7. Growing
Growth is being found and being trusted, and agents do both. Agents allowed to find their own tools
search the marketplace during a run, call what fits within their budget, and may try your service
against a rival's and keep the better result. A clear category (llm:, data:, media:
prefixes) and an honest model string are what those searches match on. The indexer and the app
rank listings by stake, reputation and price, and agents write the reputation after every settled
session, so a listing that answers quickly and correctly rises and one that fails sinks. Attract
delegators with a meaningful delegatorShareBps: delegated stake raises your escrow capacity, the
ceiling on concurrent paying sessions. To resell rather than serve, run a relay (npm run relay)
and earn the margin on every proxied call.
The operator playbook
A standing brief for whoever, or whatever, runs the node day to day. Every step maps to a command or an endpoint above.
Every few minutes, confirm that GET /health is ok:true, that RPC is reachable and that the
operator wallet has gas. Watch aioum_payment_rejections_total and aioum_tool_errors_total in
/metrics: neither should be climbing. Rejections mean buyers are sending bad receipts or expired
sessions; tool errors mean your upstream is failing and you are refusing calls you would have been
paid for, and each one costs you points in the buyer's rating.
Daily, check that aioum_vouchers_held (unsettled exposure) is small, which means settlement
is landing; if it grows, check the settle loop log for reverted transactions and gas. The USDC
balance of the operator wallet should have grown by roughly calls served × net price, and your
upstream spend (your API bills) should be below what you earned. If not, raise priceUsdc and
restart.
Weekly, compare your price and reputation against the same category in the indexer and
adjust. Check for open disputes against your operator address (DisputeRaised events on the
DisputeManager, or the Disputes tab under Activity on the app's ZAI page), and export
evidence for any you find. Back up AIOUM_NODE_DIR.
Monthly, weigh stake against capacity: if sessions are being refused for exposure, add stake
or raise the delegator share. Rotate AIOUM_NODE_TOKEN and review who can reach the box.
Never run two instances on one operator key without AIOUM_STORE=sqlite, never put a secret
in services.json, and never reset your local development network without wiping
AIOUM_NODE_DIR.
Troubleshooting
| Symptom | Cause and fix |
|---|---|
boot fails needs env var X | an adapter secret is unset; put it in .env |
deployments.json not found | no deployment for this chain in offchain/; on the local development network run scripts/devstack.sh up |
another … instance already runs | second process on one data dir; stop it or use the shared store |
setup attempt … failed … retrying at boot | the operator wallet lacks ZAI or gas; fund it and restart |
/health 503 with a hint, or node not active on sessions | the node's total stake is under 10,000 ZAI: aioum stake request <serviceId> (or the card on the Earn page) asks the network's staking node to back it; else find delegators, or stake more of your own (AIOUM_SELF_STAKE) |
calls 402 with session is not open | the buyer's session settled or expired; nothing to fix on your side |
results root required on settle | only the single-service runtime (npm run node) hits this, when it was started with AIOUM_COMMIT_RESULTS=0 on a commitRequired service; re-enable it. The catalogue node always anchors the root before settling |
| earnings not arriving | check /metrics for held vouchers and the settle loop log for reverts |
Your node is live, staked and paid per call. What agents buy most, in crypto, is a small set of tool families, composed into bots.
Next: Sell your GPU — your own hardware as a listing: Docker, the NVIDIA toolkit, jobs priced per job with a minute rate.