Marketplace Accounting: Orders, Commissions, Payouts

A marketplace order is one payment with three owners. The buyer pays 100.00; 85.00 of it belongs to the seller, 15.00 is your commission, and for a few days all 100.00 sits in your account. If your books say "we received 100.00 of revenue," they're wrong — you earned 15.00 and you owe 85.00. Get this split wrong at recording time and every downstream number (your revenue, your liabilities, what each seller is owed) is wrong too, and you'll be rebuilding it in spreadsheets at month-end.

This guide records the split correctly, as it happens, using journal entries: one balanced journal per order, one per payout, and per-seller balances you can read straight back from the ledger.

What you need:

  • A sandbox company with API keys — if you haven't done the 20-minute guide, do steps 1–2 of it first; this guide assumes a company exists and you've seen its chart of accounts
  • Your marketplace's own order ids — journals here will reference them, so the ledger and your order database stay joinable

Same headers as before:

Shell
export ACCESS_TOKEN="<from the credential exchange>"
export BASE="https://api-sandbox.paprel.com"
export AUTH="authorization: Bearer $ACCESS_TOKEN"
export CT="content-type: application/json"

First time? The five-minute setup — token exchange and request signing — is on the Authentication page. The curl examples below show the auth header only; write requests also require an x-data-signature header, which the SDK computes for you.


Step 1 — Set up the accounts

You need three accounts beyond the seeded defaults. Read the existing chart first (GET /v1/accounting/accounts) to pick parents and matching kind/account_subtype values, then create:

A payment clearing account (asset) — where buyer money lands before your PSP settles it to your bank:

Shell
curl -X POST $BASE/v1/accounting/accounts \
  -H "$AUTH" -H "$CT" \
  -d '{
    "account": "Payments Clearing",
    "kind": "…",
    "account_subtype": "…",
    "currency": "USD",
    "is_sub_account": true,
    "parent_id": "…"
  }'

Endpoint reference: POST /v1/accounting/accounts

A seller payable account (liability) — money you hold that belongs to sellers:

Shell
curl -X POST $BASE/v1/accounting/accounts \
  -H "$AUTH" -H "$CT" \
  -d '{
    "account": "Seller Payables",
    "kind": "…",
    "account_subtype": "…",
    "currency": "USD",
    "is_sub_account": true,
    "parent_id": "…"
  }'

Endpoint reference: POST /v1/accounting/accounts

A commission revenue account (income) — the only part of the order that's actually yours. Same shape, income branch.

You can create one "Seller Payables" sub-account per seller (the liability tree stays readable in reports), or keep a single payable account and segment by seller through journal metadata and line-level people_id — step 2 shows both hooks. Start with one account per seller if you have tens of sellers; prefer the metadata route at thousands.

Also register each seller as a party:

Shell
curl -X POST $BASE/v1/clients \
  -H "$AUTH" -H "$CT" \
  -d '{
    "company_name": "Nakamura Ceramics",
    "company_email": "ops@nakamura.example",
    "currency": "USD"
  }'

Endpoint reference: POST /v1/clients

Keep the returned people_id — journal lines can carry it, which is what makes "show me everything for this seller" a query instead of a reconciliation.

Behind the scenes: the seller payable account is the load-bearing piece. It's a liability, so holding seller money never inflates your revenue or looks like your cash to spend — the balance sheet shows exactly how much of "your" bank balance isn't yours.

Step 2 — Record an order as one balanced journal

A buyer pays 100.00 for an order from Nakamura Ceramics; your commission is 15%. Record the whole economic event as a single journal:

Shell
curl -X POST $BASE/v1/accounting/journals \
  -H "$AUTH" -H "$CT" \
  -d '{
    "date": "2026-06-12",
    "currency": "USD",
    "reference": "ORD-2026-58231",
    "description": "Order ORD-2026-58231 — Nakamura Ceramics",
    "posted": true,
    "lines": [
      {
        "account_code": "<Payments Clearing code>",
        "account_name": "Payments Clearing",
        "debit": "100.00",
        "description": "Buyer payment, gross"
      },
      {
        "account_code": "<Seller Payables code>",
        "account_name": "Seller Payables",
        "credit": "85.00",
        "people_id": "<seller people_id from step 1>",
        "description": "Net owed to seller"
      },
      {
        "account_code": "<Commission Revenue code>",
        "account_name": "Commission Revenue",
        "credit": "15.00",
        "description": "Platform commission, 15%"
      }
    ],
    "meta_data": {
      "root_entity_type": "marketplace_order",
      "root_entity_id": "ORD-2026-58231"
    }
  }'

