Marketplace
Package marketplace is the shop for tools and agents: browse, install into your project, publish your own free or priced.
Package marketplace is the shop for tools and agents: browse, install into your project, publish your own free or priced.
| Base URL | https://api.hanzo.ai |
| Operations | 6 |
| 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 | 6 operations |
| CLI | hanzo marketplace … | 6 of 6 |
| SDK | MarketplaceApi in every published client | 6 methods |
| MCP | tool marketplace on https://api.hanzo.ai/v1/mcp | 6 operations, 1 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 := cloud.NewConfiguration()
cfg.AddDefaultHeader("Authorization", "Bearer "+os.Getenv("HANZO_API_KEY"))
client := cloud.NewAPIClient(cfg)
resp, _, err := client.MarketplaceAPI.GetMarketplace(context.Background()).Execute()
if err != nil {
return err
}use hanzo_cloud::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.setRequestInterceptor(b -> b.header("Authorization", "Bearer " + System.getenv("HANZO_API_KEY")));
var result = new MarketplaceApi(client).getMarketplace();curl https://api.hanzo.ai/v1/marketplace \
-H "Authorization: Bearer $HANZO_API_KEY"Tool marketplace, op get_marketplace — POST the JSON-RPC envelope to https://api.hanzo.ai/v1/mcp.
curl -X POST https://api.hanzo.ai/v1/mcp \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "marketplace",
"arguments": {
"op": "get_marketplace",
"input": {}
}
}
}'Answers 200 with object — ok.
Endpoints
| Endpoint | What it does |
|---|---|
POST /v1/marketplace/install | Install activates one tool for the caller's own org and project. |
DELETE /v1/marketplace/listings/{id} | Unpublish 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 | Publish offers one tool on the marketplace, optionally monetized. |
POST /v1/marketplace/uninstall | Uninstall deactivates one tool for the caller's own org and project, so it stops being dispatchable there. |
GET /v1/marketplace | Discover 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,… |
How is this guide?