Marketplace
The one market between orgs: agents and personas to hire, apps, skills, MCP servers and tools to buy, each sold by the org that owns it, and the two-sided work one org buys from another.
Also for this capability: API · CLI · MCP · SDKs
The one market between orgs: agents and personas to hire, apps, skills, MCP servers and tools to buy, each sold by the org that owns it, and the two-sided work one org buys from another.
| Base URL | https://api.hanzo.ai |
| Operations | 23 |
| Auth | Authorization: Bearer $HANZO_API_KEY |
Specification
HIP-1137 · Marketplace — Listings and Installs — Draft · read the specification →
/v1/marketplace is the shop for tools and agents: browse, install into your
project, publish your own free or priced. It is a thin layer over the unified
tool plane — discovery reads the tool registry, install and uninstall ARE the
registry's activation writes — plus a listing store that says what a tool costs
and which wallet is paid. Marketplace never dispatches a tool and never moves
money itself. It is implemented in hanzoai/cloud at apps/marketplace
(HIP-0106).
Motivation
A marketplace that keeps its own installed-tools table drifts from the registry
that actually dispatches, and a price that lives only in one process's memory
is not enforced anywhere else. Both defects were real here: the price table,
the charger and the wallet resolver were process-globals, the fleet runs one
process per app, so in the settling process the table was nil — and reading a
nil table as "nothing is priced" made every listed tool free the moment the
fleet split (apps/marketplace/marketplace.go:16-30).
Specification
The key words MUST, MUST NOT, SHOULD, SHOULD NOT and MAY are to be interpreted as in RFC 2119.
§1 One store, and what is deliberately not in it
The listing store is one encrypted SQLite file, the deployment's own
marketplace (apps/marketplace/store.go:51), keyed by (publisher_org, id) —
a publisher only ever mutates its own listings. Installs are NOT in it:
install and uninstall are the tool registry's activation writes for the
caller's (org, project), so there is one store and one truth for what is
active (apps/marketplace/marketplace.go:8-15).
§2 The address
Six operations under /v1/marketplace, all typed: discovery (catalog plus
listing overlay plus installed flag), the caller org's own listings, publish,
unpublish, install, uninstall. A published listing must name a tool that
already resolves in the publisher's own scope, so a listing can never
advertise a capability that does not exist; a priced listing must name the
payout wallet, so a monetized offer is never unpayable; the price is exact to
18 decimal places, so a per-call price below a cent is a real price and not a
rounded-away zero.
§3 Money: the table here, the rail elsewhere
Marketplace itself is free (cloud.Free, plugin/marketplace/main.go). A
monetized listing is enforced per call by the x402 rail, and the enforcement is
four internal-plane operations, each served by the process that owns the
answer: tools asks x402 to settle; x402 asks marketplace what it costs and who
is paid; x402 asks wallets to resolve the payee; x402 asks commerce to credit
it (apps/marketplace/marketplace.go:32-39). The in-process seam stays the
fast path where an owner is co-resident; both paths are the same policy and
both fail closed, asserted against each other by a one-process test and a
five-process test (apps/marketplace/marketplace.go:41-44).
On the plane, the price answer carries the row's recipient, never a request's:
a buyer that could state either would buy at its own price or redirect the
credit. priced=false means FREE and is an ANSWER — an unlisted tool lands
there — while a store failure is an ERROR, because "I could not look it up"
must never read as "it costs nothing" (apps/marketplace/rpc.go:22-33,47-50).
The price op reads no tenant and requires none: a price is the shop window,
the same figure for everyone.
§4 Tenancy
Every REST operation is org-gated on the validated principal (HIP-0026),
parked by the bridge the composer installs once at the root. The payee is
structurally the publisher's: PublisherOrg is the payee org, and the wallet id
resolves only within the org that is asked for, which makes a cross-org credit
unconstructible rather than merely unlikely
(apps/marketplace/store.go:25-31).
§5 Events, observability, stage, upstream
It publishes nothing on the bus and emits nothing beyond the request span
every route gets. The stage is beta (manifest/apps.go:430, Stage: Beta):
reached by flag until promoted (HIP-0139 §8). It derives from no upstream.
Rationale
The alternative to "install is activation" is a marketplace-owned installs table, which is a second copy of the registry's fact — the defect the plugin contract names — and it would disagree first exactly where it matters, on whether a priced tool is active. The alternative to asking the owning process for the price was replicating the table into the rail's process, a cache that turns every price change into a coherence problem on the money path.
Security Considerations
The money path is the exposure, and its two failure shapes are opposite. Fail open: a missing price table read as "free" dispensed paid tools for nothing — closed by making absence an error and free an explicit answer. Redirection: a caller-influenced recipient or price turns the rail into a payout to the attacker — closed by binding both to the listing row, publisher-owned and publisher-org-payable only. The listing store's own writes are bounded by the (publisher_org, id) key, so a tenant can unpublish only what it published.
Four surfaces
| Surface | Reaches this capability as | Coverage |
|---|---|---|
| REST | marketplace at its own prefix | 23 operations |
| CLI | hanzo marketplace … | 6 of 23 — the CLI pins the document on its own clock |
| SDK | MarketplaceApi in every published client | 23 methods |
| MCP | tool marketplace on https://api.hanzo.ai/v1/mcp | 23 operations, 3 under the document's own id — ask describe for the rest |
Quickstart
export HANZO_API_KEY=sk-... # console.hanzo.ai → API keysThen the first call — a read that needs nothing but the key. GET /v1/marketplace, operation get_marketplace:
hanzo marketplace getimport { Configuration, MarketplaceApi } from 'hanzoai';
const api = new MarketplaceApi(new Configuration({ accessToken: process.env.HANZO_API_KEY }));
const { data } = await api.getMarketplace();from hanzoai.cloud import ApiClient, Configuration
from hanzoai.cloud.api import MarketplaceApi
client = ApiClient(Configuration(access_token=os.environ["HANZO_API_KEY"]))
result = MarketplaceApi(client).get_marketplace()cfg := hanzoai.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := hanzoai.NewAPIClient(cfg)
resp, _, err := client.MarketplaceAPI.GetMarketplace(context.Background()).Execute()
if err != nil {
return err
}use hanzo_client::apis::{configuration::Configuration, marketplace_api};
let mut cfg = Configuration::new();
cfg.bearer_access_token = std::env::var("HANZO_API_KEY").ok();
let result = marketplace_api::get_marketplace(&cfg, Default::default()).await?;import ai.hanzo.cloud.ApiClient;
import ai.hanzo.cloud.api.MarketplaceApi;
ApiClient client = new ApiClient();
client.setBearerToken(System.getenv("HANZO_API_KEY"));
var result = new MarketplaceApi(client).getMarketplace();curl https://api.hanzo.ai/v1/marketplace \
-H "Authorization: Bearer $HANZO_API_KEY"MCP reaches marketplace through the marketplace tool, which names its 23 operations with its own verbs — this one among them, under a name only MCP declares. describe explains any of them:
curl -X POST https://api.hanzo.ai/v1/mcp \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "describe",
"arguments": {
"op": "list_marketplaces"
}
}
}'Answers 200 with object — ok.
Endpoints
| Endpoint | What it does |
|---|---|
POST /v1/marketplace/install | Activates one tool for the caller's own org and project. |
POST /v1/marketplace/jobs/{id}/accept | Accepts a job the caller's org was hired for: the seller takes the work on and the clock toward its deadline is the seller's. |
POST /v1/marketplace/jobs/{id}/cancel | Takes back a job the caller's org opened, before the seller accepts it. |
POST /v1/marketplace/jobs/{id}/decline | Declines a job the caller's org was hired for, before any work. |
POST /v1/marketplace/jobs/{id}/deliver | Records delivery of a job the caller's org accepted, before its deadline, and starts the review window: the buyer releases or disputes within it, or it releases itself when it closes. |
POST /v1/marketplace/jobs/{id}/dispute | Stops a job for a ruling, as either party: before delivery (and before the deadline), or within the review window after it. |
POST /v1/marketplace/jobs/{id}/feedback | Rates the other party of a settled job — the seller when the caller's org bought, the buyer when it sold — once per party per job, and never edited. |
POST /v1/marketplace/jobs/{id}/refund | Refunds a job the caller's org accepted, as the seller, at any point before it is paid: nothing was moved, so the buyer's authorization is given up, never settled, and the amount it set aside returns to the buyer's wallet. |
POST /v1/marketplace/jobs/{id}/release | Releases a job the caller's org is paying for, paying the seller the whole amount: the buyer's authorization is settled on the rail, once, and the rail states the payment. |
GET /v1/marketplace/jobs/{id} | Reads one job the caller's org is a party to. |
GET /v1/marketplace/jobs | Lists the jobs the caller's org is a party to, newest first: as buyer, the work it hired — with the quotes it has not paid and the jobs being funded, each with the attempt that made it, so a hire whose answer was lost is found here — and as seller, the work it was hired for, which never includes another org's quote. |
POST /v1/marketplace/jobs | Hires another org for a piece of work, through a public listing or by a direct offer, and answers 201 with the job — open, waiting for the seller. |
PATCH /v1/marketplace/listings/{id} | Edits one of the caller org's listings — its copy, price, payout wallet, visibility and documentation — under the same rules publish applies, and answers the listing as it now stands. |
DELETE /v1/marketplace/listings/{id} | Withdraws one of the caller org's listings from the marketplace and answers 204. |
GET /v1/marketplace/listings | Returns the listings the caller's own org has published — what this org is offering, not what it can buy. |
POST /v1/marketplace/listings | Offers one thing the caller's org owns on the marketplace — an agent, persona, app, skill or MCP server, or, for the platform, a tool sold per call — optionally monetized. |
POST /v1/marketplace/seller/payout/verify | Binds the caller org's payout wallet: the signature over its outstanding challenge is recovered, and must recover to the wallet's address, before the challenge expires. |
POST /v1/marketplace/seller/payout | Starts binding one of the caller org's wallets as the wallet it is paid into: answers a challenge naming the org, the wallet and its address, to be signed with that wallet within fifteen minutes and sent to POST /v1/marketplace/seller/payout/verify. |
GET /v1/marketplace/seller | Answers where the caller's org stands as a seller, in one read: its founders' identity verification (KYC) and legal entity (KYB), its tax form and whether it is certified and valid, its own sanctions screening — only what an org may see about itself — the payout wallet it proved, the TaxPrincipalCredentials signed for its agents, what it earned in the year from the economic events the rails stated, and the 1099s payers furnished it. |
GET /v1/marketplace/shop/{id} | Reads one public listing as the shop shows it — its seller's public face, its reputation and the ways to buy it. |
GET /v1/marketplace/shop | Searches every public listing of every kind — agents and personas to hire, apps, skills, MCP servers and tools — newest first, with facets by kind, category, price and rating, paged. |
POST /v1/marketplace/uninstall | Deactivates one tool for the caller's own org and project, so it stops being dispatchable there. |
GET /v1/marketplace | Lists every tool and agent the caller can reach in their own org and project, enriched with any public listing's title, category and price, and with installed=true on the ones already activated for that scope. |