Download OpenAPI specification:
Read your monthly statements, sales, refunds, payouts and stock - for your accounting and royalty software.
Part of the 8Merch API documentation - Services > Ecommerce.
When 8Merch runs your store, we sell and ship your merch, keep our commission and pay out your share every month on a statement. This API gives you the same figures as the Statements tab of your portal, in a form your accounting software - or the spreadsheet you split artist royalties with - can read directly. It is especially useful if you run several stores with us: one key can read all of them.
The API is read-only. Every endpoint documented here is live at https://api.8merch.com/ecommerce/v1.
1. Get a key. Keys are issued by 8Merch - ask your account manager. A key can read all your stores (including ones you connect later) or only the ones you choose. You receive it once; keep it on your server, never in a browser or app. Every request sends it:
curl https://api.8merch.com/ecommerce/v1/account \
-H "Authorization: Bearer $EIGHTMERCH_ECOMMERCE_KEY"
2. List your statements for a period - one per store per month:
curl "https://api.8merch.com/ecommerce/v1/statements?period_from=2026-07&period_to=2026-09" \
-H "Authorization: Bearer $EIGHTMERCH_ECOMMERCE_KEY"
3. Read one in full - totals, sales by product, refunds, extra costs and the payout:
GET /statements/{id}. Its orders, each with its own figures, are at GET /statements/{id}/orders.
4. Or pull a whole quarter at once across every store: GET /sales?period_from=2026-07&period_to=2026-09
gives one row per product per store per month, with the product's vendor (normally the artist) on
each row - ready to group for royalties. GET /refunds is its counterpart for refunds.
5. Reconcile payouts with GET /payouts: what we owe or have paid for each statement.
Every figure is frozen when the statement is generated and never changes afterwards. Amounts are
in the statement's currency (the currency of the region the store sells from), rounded to 2 decimals.
| Line | Meaning |
|---|---|
gross_value |
What customers paid for the goods after discounts, including VAT. Shipping is not included. |
vat_amount |
VAT within gross_value. |
net_value |
gross_value - vat_amount. |
cost_of_goods |
What the goods cost to make, at the cost agreed for each product. |
refunded_net |
Refunds deducted on this statement (net of VAT). |
extra_costs_before_commission |
Agreed costs (freight, artwork, samples...) shared before our commission. |
profit |
net_value - cost_of_goods - refunded_net - extra_costs_before_commission |
commission |
8Merch's share of profit. |
extra_costs_after_commission |
Agreed costs that come out of your share only. |
payout |
profit - commission - extra_costs_after_commission - what you are paid. |
Which month an order belongs to. A sale is on the statement for the month it was fulfilled (shipped), not the month it was ordered. A refund is on the statement for the month it was made, once its order has been fulfilled - a refund made before shipping waits for the statement of the month the order ships.
Extra costs that are bigger than the payout are part-deducted and the rest is carried to the next
statement. Each extra cost line shows amount (deducted here), original_amount and carried_amount.
Sales by product. Inside an order with several products, the order's frozen net value, VAT and cost of goods are shared across its products in proportion to price and product cost. The per-product lines always add up to the order's figures, and the orders always add up to the statement.
When statements appear. A statement is visible here once 8Merch has reviewed and approved it -
the same moment it appears in your portal. A month with no sales and no costs still gets a statement,
with status no_activity and every figure zero.
Each statement belongs to one store (store, store_name). Filter any list with store=st_..., or
leave it out to read every store your key covers (GET /stores lists them).
For royalties, GET /sales is the endpoint you want. Every row carries product_vendor - the vendor
field of the product in the store, which is normally the artist or brand - and you can filter to one
with product_vendor=.... Subtract that artist's rows from GET /refunds' items (matched by SKU)
for the same period to get their net.
Sales are reported per statement currency. If your stores sell in different regions you will see USD, EUR and GBP rows side by side - we never convert between them.
Marketplace stores (is_marketplace: true), which settle with each vendor separately, are not
covered by this version of the API.
Errors share one shape, with a stable code to branch on and a request_id to quote to us:
{"error":{"type":"invalid_request","code":"invalid_period","message":"period must be YYYY-MM.",
"param":"period","request_id":"req_8sK2..."}}
| HTTP | type |
code |
What to do |
|---|---|---|---|
| 401 | authentication_error |
missing_api_key |
Send Authorization: Bearer <key>. |
| 401 | authentication_error |
invalid_api_key |
The key is wrong. |
| 401 | authentication_error |
api_key_revoked |
Ask us for a new key. |
| 401 | authentication_error |
wrong_api |
That is a Fulfillment API key (8m_...); this API takes 8me_live_.... |
| 403 | permission_error |
missing_scope |
The key cannot read this; ask us to add the scope. |
| 403 | permission_error |
account_inactive |
The account is closed. |
| 404 | not_found |
statement_not_found |
Not yours, not approved yet, or not a statement id. |
| 404 | not_found |
store_not_found |
The store is not covered by your key. |
| 404 | not_found |
unknown_endpoint |
Check the path. |
| 422 | invalid_request |
invalid_period |
Periods are YYYY-MM. |
| 422 | invalid_request |
invalid_period_range |
period_to is before period_from. |
| 422 | invalid_request |
period_required |
/sales and /refunds need period, or both period_from and period_to. |
| 422 | invalid_request |
period_range_too_long |
At most 12 months per /sales or /refunds request. |
| 422 | invalid_request |
invalid_status |
See the endpoint's status values. |
| 429 | rate_limited |
rate_limited |
60 requests a minute per key; wait Retry-After seconds. |
Every response carries a Request-Id header, and RateLimit-Limit, RateLimit-Remaining and
RateLimit-Reset.
Lists return {"data": [...], "has_more": true, "next_cursor": "..."}. Pass cursor=<next_cursor>
for the next page until has_more is false. /sales and /refunds are bounded by their period
instead and return everything at once.
Can I see this month's sales so far? No - only statements, which are generated after the month ends. Your portal's Orders tab shows live orders.
Why does a statement's per-product cost look slightly different from the product's cost? Inside multi-product orders the order's frozen cost is shared across its products (see above); single-product orders always match exactly. The statement totals are the figures that were paid.
Will a statement ever change? Not once it is approved - that is when it appears here. Its figures are frozen.
When was a payout paid? payout.status becomes paid once the money has been sent. The exact
transfer date is on your Stripe Express dashboard.
Is there a sandbox? Not for this API: it is read-only, so a key can safely be pointed at your real data from day one.
{- "data": [
- {
- "id": "st_cm1x2y3z4a5b6c7d8e9f0g1h2",
- "object": "store",
- "name": "string",
- "shopify_domain": "string",
- "status": "pending",
- "currency": "USD",
- "region": "string",
- "is_marketplace": true
}
], - "has_more": true,
- "next_cursor": "string"
}Newest period first. Needs statements:read.
| store | string Only this store ( |
| period | string Example: period=2026-09 One month, |
| period_from | string Example: period_from=2026-07 First month, inclusive, |
| period_to | string Example: period_to=2026-09 Last month, inclusive, |
| status | string Example: status=approved,paid Comma-separated - |
| cursor | string The |
| limit | integer [ 1 .. 100 ] Default: 50 |
curl "https://api.8merch.com/ecommerce/v1/statements?period_from=2026-07&period_to=2026-09" \ -H "Authorization: Bearer $EIGHTMERCH_ECOMMERCE_KEY"
{- "data": [
- {
- "id": "string",
- "object": "statement",
- "store": "string",
- "store_name": "string",
- "period": "2026-09",
- "currency": "USD",
- "status": "approved",
- "order_count": 0,
- "item_count": 0,
- "totals": {
- "gross_value": 0,
- "vat_amount": 0,
- "net_value": 0,
- "cost_of_goods": 0,
- "refunded_net": 0,
- "extra_costs_before_commission": 0,
- "profit": 0,
- "commission": 0,
- "extra_costs_after_commission": 0,
- "payout": 0
}, - "payout": {
- "amount": 0,
- "currency": "string",
- "status": "pending",
- "method": "stripe_transfer"
}, - "approved_at": "2019-08-24T14:15:22Z",
- "generated_at": "2019-08-24T14:15:22Z"
}
], - "has_more": true,
- "next_cursor": "string"
}The totals plus every section of the statement: sales by product, refunds, refunded products and
extra costs. Its orders are at /statements/{id}/orders. Needs statements:read.
| id required | string Example: stmt_cm1x2y3z4a5b6c7d8e9f0g1h2 |
{- "id": "string",
- "object": "statement",
- "store": "string",
- "store_name": "string",
- "period": "2026-09",
- "currency": "USD",
- "status": "approved",
- "order_count": 0,
- "item_count": 0,
- "totals": {
- "gross_value": 0,
- "vat_amount": 0,
- "net_value": 0,
- "cost_of_goods": 0,
- "refunded_net": 0,
- "extra_costs_before_commission": 0,
- "profit": 0,
- "commission": 0,
- "extra_costs_after_commission": 0,
- "payout": 0
}, - "payout": {
- "amount": 0,
- "currency": "string",
- "status": "pending",
- "method": "stripe_transfer"
}, - "approved_at": "2019-08-24T14:15:22Z",
- "generated_at": "2019-08-24T14:15:22Z",
- "products": [
- {
- "sku": "string",
- "product_title": "string",
- "product_vendor": "string",
- "quantity": 0,
- "gross_value": 0,
- "vat_amount": 0,
- "net_value": 0,
- "cost_of_goods": 0,
- "profit": 0
}
], - "refunds": [
- {
- "id": "rf_cm1x2y3z4a5b6c7d8e9f0g1h2",
- "order": "string",
- "order_number": 0,
- "shopify_order_number": 0,
- "refunded_at": "2019-08-24T14:15:22Z",
- "net_amount": 0,
- "vat_amount": 0,
- "items": [
- {
- "sku": "string",
- "quantity": 0,
- "net_amount": 0
}
]
}
], - "refunded_products": [
- {
- "sku": "string",
- "product_title": "string",
- "product_vendor": "string",
- "quantity": 0,
- "quantity_partial": true,
- "refund_line_count": 0,
- "net_amount": 0
}
], - "refunds_unattributed_net": 0,
- "extra_costs": {
- "lines": [
- {
- "id": "xc_cm1x2y3z4a5b6c7d8e9f0g1h2",
- "name": "string",
- "description": "string",
- "amount": 0,
- "original_amount": 0,
- "carried_amount": 0,
- "carry_count": 0,
- "cost_date": "2019-08-24T14:15:22Z",
- "commission_treatment": "before_commission"
}
], - "before_commission": 0,
- "after_commission": 0,
- "reconciles": true
}
}Oldest fulfilment first, each with its frozen figures and its products' share of them. Needs statements:read.
| id required | string Example: stmt_cm1x2y3z4a5b6c7d8e9f0g1h2 |
| cursor | string The |
| limit | integer [ 1 .. 500 ] Default: 100 |
{- "data": [
- {
- "id": "ord_cm1x2y3z4a5b6c7d8e9f0g1h2",
- "object": "statement_order",
- "statement": "string",
- "store": "string",
- "order_number": 0,
- "shopify_order_number": 0,
- "order_date": "2019-08-24T14:15:22Z",
- "fulfilled_at": "2019-08-24T14:15:22Z",
- "net_value": 0,
- "vat_amount": 0,
- "gross_value": 0,
- "cost_of_goods": 0,
- "profit": 0,
- "items": [
- {
- "sku": "string",
- "product_title": "string",
- "product_vendor": "string",
- "quantity": 0,
- "gross_value": 0,
- "vat_amount": 0,
- "net_value": 0,
- "cost_of_goods": 0,
- "profit": 0
}
]
}
], - "has_more": true,
- "next_cursor": "string"
}One row per product per statement for the period - the "sales by product" section of every statement
in range, flattened. Up to 12 months per request; no_activity statements contribute nothing.
Needs statements:read.
| period | string Example: period=2026-09 One month, |
| period_from | string Example: period_from=2026-07 First month, inclusive, |
| period_to | string Example: period_to=2026-09 Last month, inclusive, |
| store | string Only this store ( |
| product_vendor | string Only rows whose |
curl "https://api.8merch.com/ecommerce/v1/sales?period_from=2026-07&period_to=2026-09" \ -H "Authorization: Bearer $EIGHTMERCH_ECOMMERCE_KEY"
{- "data": [
- {
- "statement": "string",
- "store": "string",
- "store_name": "string",
- "period": "string",
- "currency": "string",
- "sku": "string",
- "product_title": "string",
- "product_vendor": "string",
- "quantity": 0,
- "gross_value": 0,
- "vat_amount": 0,
- "net_value": 0,
- "cost_of_goods": 0,
- "profit": 0
}
], - "statements": [
- "string"
]
}Every refund deducted on a statement in the period, with its items. Up to 12 months per request. Needs statements:read.
| period | string Example: period=2026-09 One month, |
| period_from | string Example: period_from=2026-07 First month, inclusive, |
| period_to | string Example: period_to=2026-09 Last month, inclusive, |
| store | string Only this store ( |
{- "data": [
- {
- "statement": "string",
- "store": "string",
- "store_name": "string",
- "period": "string",
- "currency": "string",
- "id": "rf_cm1x2y3z4a5b6c7d8e9f0g1h2",
- "order": "string",
- "order_number": 0,
- "shopify_order_number": 0,
- "refunded_at": "2019-08-24T14:15:22Z",
- "net_amount": 0,
- "vat_amount": 0,
- "items": [
- {
- "sku": "string",
- "quantity": 0,
- "net_amount": 0
}
]
}
], - "statements": [
- "string"
]
}The payout side of each statement, newest period first. no_activity statements have no payout and are not listed. Needs statements:read.
| store | string Only this store ( |
| period | string Example: period=2026-09 One month, |
| period_from | string Example: period_from=2026-07 First month, inclusive, |
| period_to | string Example: period_to=2026-09 Last month, inclusive, |
| status | string Example: status=pending Comma-separated - |
| cursor | string The |
| limit | integer [ 1 .. 100 ] Default: 50 |
{- "data": [
- {
- "statement": "string",
- "store": "string",
- "store_name": "string",
- "period": "string",
- "approved_at": "2019-08-24T14:15:22Z",
- "amount": 0,
- "currency": "string",
- "status": "pending",
- "method": "stripe_transfer"
}
], - "has_more": true,
- "next_cursor": "string"
}Your products with their agreed cost and the stock we hold. Needs products:read.
| store | string Only this store ( |
| sku | string Comma-separated SKUs (up to 200). |
| cursor | string The |
| limit | integer [ 1 .. 500 ] Default: 100 |
{- "data": [
- {
- "id": "prod_cm1x2y3z4a5b6c7d8e9f0g1h2",
- "store": "string",
- "sku": "string",
- "barcode": "string",
- "title": "string",
- "variant_title": "string",
- "vendor": "string",
- "price": 0,
- "currency": "string",
- "cost_of_goods": 0,
- "stock": {
- "on_hand": 0,
- "synced_at": "2019-08-24T14:15:22Z"
}
}
], - "has_more": true,
- "next_cursor": "string"
}