Reference

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:

headermeaning
x-ratelimit-limityour per-minute ceiling
x-ratelimit-remainingrequests left in the window
x-ratelimit-resetunix 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 a secret): 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

envpurpose
KV_REST_API_URL / KV_REST_API_TOKENUpstash/Vercel KV (or UPSTASH_REDIS_REST_URL / _TOKEN) — enables limits/keys/webhooks
MARKETPLACE_ADMIN_SECRETBearer for issuing keys + triggering webhook dispatch
MARKETPLACE_ANON_RPManonymous requests/min (default 30)

Without KV configured, all of the above no-op and the API stays fully open.