8Merch Fulfillment API (1.0.0)

Download OpenAPI specification:

Send orders to 8Merch for fulfillment, manage the products we hold for you, and follow every order to the doorstep.

Part of the 8Merch API documentation - Services > Fulfillment.

The 8Merch Fulfillment API lets a store, marketplace or artist platform with its own checkout send orders to 8Merch warehouses (US, EU, UK) to be picked, packed and shipped, and follow them through to delivery.

Every endpoint documented here is live at https://api.8merch.com/v1. Build and test against a sandbox account with a test key first - see Sandbox below.

Getting started

1. Get a key. Keys are issued by 8Merch - email us and we will create one with the scopes you need. You receive it once; store it on your server (never in a browser or app). Every request sends it:

curl https://api.8merch.com/v1/warehouses \
  -H "Authorization: Bearer $EIGHTMERCH_API_KEY"

A partner key (one key, many artist accounts) also says which account each request is for: add -H "8Merch-Account: acc_..." (list yours with GET /v1/accounts).

2. Create your products, keyed by your SKU. Sending a SKU again updates it, so a catalogue sync can simply send everything:

curl -X POST https://api.8merch.com/v1/products \
  -H "Authorization: Bearer $EIGHTMERCH_API_KEY" -H "Content-Type: application/json" \
  -d '{"sku":"KM-TEE-BLK-L","title":"Killer Moon T-shirt","variant_title":"Black / L","artist":"96BB",
       "hs_code":"6109.10","country_of_origin":"PT","customs_description":"Cotton T-shirt, printed"}'

3. Send us stock and tell us it is coming (POST /v1/inbound-shipments). We count it in on arrival; GET /v1/stock shows it as incoming, then on_hand.

4. Send each paid order with an Idempotency-Key, so a retry can never create it twice:

curl -X POST https://api.8merch.com/v1/orders \
  -H "Authorization: Bearer $EIGHTMERCH_API_KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: order-10045" \
  -d '{"external_id":"10045","order_number":"#10045",
       "shipping_address":{"name":"Ann Lee","line1":"1 Main St","city":"Austin","region":"TX",
                           "postal_code":"78701","country_code":"US"},
       "items":[{"sku":"KM-TEE-BLK-L","quantity":1,"unit_price":25}]}'

5. Register a webhook (POST /v1/webhook-endpoints) to hear order.shipped with the tracking number, and the rest of the lifecycle - or poll GET /v1/orders?updated_since=....

Two ways to integrate

Pick whichever fits how you work with your artists. Both use the same endpoints.

A. One account, artists as stores. You are a single 8Merch account. Each artist is a store inside it (POST /v1/stores), and products and orders name their store. 8Merch bills you, monthly, for everything; you settle with your artists. Use an account key.

B. Platform partner, one account per artist. Each artist has their own 8Merch account, with their own billing, payment method and monthly statement, and your partner key can act for all of them. Send 8Merch-Account: <account id> on every request to say which artist it is for; GET /v1/accounts lists the accounts linked to your key. New artists are invited with POST /v1/accounts and finish their own billing set-up from the link we return.

You can mix them: a partner key can act for an account that itself has several stores.

Fulfillment costs are billed monthly to the account an order belongs to, on its statement.

Sandbox

Test the whole integration - products, stock, orders, webhooks - without anything shipping or being billed. Ask us for a sandbox account and a test key (8m_test_...). Same URL, same endpoints, same validation; the differences:

  • A test key acts only for sandbox accounts, and a live key never does (403 livemode_mismatch). With a partner test key, POST /v1/accounts creates sandbox accounts (no invitation email is sent) and GET /v1/accounts lists only those.
  • Nothing reaches a warehouse and nothing is billed. Instead a simulator, running every 5 minutes, plays the warehouse:
    • an order moves accepted -> in_warehouse -> shipped, at least 2 minutes per step, so allow 5-15 minutes; it ships with tracking SANDBOX-<number> and its stock is deducted;
    • an inbound shipment is received in full 2+ minutes after you announce it;
    • an order with "metadata": {"sandbox": "hold"} goes on_hold, to test that path;
    • pre-order products hold their orders, exactly as live.
  • Webhook endpoints registered with a test key receive only sandbox events; live endpoints never do.

