Guide: Publish an endpoint
You can do everything below as MCP tool calls (connected in Claude) or as REST calls with an automation key / session. Both share the same rules.
Authenticate first — see Connect in Claude. All examples assume
Authorization: Bearer <oauth-or-empk-token>for REST.
A. API relay (wrap one HTTP origin)
Create (MCP tool create_api_relay, or POST /dashboard/endpoints/api):
{
"name": "Weather",
"slug": "weather",
"origin_url": "https://api.example.com/v1/weather",
"method": "GET",
"price_usd": "0.02",
"networks": ["eip155:8453"],
"pay_to": "0xYourPayoutWallet",
"description": "5-day forecast for a city",
"public_listing": false
}
Free listings
Want to give an endpoint away? Set free: true and omit price_usd / pay_to:
{
"name": "Open Weather",
"slug": "open-weather",
"origin_url": "https://api.example.com/v1/weather",
"method": "GET",
"free": true,
"networks": ["eip155:8453"],
"public_listing": true
}
A free endpoint delivers the origin’s response with no payment, no settlement,
and no protocol fee — buyers just call the URL. It shows as Free on the
marketplace. Toggle later with update_endpoint: free: true clears pricing;
free: false (with price_usd, networks, pay_to) makes it paid again. In the
dashboard, tick “Make this free” on the create form.
Field notes:
slug— lowercase[a-z0-9-], unique per provider; becomes the URL.networks— one or more CAIP-2 ids; all must be the same environment (testnet →test, mainnet →live). See Networks.pay_to— your wallet (the splitter pays your leg here). Match the chain family.public_listing: truemakes it eligible for the marketplace once published.- Optional origin auth (if your API needs a key): add
auth_type(bearer | api_key | basic | custom),auth_value, andauth_header_name(forapi_key/custom). Encrypted at rest; injected on forward; never shown to buyers.
The response includes the relay_url (/r/{provider}/{slug}) and the price in
atomic units per network.
The buyer-facing sample (what buyers see before paying)
Every listing shows buyers a data sample — a JSON Schema + an example — so they (often agents) can judge fit before paying. You don’t hand-write it; the backend builds it. All fields below are optional with safe defaults — leave them alone and you get a synthetic sample automatically.
protection— what the published example contains. Defaultsynthetic: the structure is real but the values are fabricated, so no real data is given away. Alternatives:redacted(real values masked) orreal(show the actual captured response — only for open data).- How we get the schema (two paths, you pick one):
- Captured (recommended): set
trial_safe: true(you assert the call has no side effects) and give asample_request(the body/params we send). On publish we call your origin once, capture the response, infer the schema, and synthesize the example. The real values are discarded — they never persist. For a parameterized origin,sample_requestsupplies the params: on a GET they fill any{placeholder}in yourorigin_url(e.g..../{iso3}/profilewith{"iso3":"USA"}) and pass through as query string — exactly what a buyer sends as?iso3=USA. So you don’t hardcode a country; each buyer picks their own. - Declared: if we can’t safely call your origin, paste one example response
in
example.output(or supply a rawoutput_schema). We infer the schema from it and synthesize. No schema authoring required.
- Captured (recommended): set
- Offer real data as a free trial (optional, drives conversion):
trial_safe: trueplusfree_trial_calls(1–20) lets buyers make that many unpaid live calls to see real data; bound your origin cost withfree_trial_daily_cap. This is separate from the sample: sample = free synthetic shape; trial = metered real data. Settingprotection: "real"andfree_trial_callsis redundant (you’d be giving real data away for free unmetered) — the API returns a softwarningsnote.
Example with sampling enabled:
{
"name": "Weather", "slug": "weather",
"origin_url": "https://api.example.com/v1/weather", "method": "GET",
"price_usd": "0.02", "networks": ["eip155:8453"], "pay_to": "0xYourPayoutWallet",
"trial_safe": true,
"sample_request": { "city": "SF" },
"protection": "synthetic",
"free_trial_calls": 3,
"public_listing": true
}
Re-run the capture anytime with POST /dashboard/endpoints/{id}/probe — the
response returns the generated sample so you can preview exactly what buyers see.
B. MCP wrapper (wrap an upstream MCP server, priced per tool)
Create (MCP tool create_mcp_wrapper, or POST /dashboard/endpoints/mcp):
{
"name": "Forecast MCP",
"slug": "forecast",
"origin_url": "https://mcp.example.com/rpc",
"networks": ["stellar:pubnet"],
"pay_to": "G...YourStellarWallet",
"tools": [
{"name": "get_forecast", "price_usd": "0.03", "description": "5-day forecast",
"input_schema": {"type": "object", "properties": {"city": {"type": "string"}}}}
],
"public_listing": true
}
- Each tool is priced individually. Buyers connect to
/m/{provider}/{slug};initialize/tools/listare free,tools/callis billed per tool. - Origin auth fields work here too (same as above).
- Sample for MCP wrappers is naturally per-tool: the input schema of each
tool is the sample. Because
tools/listis free upstream, you can fill those schemas with no cost or hand-typing — callPOST /dashboard/endpoints/{id}/introspectand we read the server’s realtools/listand populate each priced tool’sinput_schema+description(provenance: "mcp_introspected").
C. Publish
Creation makes a draft. Publish it to go live:
- MCP tool
publish_endpointwith{ "endpoint_id": "…" }, orPOST /dashboard/endpoints/{endpoint_id}/publish. - With
public_listing: true, publishing also lists it on the marketplace (/discover.json, listing page/e/{provider}/{slug}).
Design your unit of sale
Per-call pricing only works when a call maps to a sensible unit of data. Three rules of thumb:
- Granular beats bulk — for YOUR revenue.
GET /profile?iso3=DEUat $0.01 monetizes every question an agent asks, forever.GET /all-recordsat $0.01 sells your entire dataset once, to the first buyer. If a method exists to dump everything, it’s really a dataset — list it as one (create_dataset), priced per download. - Watch price vs payload. Every listing’s
reliabilityblock now carriesavg_response_bytesover recent paid calls — if your $0.001 call ships 5 MB, your pricing is telling buyers to arbitrage you. - Make the unit explicit with
max_response_kb(create/update, 1–5120 KB): a hard per-call response cap, enforced loudly by the relay. Bigger asks then require more calls — or the dataset listing. (Downloads are exempt: they stream, any size.)
Agents are the buyers: they prefer targeted, parameterized calls they can pay for repeatedly over one giant blob they must parse — granularity fits both your margin and their workflow.
Rank higher with the DS-ready badge. Every create/update response carries a
quality score and a quality_next hint. Complete listings — description,
per-column descriptions in the schema (the buyer’s codebook), ≥3 sample rows,
a license for datasets — get ▣ DS-ready and sort first in search. See the
checklist.
Worked example — World Bank GDP, end-to-end
A concrete walk-through wrapping a real public API (no origin auth, safe to call),
the way it actually goes in a connected Claude session. The origin is parameterized
({country} / {indicator}), so each buyer picks their own values.
1 · Create the relay (create_api_relay):
{
"name": "World Bank — latest indicator value",
"slug": "worldbank-latest-value",
"origin_url": "https://api.worldbank.org/v2/country/{country}/indicator/{indicator}?format=json&mrnev=1",
"method": "GET",
"price_usd": "0.02",
"networks": ["eip155:8453"],
"pay_to": "0xYourPayoutWallet",
"description": "Most-recent value of any World Bank indicator for a country",
"trial_safe": true,
"sample_request": { "country": "USA", "indicator": "NY.GDP.MKTP.CD" },
"protection": "synthetic",
"free_trial_calls": 3,
"public_listing": true
}
What each choice does here:
origin_urlhas{country}/{indicator}placeholders — a buyer’sparams={"country":"USA","indicator":"NY.GDP.MKTP.CD"}fills them.trial_safe: true+sample_requestlets us call the origin once on publish, infer the response schema, and synthesize a fake-but-shaped example.protection: "synthetic"→ buyers see the shape, not real values, for free.free_trial_calls: 3→ buyers can make 3 unpaid real calls before paying.
The response gives you the relay_url /r/{provider}/worldbank-latest-value.
2 · Preview the sample (optional): POST /dashboard/endpoints/{id}/probe
re-runs the capture and returns exactly what buyers will see — confirm the schema
and synthetic example look right.
3 · Publish: publish_endpoint({ "endpoint_id": "…" }). With
public_listing: true it now appears on the marketplace and /discover.json.
4 · Verify: hit the relay URL with no payment → 402 with 0.02 + the
networks; then pay it once (or use the connector’s call_endpoint) → real GDP data
- an on-chain receipt, and your
pay_towallet is credited the full$0.02.
Selling a file instead of a live query? The dataset flow is the same idea with a download link — see Sell a dataset.
Verify it works
list_endpoints(orGET /dashboard/endpoints) showsstatus: active.- Hit the relay URL with no payment → you should get 402 with your price.
- Pay it once end-to-end (see Buying) to confirm settlement + forwarding.
Next: Getting paid (how pay_to + the splitter settle to
you per call), then Manage endpoints to edit price/origin,
pause, or delete.