---
title: "Marketplaces · Hypamoon API docs"
description: "Several sellers in one shop: one checkout, a sub-order and a payout per seller, your commission, and rules per country, on your own payment account."
url: https://hypamoon.com/docs/marketplaces
---

**Start for free.** [Sign up](https://app.hypamoon.com/?mode=signup&utm_source=docs&utm_campaign=banner) 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](https://docs.stripe.com/keys/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`.

[PreviousPayments](https://hypamoon.com/docs/payments) [NextTest mode](https://hypamoon.com/docs/test-mode)

Hypamoon is the infrastructure your chat or agent builds on for a business. Hypamoon API, version 1.[hypamoon.com](https://hypamoon.com/)