When you go live, the same code works with a live key against your real account.

Requests and responses

  • JSON in, JSON out. Send Content-Type: application/json. Dates are ISO 8601, in UTC.
  • Ids are typed: acc_ account, st_ store, wh_ warehouse, ord_ order, ib_ inbound shipment, we_ webhook endpoint, evt_ event. Products are addressed by your SKU, orders also by ext:<your external_id>.
  • Lists are paginated: limit (1-100, default 50); follow next_cursor while has_more is true.
  • Retries are safe on POST /v1/orders and POST /v1/inbound-shipments with an Idempotency-Key: the same key returns the original object with 200 and Idempotent-Replayed: true instead of creating another (201). POST /v1/products is an upsert and safe to repeat anyway.
  • Rate limit: 120 requests a minute per key. Every response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset; over the limit you get 429 with Retry-After.
  • Request-Id: every response has one - quote it when you contact us.
  • Fields we do not know are ignored, so you can send your own records without stripping them.

Errors

Every error has the same shape:

{ "error": { "type": "invalid_request", "code": "unknown_sku",
             "message": "No product with SKU KM-TEE-BLK-L ...", "param": "items[0].sku",
             "request_id": "req_..." } }

Branch on type and code; message is for people and may change.

HTTP type means
400 / 422 invalid_request Something in the request is wrong - param names the field.
401 authentication_error Missing, invalid or revoked key.
402 account_on_hold The account cannot take new orders for that warehouse right now.
403 permission_error The key lacks a scope, or cannot act for that account.
404 not_found No such object for this account (or no such endpoint).
409 conflict Duplicate, or too late (e.g. the order has shipped).
429 rate_limited Slow down; retry after Retry-After seconds.
5xx server_error Our fault - retry with the same Idempotency-Key.
code HTTP what to do
missing_api_key, invalid_api_key, api_key_revoked 401 Check the Authorization header; ask us for a new key.
partner_disabled 401 Your partner access is paused - contact us.
missing_scope 403 The key lacks the scope named in message; ask us to add it.
missing_account 400 Partner keys must send 8Merch-Account.
account_not_linked, account_inactive 403 The account is not linked to your key, or is closed.
partner_key_required 403 /v1/accounts needs a partner key.
livemode_mismatch 403 Test keys act only for sandbox accounts, live keys only for real ones.
missing_payment_method, missing_billing_details, payment_hold, account_inactive 402 Order refused: the account must add a card, finish billing details or pay a statement. Retry once fixed.
unknown_sku 422 Create the product first; nothing was created.
duplicate_sku 422 Send one line per SKU.
duplicate_external_id 409 You already sent this order - fetch it with GET /v1/orders/ext:<id>.
order_locked 409 The order has shipped (or was cancelled); contact us.
warehouse_required, warehouse_not_granted, warehouse_store_mismatch, unknown_warehouse 422 Say which warehouse (warehouse or store), one this account uses - ids from GET /v1/warehouses.
unknown_store 422 The store id is not one of this account's.
sku_immutable, warehouse_on_update 422 A SKU cannot change; warehouse only applies when a product is created.
invalid_* (e.g. invalid_country_code, invalid_quantity) 422 Fix the field named in param.
email_in_use 409 That artist already has an 8Merch account - ask us to link it.
too_many_endpoints 422 Up to 10 webhook endpoints; remove one first.
*_not_found, unknown_endpoint 404 Check the id or URL.
rate_limited 429 Wait Retry-After seconds.

Statuses

Orders

status means cancel / change address?
accepted With 8Merch, about to go to the warehouse - or waiting for stock or a pre-order release. Yes.
in_warehouse With the warehouse to pick and pack. Yes - we pass it to the warehouse and alert our team, but it may already be packed.
on_hold Stopped by 8Merch for a check; hold_reason says why. We contact the account. Yes.
shipped Handed to the carrier; shipment has the tracking. No (409 order_locked).
cancelled Cancelled. Cancelling again returns it unchanged.

