Start for free. Sign up without a card and you'll get a test project and a test key right away. Once your company (or, for a personal account, you) is verified, you can create a live key in the portal.
Marketplaces
A marketplace lets several sellers sell in one shop. Buyers pay once for products of several sellers; each seller gets their share, and you, the holder, keep a commission. You are the marketplace at your own payment provider: the money goes through your own Stripe account, and Hypamoon never holds it.
This page covers the API under /api/v1/marketplaces/{app_id} and the MCP tools. Payments on a normal shop are on the Payments page.
How it fits together
- The holder is you: a key or chat connection of the project (writing needs
project:write), or a user of the app with the rolemarketplace_admin. - A seller is a user of the app with the role
sellerwho you invited as a seller. A seller only ever sees their own details, products, sub-orders and payouts. Asking for anything of another seller gives404. - A buyer is anyone else using the app.
- One app, one marketplace. A test app has a test marketplace, a live app a live one. A test key never reaches a live marketplace.
Who you are follows from the key and the signed user headers of the app, never from the request body.
Rules per country
Rules that differ per country (who may sell spirits, the minimum age, reporting duties, whether a seller from another country is allowed) are data in Hypamoon's rule set. Every rule has an explanation, a source, a review date and a certainty:
| certainty | meaning |
|---|---|
zeker |
taken from the legal text |
waarschijnlijk |
likely: the source is not the legal text yet |
jurist |
a cautious default that a lawyer still has to confirm |
Unknown is closed: a restricted category without a rule for the country is refused. A hard rule always applies; a default rule you can turn off with a statement of your own (disabled_default_rules). You can always be stricter than the rule set.
GET /marketplaces/{app_id}/rules (or the tool get_marketplace_rules, also without a marketplace) explains the rules for a seller country, delivery country and category in plain words.
rule_blocked
When a rule refuses something, the answer is 422 with the error type rule_blocked:
{
"type": "https://hypamoon.com/docs/errors/rule_blocked",
"status": 422,
"detail": "A seller from this country cannot be admitted to this marketplace.",
"rule_id": "EU.holder.sellers_eu",
"explain": "A marketplace holder in the EU admits sellers from the EU-27 by default. ...",
"certainty": "zeker",
"refusals": [{ "code": "country_not_allowed", "rule_id": "EU.holder.sellers_eu", "explain": "...", "certainty": "zeker" }]
}rule_id, explain and certainty are those of the first rule that refused; refusals lists all of them (per product in a checkout). Nothing was stored or charged. The kinds of seller you admit count as a rule too, with the id holder.allowed_seller_kinds.
Step by step
- An app with sign-in and the roles
marketplace_adminandseller. - A product collection (
create_collectionwith roleproducts). Approved products of active sellers appear there by themselves. - Set up the marketplace:
PUT /marketplaces/{app_id}withholder_country(where your business is established),commission(percentage_bp, basis points of each seller's subtotal, plusfixed_cents) andpayout_moment(on_payment,on_shipment(the default) orafter_shipmentwithdays). - Connect your own Stripe account with a restricted key: see below.
- Invite sellers:
POST /marketplaces/{app_id}/sellerswith theuser_idof an app user with the roleseller. Country and kind are checked against the rules first. The seller signs in and fills in their own details (PUT .../sellers/{id}/onboarding); thenPOST .../sellers/{id}/paymentsgives a link to Stripe's verification. A seller can sell once Stripe verified them. - Sell:
POST .../quoteandPOST .../checkoutwork over several sellers, with the rules of the delivery country. One payment, then one sub-order and one payout per seller. - Ship, refund, payouts: per sub-order.
- Check:
GET .../readiness.
Connecting your Stripe account
You are the marketplace at Stripe. In your Stripe Dashboard you turn on Connect and create a restricted key (rk_test_ for a test marketplace, rk_live_ for a live one) with these permissions:
| Resource | Permission | Stripe permission | Why |
|---|---|---|---|
| Connect: Accounts (v2) | Write | v2_account_storer_write |
create seller accounts, their Stripe onboarding links, and read their verification |
| Connect: Accounts | Read | connected_account_read |
read your own account id and mode |
| Connect: Transfers | Write | transfer_write |
pay each seller, and reverse a transfer on a refund or dispute |
| Checkout Sessions | Write | checkout_session_write |
one payment per order on your account |
| PaymentIntents | Read | payment_intent_read |
read the payment after checkout |
| Charges | Write | charge_write |
refund one sub-order, and pay sellers from that charge |
| Disputes | Read | dispute_read |
see which seller a dispute is about |
| Webhook Endpoints | Write | webhook_write |
send payment events and seller verification events (event destinations) to Hypamoon |
| Events | Read | event_read |
fetch an event after a notification |
Stripe has no separate permission for refunds, account links or event destinations: they fall under Charges, Accounts (v2) and Webhook Endpoints. The Stripe permission is the id in Stripe's permissions reference.
GET /marketplaces/{app_id}/payments lists them exactly as required_permissions. A secret key (sk_) or publishable key (pk_) is refused.
With the API: PUT /marketplaces/{app_id}/payments with { "restricted_key": "rk_test_..." }, from your own machine with a project key; never from inside the app.
In a chat: connect_marketplace_payments never takes the key. It gives a one-time link to the Hypamoon vault where you paste it; the next call moves it from the vault to the marketplace once and removes it from the vault. Never paste a key in a chat.
The key is stored encrypted and never returned. We add a webhook endpoint and an event destination on your account for the events of the marketplace.
Money per sub-order
- Commission is taken from each seller's subtotal; shipping goes to the seller.
- Payouts follow
payout_moment.GET /marketplaces/{app_id}/payoutsshows per seller what was paid out, what is waiting and what was taken back; the payout status of a sub-order iswaiting,in_progress,paid_out,voidorfailed. - Shipped:
PATCH .../suborders/{id}withshippedordelivered(and tracking). The holder or the seller of that sub-order. - Refund:
POST .../suborders/{id}/refund, only the holder, at most what is left on that sub-order. The buyer gets the money from your Stripe account; the seller's share is taken back (a transfer reversal, or less to pay out). The sameidempotency_keyis the same refund. If the seller's balance is too low, the buyer still gets the money and you carry the difference. - Dispute: payouts on that order wait while the dispute is open;
POST .../suborders/{id}/reversetakes a payout back from the seller. - Stock: when an order is paid, the
stockof each product goes down by what was bought in that seller's sub-order, once and never below zero. If there was less stock than ordered, the sub-order saysstock_shortin its webhook and the seller's email says so. A sub-order that is refunded in full before it is shipped puts the stock back, once. The product collection follows.
From a chat, a refund on a sub-order and taking a payout back wait for your confirmation in the Hypamoon portal; with an API key they go directly. Neither is available in Claude or ChatGPT. In Claude, get_refund_link with suborder_id gives a link to that sub-order in the portal, where you check the amount and confirm the refund yourself.
Emails and webhooks
When an order is paid, each seller gets an email with their part: what to ship, the shipping method and the delivery address (not the buyer's email address). The buyer gets one confirmation for the whole order, with per seller who sells and ships it, and the notices the rules ask for. Each email goes once.
The holder chooses who sends the buyer's confirmation with emails on configure_marketplace (PUT /marketplaces/{app_id}): buyer is default (Hypamoon's confirmation), template (your own template from create_email_template, id in templates.buyer, with {{blocks.order}}, {{order.number}}, {{order.total}} and {{marketplace.name}}; without {{blocks.order}} that block is added under it, because the buyer must see who sells each part) or off (Hypamoon sends nothing and the webhook marketplace_order.confirmation carries the buyer's email address and the default email, so you send it; the answer warns that the confirmation is then yours).
seller works the same for the emails to sellers: default (Hypamoon's: the new order, and a suspension or reactivation with the reason), template (your template for the order email, id in templates.seller, with {{blocks.order}}, {{order.number}}, {{suborder.number}}, {{suborder.total}}, {{seller.name}} and {{marketplace.name}}; without {{blocks.order}} the block is added under it; suspension emails stay the default) or off (Hypamoon sends sellers nothing; use suborder.paid, and when you suspend a seller, tell them the reason yourself, which the answer warns about).
All these emails go in the language of the app's site (Dutch or English; another language gets English), with replies to the project's contact address, and from the project's own verified sending domain when it has one (otherwise from Hypamoon's address as "
Subscribe a webhook endpoint (create_webhook) to:
| Event | When |
|---|---|
suborder.created, suborder.paid |
an order was paid: one of each per seller, in that order |
suborder.shipped |
a sub-order was marked shipped or delivered, once |
suborder.refunded |
a refund on a sub-order, once per refund, with data.refund |
marketplace_order.confirmation |
only with emails.buyer: off: the buyer's confirmation to send yourself, with data.order and data.email (to, variables, default) |
data.suborder is the sub-order (lines, amounts, status, tracking, payout status) and data.marketplace the app; never the buyer's name, address or email. Only marketplace_order.confirmation carries the buyer's email address, because you chose to send the confirmation yourself.
Suspending a seller
suspend_seller (POST .../sellers/{id}/suspend, only the holder) suspends a seller right away. A reason is required: the seller gets it by email (unless emails.seller is off, then you tell them), as the EU platform-to-business rules ask. until is optional, the date in that email. A suspended seller does not appear in the quote or the checkout and their products leave the product collection; sub-orders that were already paid stay theirs to ship and are paid out as usual. reinstate_seller (POST .../sellers/{id}/reinstate) lifts it.
The marketplace fee
Hypamoon charges a marketplace fee per paid order on your balance. The amount is in the terms (article 12) and under marketplaces in the pricing of the API reference (x-hypamoon-pricing); check_readiness names it too. A test marketplace costs nothing. A live marketplace opens only after the owner of the project agreed to the current fee in the portal (Payments, Marketplace; only the owner can). If the fee goes up later, the owner agrees again before a live marketplace takes new orders; an order is never charged more than the owner agreed to.
MCP tools
| Tool | Does |
|---|---|
configure_marketplace |
holder country, commission, payout moment, kinds of seller, review of products, status |
connect_marketplace_payments |
your own Stripe account, through the vault |
invite_seller |
an app user with the role seller becomes a seller, after the rules |
list_sellers, get_seller |
sellers with onboarding, credentials and verification |
suspend_seller, reinstate_seller |
suspend a seller with a reason, and lift it |
create_seller_onboarding_link |
a fresh link to Stripe's verification |
list_suborders |
sub-orders per seller or per order |
update_order_status |
with app_id and suborder_id: shipped or delivered |
refund_order |
with app_id and suborder_id: refund one sub-order |
get_refund_link |
with suborder_id: a link to refund one sub-order in the portal |
get_payouts |
payouts per seller |
reverse_seller_transfer |
take a payout back after a dispute |
get_marketplace_rules |
the rules per country, in plain words |
check_readiness |
with marketplace_app_id: connection, terms, the owner's agreement, sellers, rules |
The recipe marketplace (get_recipe) walks a chat through all of it in test mode: two sellers, one purchase over both, one refund.
Readiness
GET /marketplaces/{app_id}/readiness (only the holder) returns ready and five checks, each with ok, detail and where useful next_tool:
payments_connected: your Stripe account is connected, in the mode of the marketplace;terms_published: the terms for the marketplace fee are published (Hypamoon's side);fee_agreed: the owner of the project agreed to the marketplace fee in the portal (always ok for a test marketplace, with the link for later);sellers_verified: there are sellers, and every one that is not closed is active and verified;holder_rules: a marketplace can open in your country, with your tax roles and reporting duties, and whether a rule is past its review date.
It is advice: it blocks nothing.
Routes
| Method and path | Who |
|---|---|
GET /marketplaces/{app_id} |
everyone (sellers and buyers see only country and status) |
PUT /marketplaces/{app_id} |
holder |
GET /marketplaces/{app_id}/rules |
everyone |
GET, POST /marketplaces/{app_id}/sellers |
holder (a seller sees themself) |
GET, PATCH /marketplaces/{app_id}/sellers/{id} |
holder, a seller only themself |
PUT .../sellers/{id}/onboarding, PUT .../sellers/{id}/tin |
the seller themself |
PATCH .../sellers/{id}/credentials/{type} |
holder |
GET, PUT .../sellers/{id}/shipping |
holder or the seller themself |
POST .../sellers/{id}/payments |
holder or the seller themself |
POST .../sellers/{id}/suspend, POST .../sellers/{id}/reinstate |
holder |
GET, POST /marketplaces/{app_id}/listings, GET, PATCH .../listings/{id} |
per role |
GET, PUT, DELETE /marketplaces/{app_id}/payments |
holder (writing only with a project key) |
POST /marketplaces/{app_id}/quote, POST .../checkout |
everyone |
GET .../orders/{id} |
holder |
GET .../suborders, GET, PATCH .../suborders/{id} |
holder, a seller only their own |
POST .../suborders/{id}/refund, POST .../suborders/{id}/reverse |
holder |
GET .../payouts |
holder, a seller only their own |
GET .../readiness |
holder |
Reading needs data:read, writing data:write; the holder writes only with project:write or as marketplace_admin.