Skip to content
Hypamoon

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 role marketplace_admin.
  • A seller is a user of the app with the role seller who you invited as a seller. A seller only ever sees their own details, products, sub-orders and payouts. Asking for anything of another seller gives 404.
  • 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:

JSON
{
  "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

  1. An app with sign-in and the roles marketplace_admin and seller.
  2. A product collection (create_collection with role products). Approved products of active sellers appear there by themselves.
  3. Set up the marketplace: PUT /marketplaces/{app_id} with holder_country (where your business is established), commission (percentage_bp, basis points of each seller's subtotal, plus fixed_cents) and payout_moment (on_payment, on_shipment (the default) or after_shipment with days).
  4. Connect your own Stripe account with a restricted key: see below.
  5. Invite sellers: POST /marketplaces/{app_id}/sellers with the user_id of an app user with the role seller. Country and kind are checked against the rules first. The seller signs in and fills in their own details (PUT .../sellers/{id}/onboarding); then POST .../sellers/{id}/payments gives a link to Stripe's verification. A seller can sell once Stripe verified them.
  6. Sell: POST .../quote and POST .../checkout work over several sellers, with the rules of the delivery country. One payment, then one sub-order and one payout per seller.
  7. Ship, refund, payouts: per sub-order.
  8. 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}/payouts shows per seller what was paid out, what is waiting and what was taken back; the payout status of a sub-order is waiting, in_progress, paid_out, void or failed.
  • Shipped: PATCH .../suborders/{id} with shipped or delivered (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 same idempotency_key is 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}/reverse takes a payout back from the seller.
  • Stock: when an order is paid, the stock of 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 says stock_short in 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 " via Hypamoon"). The name is the app's, otherwise the project's.

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.

Hypamoon is the infrastructure your chat or agent builds on for a business. Hypamoon API, version 1.hypamoon.com