Inbound shipments: announced (on its way) -> received (counted in; received_items has what arrived) or cancelled.

Accounts: pending (new, finishing billing set-up), active (taking orders), on_hold (orders refused - hold_reasons says which warehouse and why), inactive (closed).

Verifying webhooks

Each delivery has 8Merch-Signature: t=<unix seconds>,v1=<hex>, where v1 is the HMAC-SHA256 of "<t>.<raw body>" with your endpoint secret. Verify it against the raw body, before parsing:

// Node 18+
const crypto = require("node:crypto");
function verify(rawBody, header, secret) {
  const parts = Object.fromEntries(header.split(",").map((p) => p.split("=")));
  if (Math.abs(Date.now() / 1000 - Number(parts.t)) > 300) return false;
  const expected = crypto.createHmac("sha256", secret).update(`${parts.t}.${rawBody}`).digest("hex");
  return parts.v1?.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(parts.v1, "hex"), Buffer.from(expected, "hex"));
}
# Python 3
import hashlib, hmac, time
def verify(raw_body: bytes, header: str, secret: str) -> bool:
    parts = dict(p.split("=", 1) for p in header.split(","))
    if abs(time.time() - int(parts["t"])) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{parts['t']}.".encode() + raw_body, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, parts.get("v1", ""))

Then respond 2xx within 10 seconds and do the work afterwards. Use the event id to ignore repeats.

FAQ

Do products have to exist before orders? Yes. An order with an unknown SKU is refused whole (422 unknown_sku) so nothing ships half-complete.

What happens if something is out of stock? The order is accepted and waits in accepted; it is never refused for stock. When open orders need more than we hold, the product is short: stock.shortfall_opened is sent, the account is emailed and sees it on its dashboard. GET /v1/stock?shortfall_only=true lists them.

How do pre-orders work? Mark the product preorder.is_preorder: true. Orders containing it are held (status accepted, counted as preordered, not short) until the account releases them in the 8Merch portal when the stock is ready.

How do I change the items on an order? Cancel it and send a new one. external_id stays unique even after a cancel, so give the new order a new id (e.g. 10045-2).

How do I get tracking? order.shipped carries shipment.carrier, service and tracking_number; GET /v1/orders/{id} shows the same.

What does shipping cost? It is billed on the account's monthly statement (postage by measured weight and destination, plus the per-order and per-item fees agreed with us), visible in the 8Merch portal. Orders do not carry a cost in the API.

Which warehouse ships an order? The one you name, or the store's, or the account's only one. GET /v1/warehouses lists those the account can use; each has one currency.

Do I need customs data? For parcels crossing a customs border (e.g. UK to EU, anywhere to the US): hs_code, country_of_origin and customs_description on the product, and unit_price on order lines.

My server was down - did I miss webhooks? Deliveries are retried for about 3 days. To catch up regardless, list GET /v1/orders?updated_since=<last time you synced>.

How do I test before going live? Use a sandbox account and a test key (see Sandbox): the real API, with a simulated warehouse. To build request code before you have a key, run a local mock from this spec: npx @stoplight/prism-cli mock https://api.8merch.com/docs/fulfillment/openapi.yaml.

Can the rate limit be raised? Yes - tell us what you need.

Accounts

Platform partners only - the artist accounts your partner key can act for.

List the accounts your partner key can act for

Partner keys only. An account key gets 403.

Authorizations:
apiKey
query Parameters
cursor
string

The next_cursor from the previous page.

limit
integer [ 1 .. 100 ]
Default: 50

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Invite a new artist account

Creates the account linked to your partner key and returns an onboarding_url for the artist to enter their billing details and payment method. Until that is done the account is pending and orders for it are refused with 402 account_on_hold.

Authorizations:
apiKey
header Parameters
Idempotency-Key
string <= 255 characters