Endpoint reference: POST /v1/accounting/journals

The response returns the created journal. Two fields here are doing structural work:

  • meta_data.root_entity_type / root_entity_id tie the journal to your order id. This is the join key between your marketplace database and the ledger — step 4 queries by it directly.
  • people_id on the payable line attributes that slice of the liability to a specific seller.

Behind the scenes: the journal that posted:

AccountDebitCredit
Payments Clearing100.00
Seller Payables85.00
Commission Revenue15.00

One event, three truths, and they're forced to agree: debits equal credits or the posting is rejected. Your P&L gains exactly 15.00 of revenue — not 100.00 — and the 85.00 shows up where it belongs, as a liability. If your order pipeline retries the request after a timeout, resubmitting the same request returns the original result rather than double-posting the order.

This pattern scales by repetition: every order is the same three-line shape with different amounts and a different root_entity_id. Refunds are the same journal with the legs reversed.

Step 3 — Pay the seller out

Payout day. You transfer 85.00 to Nakamura Ceramics and record the second journal — debit the payable (the debt shrinks), credit cash:

Shell
curl -X POST $BASE/v1/accounting/journals \
  -H "$AUTH" -H "$CT" \
  -d '{
    "date": "2026-06-19",
    "currency": "USD",
    "reference": "PAYOUT-2026-0142",
    "description": "Weekly payout — Nakamura Ceramics",
    "posted": true,
    "lines": [
      {
        "account_code": "<Seller Payables code>",
        "account_name": "Seller Payables",
        "debit": "85.00",
        "people_id": "<seller people_id>"
      },
      {
        "account_code": "<bank/cash account code>",
        "account_name": "Cash · Operating",
        "credit": "85.00"
      }
    ],
    "meta_data": {
      "root_entity_type": "marketplace_payout",
      "root_entity_id": "PAYOUT-2026-0142"
    }
  }'

Endpoint reference: POST /v1/accounting/journals

Behind the scenes:

AccountDebitCredit
Seller Payables85.00
Cash · Operating85.00

The seller's payable balance drops to zero, cash drops by what you actually sent, and your revenue is untouched — it was earned at order time, not payout time. A real weekly payout batches many orders into one journal: one debit line per seller (or one total against their payable sub-account), one credit against cash, still balanced.

Step 4 — Read balances back

The point of recording orders this way is that the answers are now reads, not reconstructions. Pull every journal tied to a specific order:

Shell
curl "$BASE/v1/accounting/journals/for-entity?entity_type=marketplace_order&entity_id=ORD-2026-58231" \
  -H "$AUTH"

Endpoint reference: GET /v1/accounting/journals/for-entity

That's the full ledger history of one order — the split at sale time and, once you adopt the same metadata on refunds, those too. For "what do we owe seller X right now," filter the journal list by the payable account (GET /v1/accounting/journals supports account and date_range query params) and the per-seller sub-account or people_id segmentation from step 1 — credits minus debits on that slice is the live balance. And the balance sheet's Seller Payables line is the total you're holding across all sellers, always current, because it's derived from these same rows.

Two models: ledger accounts vs. company-per-seller

Everything above keeps all sellers inside your company's books — sellers are parties and payable balances, not accounting entities. That's the right default: one set of books, cheap to operate, and per-seller answers come from account structure and metadata.

The heavier model: create a separate company per seller (POST /v1/company, exactly as in the 20-minute guide) so each seller gets fully isolated books — their own chart of accounts, their own P&L and balance sheet, their own audit trail. Choose this when sellers are real businesses who need their own financial statements out of your platform — when "embedded accounting for your sellers" is part of the product you're selling, not just internal bookkeeping. The tradeoff is operational: every order now writes to two ledgers (yours records the commission and payable; the seller's records their sale), you manage N tenants instead of one, and cross-seller reporting becomes aggregation across companies rather than one query. Don't start here; migrate the sellers who outgrow the payable model.


Where next

  • Build Embedded Accounting in 20 Minutes — the prerequisite flow: company, chart of accounts, invoices, payments
  • API reference — exact request and response schemas for journals, accounts, and clients
  • Pricing — model your order volume; one journal per order plus one per payout batch is the whole write load
Evaluate Paprel

Build in sandbox, launch with a production trial

Use sandbox for developer testing with no billing. When you are ready for real workflows, start production on a monthly plan with a 14-day free trial.

API-First Delivery
Audit-Ready Controls
Sandbox And Guided Rollout
We use cookies to improve your experience. Manage preferences or accept all.