Skip to content
Zuuppa
Dashboard

API Reference

Marketplace API

A marketplace takes payments for its sellers and settles them directly to each seller's wallet, keeping a cut in the same on-chain transaction — never holding a seller's funds. This covers the /marketplace/* surface and the on-behalf-of payment flow.

Base URL: https://api.zuuppa.com. Read the seller model and fee split first.

Becoming a marketplace#

A business becomes a marketplace by applying from the dashboard and being approved (a platform-operator decision). Once approved, your existing sk_ key gains the /marketplace/* surface and the ability to take on-behalf-of payments. There is no API to self-approve.

Authentication#

Auth: Authorization: Bearer sk_... from a business with an active marketplace capability. A key from an ordinary business gets 404 on every route here, so the surface is invisible to accounts that don't have the capability.

Warning

If the capability is revoked

Reads stay open (your app doesn't black out and history stays visible), but every write answers 409 this marketplace has been disabled; contact support. Payments already in flight still settle, and a seller's money still reaches the seller.

The seller model#

A seller is an account a marketplace takes payments for. It has no login, no dashboard, and no API key. Its payout address is set and changed by the marketplace, and its webhook events go to the marketplace's URL signed with the marketplace's secret.

Taking a payment for a seller#

Create an ordinary intent with one extra header and one extra requirement:

POST
Marketplace header

An ordinary POST /intents plus the X-Zuuppa-On-Behalf-Of header naming a seller you own.

cURL
curl -X POST https://api.zuuppa.com/intents \
  -H 'Authorization: Bearer sk_live_...' \
  -H 'X-Zuuppa-On-Behalf-Of: <seller_id>' \
  -H 'Content-Type: application/json' \
  -d '{
        "amount_usd_cents": 10000,
        "accepted_tokens": [{ "kind": "sol" }],
        "application_fee_bps": 300,
        "customer": { "email": "buyer@example.com" },
        "reference": "mkt-order-77"
      }'

Three differences from taking your own payment:

  • customer.email is required — a create without it is a 400. A seller has no login and the platform sends no email.
  • The settlement splits three ways in one transaction: the seller's share to the seller's wallet, your cut to yours, the platform fee to the platform. Marketplace splitting is SOL only today.

Everything downstream is unchanged: the same client_secret, the same payment_uri, the same /status reads. Webhooks arrive at your endpoint, signed with your secret, and carry account.is_seller: true so you know whose payment it was.

The cut rate (application_fee_bps, 0–5000) is resolved once at create time and frozen onto the intent as marketplace_fee_bps: the request's application_fee_bps, else the seller's marketplace_fee_bps override, else the marketplace's default_fee_bps. The cap is 5000 bps. Any bad X-Zuuppa-On-Behalf-Of value answers a uniform 404 seller not found.

Endpoints#

All routes are under /marketplace. Reads need marketplace-view access, writes need marketplace-manage access on an active capability.

MethodPathWhat it does
GET/marketplaceThe capability, its configuration, and the seller headcount.
PATCH/marketplaceChange default_fee_bps and/or fee_wallet.
GET/marketplace/sellersOne page of sellers, newest first.
POST/marketplace/sellersCreate a seller. Idempotent on external_id.
GET/marketplace/sellers/:idOne seller.
PATCH/marketplace/sellers/:idEdit a seller.
DELETE/marketplace/sellers/:idArchive a seller. Never deletes.
GET/marketplace/paymentsEvery payment across the marketplace's sellers.
GET/marketplace/payments/:idOne payment, with its settlement legs.
GET/marketplace/stats/summaryVolume, fees, and counts for a range.
GET/marketplace/stats/timeseriesThe same numbers bucketed over time.
GET/marketplace/auditThe marketplace's own audit trail.
GET
sk_ API key

Returns the capability and its configuration: default_fee_bps, fee_wallet, seller_limit, sellers_count, and sellers_total.

seller_limit is null for no limit (the normal state). sellers_total counts archived sellers and sellers_count does not — any limit is charged against the total, because an archived seller keeps its derivation index (and so its deposit addresses). Archiving a seller does not buy back a slot.

PATCH
sk_ API key

Both optional, but at least one is required.

default_fee_bpsintegerOptional
The rate applied to sellers with no override, 0–5000.
fee_walletstringOptional
Where the cut lands. "" clears it, falling back to the marketplace account's own sweep_destination.

Errors: 400 nothing to update; 400 fee_wallet is not a valid Solana address; 400 on an out-of-range rate; 409 if the capability is disabled.

POST
sk_ API key

Idempotent on external_id. The response carries the seller object plus a created flag (so an idempotent repeat is distinguishable from a fresh create).

Request body
namestringOptional
The seller's display name. This is the label a buyer sees in payment_uri.
descriptionstringOptional
Optional free text.
external_idstringOptional
Your own id. Also the idempotency key: posting the same one twice returns the same seller.
sweep_destinationstringOptional
Where this seller's money goes. Optional at creation; POST /intents refuses a payment for a seller that still has none.
marketplace_fee_bpsintegerOptional
Per-seller rate override, 0–5000. Omit to inherit default_fee_bps.
metadataobjectOptional
Opaque JSON you keep on the seller. Echoed back verbatim.
The seller object
idstring
The seller id. This is what goes in X-Zuuppa-On-Behalf-Of.
name, description, logo_urlstring
Presentation. logo_url is null unless object storage is configured.
external_idstring
Your id for it.
sweep_destinationstring | null
Its payout address, or null.
marketplace_fee_bpsinteger | null
Its override, or null.
effective_fee_bpsinteger
The rate that will actually apply: the override, else the marketplace default.
metadataobject
Your opaque JSON.
account_indexinteger
Its BIP-44 account level (ties a seller to its on-chain deposit addresses when reconciling).
created_at, archived_at, archived_reasontimestamp
Lifecycle.

Errors: 400 on a missing name, bad sweep_destination, or an out-of-range rate; 409 when the capability is disabled, or a set seller_limit is reached; 429 when creating faster than the rate limit allows.

Note

Rate limit

Nothing caps how many sellers you may have; what's bounded is how fast you create them (300 per 10 seconds by default). Exceeding it is a 429 whose body carries retry_after_secs; the same request succeeds once the window rolls forward. An idempotent hit on external_id is resolved before the rate limit, so retries are free.
GET
sk_ API key

One page of sellers, newest first.

Query params: limit, cursor (keyset pagination), search (case-insensitive substring over name and external_id), include_archived (off by default).

PATCH
sk_ API key
namestringOptional
descriptionstringOptional
"" clears it.
sweep_destinationstringOptional
The field that moves money. Cannot be blanked; once set, it can only be repointed to another valid address.
marketplace_fee_bpsintegerOptional
The override.
clear_fee_bpsbooleanOptional
true removes the override (falls back to the marketplace default).
metadataobjectOptional
clear_metadatabooleanOptional
true removes it.
Important

You are responsible for where payouts land

You may repoint your sellers' payouts. The server verifies the address is well-formed and records who changed it, when, and from which credential and IP. It does not, and cannot, verify that the address belongs to the seller.

Each dimension changed writes its own audit row: seller.payout_changed, seller.fee_changed, seller.updated.

DELETE
sk_ API key

Archives the seller — never deletes.

Archives the seller: it stops accepting new payments and drops out of the live roster, while its row, payment history, and derivation indexes all survive. Nothing is deleted. Archival emits no webhook — treat your own DELETE call as the event.

Errors: 409 with an intent_ids list while any of that seller's money is still in flight. Settle or resolve those payments first.

GET
sk_ API key

Query params: limit, cursor (newest first), seller_id, status (comma-separated, e.g. swept,settle_failed), search (over reference and buyer name/email), from/to (RFC 3339 bounds on created_at, from inclusive, to exclusive).

200 OKGET /marketplace/payments
JSON
{ "payments": [ /* ... */ ], "next_cursor": "..." }

Each payment is the full PaymentIntent JSON plus ticker (the pay-in asset's display symbol), seller_account_id, and seller ({ id, name, external_id } or null).

One payment, with its settlement legs broken out.

The _ui fields here are floats for display; the exact decimal strings live on the webhook payload.

Volume, fee revenue, and payment counts over a range.

Also GET /marketplace/stats/timeseries for the same numbers bucketed over time. Aggregated across every seller or narrowed with seller_id. A seller_id is resolved against your marketplace first, so it can never read another marketplace's numbers.

GET
sk_ API key

Your own audit trail, newest first.

Query params: limit, cursor, seller_id, action.

ActionWritten when
marketplace.updatedPATCH /marketplace
seller.createda seller is created
seller.updatedname, description, or metadata changed
seller.payout_changedsweep_destination changed
seller.fee_changedthe rate override changed
seller.archiveda seller is archived
payment.createda payment is taken for a seller

© 2026 Zuuppa. All rights reserved.