Any unique string up to 255 characters. Repeating a request with the same key returns the first result instead of doing the work again (keys are kept for at least 24 hours; inbound shipment keys are kept for good). A replayed response carries Idempotent-Replayed: true. Strongly recommended on every POST.

Request Body schema: application/json
required
name
required
string

The artist or label name.

email
required
string <email>

Receives the onboarding link and billing emails.

external_id
string

Your id for this artist.

warehouses
required
Array of strings non-empty

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "email": "[email protected]",
  • "external_id": "string",
  • "warehouses": [
    ]
}

Response samples

Content type
application/json
{
  • "id": "acc_7Hk2",
  • "name": "96BB",
  • "external_id": "string",
  • "status": "pending",
  • "hold_reasons": [
    ],
  • "warehouses": [
    ],
  • "created_at": "2019-08-24T14:15:22Z",
  • "onboarding_url": "http://example.com"
}

Get an account - including whether it is on hold and why

Authorizations:
apiKey
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "id": "acc_7Hk2",
  • "name": "96BB",
  • "external_id": "string",
  • "status": "pending",
  • "hold_reasons": [
    ],
  • "warehouses": [
    ],
  • "created_at": "2019-08-24T14:15:22Z"
}

Stores

Storefronts inside one account - e.g. one per artist.

List the stores in the account

Authorizations:
apiKey
header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Create a store - e.g. one per artist

Authorizations:
apiKey
header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Request Body schema: application/json
required
name
required
string <= 120 characters
warehouse
required
string

Default warehouse for this store's orders.

external_id
string

Your id for this artist or storefront.

Responses

Request samples

Content type
application/json
{
  • "name": "96BB official store",
  • "warehouse": "string",
  • "external_id": "string"
}

Response samples

Content type
application/json
{
  • "id": "st_3Fq9",
  • "name": "string",
  • "warehouse": "string",
  • "external_id": "string",
  • "created_at": "2019-08-24T14:15:22Z"
}

Warehouses

Where your orders ship from. One warehouse, one currency.

List the warehouses your account can ship from

Each warehouse ships in one currency. Orders and products are always placed in a warehouse.

Authorizations:
apiKey
header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Responses

Request samples

curl "https://api.8merch.com/v1/warehouses" \
  -H "Authorization: Bearer $EIGHTMERCH_API_KEY"

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Products

The items we hold and ship for you, keyed by your SKU.

List products

Authorizations:
apiKey
query Parameters
cursor
string

The next_cursor from the previous page.

limit
integer [ 1 .. 100 ]
Default: 50
artist
string

Only products with this artist.

updated_since
string <date-time>
header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Create a product (or update it, if the SKU exists)

Products are keyed by your SKU, which must be unique within your account. Sending a SKU that already exists updates that product rather than failing (200 instead of 201), so a catalogue sync can simply send everything. New products are made available to the warehouse within a few minutes.

A new product starts in one warehouse: warehouse if you send it, otherwise the store's, otherwise the account's only warehouse (an account with several must say which). warehouse is ignored when the SKU already exists - to stock a product in another warehouse, send an inbound shipment there.

Fields you leave out are left as they are; null clears an optional field. Fields we do not know are ignored.

Authorizations:
apiKey
header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Idempotency-Key
string <= 255 characters

Any unique string up to 255 characters. Repeating a request with the same key returns the first result instead of doing the work again (keys are kept for at least 24 hours; inbound shipment keys are kept for good). A replayed response carries Idempotent-Replayed: true. Strongly recommended on every POST.

Request Body schema: application/json
required
sku
required
string <= 64 characters
store
string or null

Model A - the store (artist) this product belongs to (st_...). Optional.

warehouse
string

Where a NEW product starts (wh_...). See above for the default. Create only.

title
required
string <= 255 characters
variant_title
string <= 255 characters
artist
string <= 255 characters

The artist or band. Shown on warehouse pick lists and packing slips so the right item is picked.

barcode
string

EAN/UPC if the item carries one.

price
number

Retail price, for customs declarations.

