Rate limits, API keys & webhooks
The marketplace API is open by default. A host can optionally enable rate limiting, API keys, and webhooks by wiring KV (Upstash / Vercel KV). Until then everything stays fully open with no limits, no keys required.
#Rate limits
When KV is configured, every response carries:
| header | meaning |
|---|---|
x-ratelimit-limit | your per-minute ceiling |
x-ratelimit-remaining | requests left in the window |
x-ratelimit-reset | unix seconds when the window resets |
Over the limit → 429 with a retry-after header. Anonymous callers are limited per IP
(MARKETPLACE_ANON_RPM, default 30/min); presenting an API key raises the limit to the key's
ratePerMin.
#API keys (optional — raise limits + attribute usage)
A key is not required (the API is keyless). It only (a) lifts your rate limit and (b)
attributes the listings/usage you bring to your integrator. Keys are issued by the host
operator (admin):
curl -X POST $SUP/api/adaptors/keys \
-H "authorization: Bearer $MARKETPLACE_ADMIN_SECRET" \
-d '{ "integrator": "acme", "ratePerMin": 600, "label": "Acme prod" }'
# → { "key": "mk_…", "integrator": "acme", "ratePerMin": 600 }
Send the key as x-api-key: mk_… (or Authorization: Bearer mk_…). When you call draft
with a key, the resulting listing is auto-attributed to your integrator (unless you pass an
explicit integrator in the body).
#Webhooks
Subscribe to on-chain registry events and get them pushed to your endpoint.
#Register
curl -X POST $SUP/api/adaptors/webhooks \
-H "x-api-key: mk_…" \
-d '{
"url": "https://you.app/hooks/sup",
"events": ["Registered", "Updated", "Unlisted", "Attested"],
"secret": "whsec_…"
}'
# → { "id": "wh_…", "url": "…", "events": [ … ] }
Requires an API key (or the admin secret).
#Delivery
Each event is POSTed to your url with:
- header
x-sup-event: the event name - header
x-sup-signature(if you set asecret):HMAC-SHA256(secret, rawBody)as hex - body:
{ network, event, data, timestampMs, id }
Verify the signature before trusting the payload:
import crypto from "node:crypto";
const expected = crypto.createHmac("sha256", secret).update(rawBody).digest("hex");
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.headers["x-sup-signature"]));
#Dispatch (host side)
Delivery runs on the host's schedule — it polls new registry events since a stored cursor and fans them out. Wire it as a cron (Vercel example):
{ "crons": [{ "path": "/api/adaptors/webhooks/dispatch?network=testnet", "schedule": "*/2 * * * *" }] }
The dispatch route is admin/cron-gated.
#Host environment
| env | purpose |
|---|---|
KV_REST_API_URL / KV_REST_API_TOKEN | Upstash/Vercel KV (or UPSTASH_REDIS_REST_URL / _TOKEN) — enables limits/keys/webhooks |
MARKETPLACE_ADMIN_SECRET | Bearer for issuing keys + triggering webhook dispatch |
MARKETPLACE_ANON_RPM | anonymous requests/min (default 30) |
Without KV configured, all of the above no-op and the API stays fully open.