currency
string
Enum: "USD" "EUR" "GBP"
image_url
string <uri>

Helps the warehouse identify the item.

hs_code
string

Customs tariff (HS) code, 6-10 digits; dots and spaces are ignored (e.g. 6109.10). Needed for parcels crossing a customs border.

country_of_origin
string

Where the item was made - ISO 3166-1 alpha-2, e.g. PT, BD.

customs_description
string <= 200 characters

Plain description for the customs declaration, e.g. Men's cotton T-shirt, printed.

object

A pre-order product holds every order containing it until the release is triggered; its orders count as preordered, not reserved, and never as a shortfall.

Responses

Request samples

Content type
application/json
{
  • "sku": "KM-TEE-BLK-L",
  • "store": "string",
  • "warehouse": "string",
  • "title": "Killer Moon T-shirt",
  • "variant_title": "Black / L",
  • "artist": "96BB",
  • "barcode": "string",
  • "price": 0,
  • "currency": "USD",
  • "image_url": "http://example.com",
  • "hs_code": "string",
  • "country_of_origin": "string",
  • "customs_description": "string",
  • "preorder": {
    }
}

Response samples

Content type
application/json
{
  • "sku": "KM-TEE-BLK-L",
  • "store": "string",
  • "warehouse": "string",
  • "title": "Killer Moon T-shirt",
  • "variant_title": "Black / L",
  • "artist": "96BB",
  • "barcode": "string",
  • "price": 0,
  • "currency": "USD",
  • "image_url": "http://example.com",
  • "hs_code": "string",
  • "country_of_origin": "string",
  • "customs_description": "string",
  • "preorder": {
    },
  • "warehouses": [
    ],
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Get a product

Authorizations:
apiKey
path Parameters
sku
required
string

Your SKU, URL-encoded.

header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Responses

Response samples

Content type
application/json
{
  • "sku": "KM-TEE-BLK-L",
  • "store": "string",
  • "warehouse": "string",
  • "title": "Killer Moon T-shirt",
  • "variant_title": "Black / L",
  • "artist": "96BB",
  • "barcode": "string",
  • "price": 0,
  • "currency": "USD",
  • "image_url": "http://example.com",
  • "hs_code": "string",
  • "country_of_origin": "string",
  • "customs_description": "string",
  • "preorder": {
    },
  • "warehouses": [
    ],
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Update a product

Send only the fields to change. The SKU itself cannot be changed - create a new product instead.

Authorizations:
apiKey
path Parameters
sku
required
string

Your SKU, URL-encoded.

header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Request Body schema: application/json
required
store
string or null
title
string
variant_title
string
artist
string
barcode
string
price
number
currency
string
Enum: "USD" "EUR" "GBP"
image_url
string <uri>
hs_code
string
country_of_origin
string
customs_description
string <= 200 characters
object

Responses

Request samples

Content type
application/json
{
  • "store": "string",
  • "title": "string",
  • "variant_title": "string",
  • "artist": "string",
  • "barcode": "string",
  • "price": 0,
  • "currency": "USD",
  • "image_url": "http://example.com",
  • "hs_code": "string",
  • "country_of_origin": "string",
  • "customs_description": "string",
  • "preorder": {
    }
}

Response samples

Content type
application/json
{
  • "sku": "KM-TEE-BLK-L",
  • "store": "string",
  • "warehouse": "string",
  • "title": "Killer Moon T-shirt",
  • "variant_title": "Black / L",
  • "artist": "96BB",
  • "barcode": "string",
  • "price": 0,
  • "currency": "USD",
  • "image_url": "http://example.com",
  • "hs_code": "string",
  • "country_of_origin": "string",
  • "customs_description": "string",
  • "preorder": {
    },
  • "warehouses": [
    ],
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Stock

What is in each warehouse, promised to orders, and on its way.

Stock levels per product and warehouse

available = on_hand - reserved, where reserved is stock already promised to open orders. Stock changes when we receive an inbound shipment, ship an order or correct a count; subscribe to stock.updated rather than polling.

Authorizations:
apiKey
query Parameters
cursor
string

The next_cursor from the previous page.

limit
integer [ 1 .. 100 ]
Default: 50
warehouse
string
sku
string

One SKU, or several separated by commas.

shortfall_only
boolean

Only products that are short - open orders need more units than are on hand.

header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Responses

Request samples

curl "https://api.8merch.com/v1/stock?shortfall_only=true" \
  -H "Authorization: Bearer $EIGHTMERCH_API_KEY"

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Inbound shipments

Announce stock you are sending us; we report what was counted in.

List inbound shipments

Authorizations:
apiKey
query Parameters
cursor
string

The next_cursor from the previous page.

limit
integer [ 1 .. 100 ]
Default: 50
status
string (InboundStatus)
Enum: "announced" "received" "cancelled"
header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Tell us stock is on its way

Announce goods you are sending to a warehouse, so they are expected and counted in quickly. Quantities you announce show as incoming until the warehouse counts them in; the counted quantity (which may differ) is what becomes on_hand, and inbound_shipment.received reports it.

Every SKU must already exist (POST /v1/products first), once per shipment, in a warehouse the account uses. Send an Idempotency-Key so a retried request cannot announce the goods twice.

Authorizations:
apiKey
header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Idempotency-Key
string <= 255 characters

Any unique string up to 255 characters. Repeating a request with the same key returns the first result instead of doing the work again (keys are kept for at least 24 hours; inbound shipment keys are kept for good). A replayed response carries Idempotent-Replayed: true. Strongly recommended on every POST.

Request Body schema: application/json
required
warehouse
required
string
reference
string

Your reference, e.g. a purchase-order number.

carrier
string
tracking_number
string
expected_arrival
string <date>
required
Array of objects non-empty

Responses

Request samples

Content type
application/json
{
  • "warehouse": "string",
  • "reference": "string",
  • "carrier": "string",
  • "tracking_number": "string",
  • "expected_arrival": "2019-08-24",
  • "items": [
    ]
}

Response samples

Content type
application/json
{
  • "warehouse": "string",
  • "reference": "string",
  • "carrier": "string",
  • "tracking_number": "string",
  • "expected_arrival": "2019-08-24",
  • "items": [
    ],
  • "id": "ib_cm9x2k",
  • "status": "announced",
  • "received_at": "2019-08-24T14:15:22Z",
  • "received_items": [
    ],
  • "created_at": "2019-08-24T14:15:22Z"
}

Get an inbound shipment

Authorizations:
apiKey
path Parameters
id
required
string
Example: ib_cm9x2k
header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Responses

Response samples

Content type
application/json
{
  • "warehouse": "string",
  • "reference": "string",
  • "carrier": "string",
  • "tracking_number": "string",
  • "expected_arrival": "2019-08-24",
  • "items": [
    ],
  • "id": "ib_cm9x2k",
  • "status": "announced",
  • "received_at": "2019-08-24T14:15:22Z",
  • "received_items": [
    ],
  • "created_at": "2019-08-24T14:15:22Z"
}

Orders

Send paid orders for fulfillment and follow them to delivery.

List orders

Orders you sent through the API. Orders from the account's own Shopify or Bandcamp stores are not listed here.

Authorizations:
apiKey
query Parameters
cursor
string

The next_cursor from the previous page.

limit
integer [ 1 .. 100 ]
Default: 50
status
string
Example: status=accepted,in_warehouse

One status, or several separated by commas.

updated_since
string <date-time>

Only orders changed at or after this time - the efficient way to catch up after downtime.

external_id
string
header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Responses

Response samples

Content type
application/json
{
  • "data": [
    ],
  • "has_more": true,
  • "next_cursor": "string"
}

Send an order for fulfillment

Send each order once it is paid. Every SKU must already exist as a product in the order's warehouse; unknown SKUs reject the whole order (422 unknown_sku) so nothing ships half-complete.

Retrying is safe. Send an Idempotency-Key header (your order id works well): repeating the same request with the same key returns the original order instead of creating a second one. external_id is also unique per account - a second order with an external_id you have already used is refused with 409 duplicate_external_id.

Orders for items that are not in stock are accepted and wait in accepted until stock is received - intake is never paused. When open orders (other than pre-orders) need more units than we hold, the product is short: we send stock.shortfall_opened, email the account and show it on their dashboard, so stock can be sent. stock.shortfall_resolved follows when stock covers the orders again.

If the account is on hold for the order's warehouse (no valid payment method, missing billing details, or an unpaid statement after three failed charges), new orders are refused with 402 account_on_hold - never silently dropped. error.code says which: missing_payment_method, missing_billing_details, payment_hold or account_inactive.

Without a store, the order is filed under the account's "API orders" store for that warehouse, created automatically.

Authorizations:
apiKey
header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Idempotency-Key
string <= 255 characters

Any unique string up to 255 characters. Repeating a request with the same key returns the first result instead of doing the work again (keys are kept for at least 24 hours; inbound shipment keys are kept for good). A replayed response carries Idempotent-Replayed: true. Strongly recommended on every POST.

Request Body schema: application/json
required
external_id
required
string <= 128 characters

Your order id. Unique per account.

store
string

Model A - the store (artist) the order was placed in (st_...). Optional - see above.

order_number
string <= 64 characters

What your customer sees, e.g. #10045. Shown to our team and returned to you.

order_date
string <date-time>

Defaults to now.

warehouse
string

Which warehouse ships it (wh_...). Optional when the store fixes it or the account uses one warehouse.

currency
string
Enum: "USD" "EUR" "GBP"

The checkout currency - informational.

shipping_method
string <= 255 characters

The shipping option your customer chose at checkout, e.g. Standard or Express. We map it to a carrier service.

object

The recipient's name is shipping_address.name.

required
object (Address)
required
Array of objects [ 1 .. 200 ] items

One line per SKU.

object

Up to 20 keys of your own, returned unchanged.

Responses

Request samples

Content type
application/json
{
  • "external_id": "string",
  • "store": "string",
  • "order_number": "string",
  • "order_date": "2019-08-24T14:15:22Z",
  • "warehouse": "string",
  • "currency": "USD",
  • "shipping_method": "string",
  • "customer": {},
  • "shipping_address": {
    },
  • "items": [
    ],
  • "metadata": {
    }
}

Response samples

Content type
application/json
{
  • "external_id": "string",
  • "store": "string",
  • "order_number": "string",
  • "order_date": "2019-08-24T14:15:22Z",
  • "warehouse": "string",
  • "currency": "USD",
  • "shipping_method": "string",
  • "customer": {},
  • "shipping_address": {
    },
  • "items": [
    ],
  • "metadata": {
    },
  • "id": "ord_cm9x2k",
  • "status": "accepted",
  • "hold_reason": "string",
  • "cancel_reason": "string",
  • "shipment": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Get an order

Authorizations:
apiKey
path Parameters
id
required
string

Our order id, or ext: followed by your external_id (e.g. ext:10045).

header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Responses

Request samples

curl "https://api.8merch.com/v1/orders/ext:10045" \
  -H "Authorization: Bearer $EIGHTMERCH_API_KEY"

Response samples

Content type
application/json
{
  • "external_id": "string",
  • "store": "string",
  • "order_number": "string",
  • "order_date": "2019-08-24T14:15:22Z",
  • "warehouse": "string",
  • "currency": "USD",
  • "shipping_method": "string",
  • "customer": {},
  • "shipping_address": {
    },
  • "items": [
    ],
  • "metadata": {
    },
  • "id": "ord_cm9x2k",
  • "status": "accepted",
  • "hold_reason": "string",
  • "cancel_reason": "string",
  • "shipment": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Correct the shipping address

Replaces the whole address. Possible until the order ships; after that 409 order_locked. If the order is already with the warehouse (in_warehouse) the new address is sent there and our team is alerted - a label printed before the change is reprinted.

Authorizations:
apiKey
path Parameters
id
required
string

Our order id, or ext: followed by your external_id (e.g. ext:10045).

header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Request Body schema: application/json
required
name
required
string

The recipient.

company
string

Printed on the label.

line1
required
string
line2
string
city
required
string
region
string

State, province or county - required for US, CA and AU.

postal_code
required
string
country_code
required
string

ISO 3166-1 alpha-2, e.g. US, DE, GB.

phone
string

Many carriers require it for international parcels.

Responses

Request samples

Content type
application/json
{
  • "name": "string",
  • "company": "string",
  • "line1": "string",
  • "line2": "string",
  • "city": "string",
  • "region": "string",
  • "postal_code": "string",
  • "country_code": "string",
  • "phone": "string"
}

Response samples

Content type
application/json
{
  • "external_id": "string",
  • "store": "string",
  • "order_number": "string",
  • "order_date": "2019-08-24T14:15:22Z",
  • "warehouse": "string",
  • "currency": "USD",
  • "shipping_method": "string",
  • "customer": {},
  • "shipping_address": {
    },
  • "items": [
    ],
  • "metadata": {
    },
  • "id": "ord_cm9x2k",
  • "status": "accepted",
  • "hold_reason": "string",
  • "cancel_reason": "string",
  • "shipment": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Cancel an order

Possible until the order ships; after that 409 order_locked - contact us and we will try to stop it. An order still accepted is simply cancelled. One already in_warehouse is cancelled at the warehouse too and our team is alerted, because it may already be packed - if it still goes out, we will tell you. Cancelling a cancelled order returns it unchanged.

Authorizations:
apiKey
path Parameters
id
required
string

Our order id, or ext: followed by your external_id (e.g. ext:10045).

header Parameters
8Merch-Account
string
Example: acc_7Hk2

Partner keys - the account (artist) this request is for. Required with a partner key; ignored with an account key.

Request Body schema: application/json
optional
reason
string <= 500 characters

Responses

Request samples

Content type
application/json
{
  • "reason": "string"
}

Response samples

Content type
application/json
{
  • "external_id": "string",
  • "store": "string",
  • "order_number": "string",
  • "order_date": "2019-08-24T14:15:22Z",
  • "warehouse": "string",
  • "currency": "USD",
  • "shipping_method": "string",
  • "customer": {},
  • "shipping_address": {
    },
  • "items": [
    ],
  • "metadata": {
    },
  • "id": "ord_cm9x2k",
  • "status": "accepted",
  • "hold_reason": "string",
  • "cancel_reason": "string",
  • "shipment": {
    },
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Webhooks

Get told when things change instead of polling.

List webhook endpoints

Endpoints belong to whoever owns the key. A partner key's endpoints receive the events of every account linked to the partner - register once, not once per artist; each event names its account. An account key's endpoints receive that account's events. The 8Merch-Account header plays no part here.

Authorizations:
apiKey

Responses

Response samples

Content type
application/json
{
  • "data": [
    ]
}

Register a webhook endpoint

The signing secret is returned once, in this response only. Store it to verify deliveries. The URL must be https and publicly reachable; up to 10 endpoints per key owner.

Authorizations:
apiKey
Request Body schema: application/json
required
url
required
string <uri>

Must be https.

events
required
Array of strings (EventType)
Items Enum: "order.accepted" "order.in_warehouse" "order.shipped" "order.cancelled" "order.on_hold" "stock.updated" "stock.shortfall_opened" "stock.shortfall_resolved" "inbound_shipment.received" "account.hold_changed"

Responses

Request samples

Content type
application/json
{}

Response samples

Content type
application/json
{
  • "id": "string",
  • "events": [
    ],
  • "status": "active",
  • "last_success_at": "2019-08-24T14:15:22Z",
  • "last_failure_at": "2019-08-24T14:15:22Z",
  • "last_error": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "secret": "whsec_3fX9..."
}

Remove a webhook endpoint

Authorizations:
apiKey
path Parameters
id
required
string

Responses

Response samples

Content type
application/json
{
  • "error": {
    }
}