Skip to content

API ​

Connect your shop or order system to microfulfill to create orders and inbound shipments automatically and to read stock levels.

The complete reference with every field and response is the API reference, generated from the code. In the app it is also available at /docs/api.

Authentication ​

Create an API token under Settings → API tokens and send it with every request:

http
Authorization: Bearer <your token>
Accept: application/json

Keep tokens secret. Anyone with a token can act for your organization. You can delete a token at any time. The API allows 120 requests per minute.

Choosing the organization ​

If you belong to several organizations, send the organization id in the X-Team-Id header. Without it, the organization you last used in the web interface is used.

Base URL: https://<your domain>/api/v1

Warehouses ​

GET /warehouses lists the warehouses you can send stock and orders to.

Inventory ​

RequestDescription
GET /inventory?search=TEEList your products (paginated, per_page up to 100)
POST /inventoryCreate a product
GET /inventory/{sku}Product with stock per warehouse
PATCH /inventory/{sku}Change a product
json
{
  "sku": "TEE-L",
  "name": "T-Shirt L",
  "category": "Apparel",
  "weight": 210, "weight_unit": "g",
  "length": 30, "width": 20, "height": 2, "dimensions": "cm",
  "customs_value": 12.00, "currency": "EUR",
  "hs_code": "610910",
  "low_stock_threshold": 10
}

hs_code needs 6 to 10 digits whose first six are a valid HS subheading (dots are removed). category is optional and free text.

Orders ​

RequestDescription
GET /orders?status=openList orders. Statuses: open, ready, picking, packed, shipped, cancelled
POST /ordersCreate an order (status open)
GET /orders/{id}Order with items, label and tracking
PATCH /orders/{id}Change an open order; fields you leave out keep their value
GET /orders/{id}/ratesShipping rates for the order, with taxes_due_on_delivery (paid by the recipient) and taxes_included
POST /orders/{id}/labelBuy the label with a rate_id; the order becomes ready
GET /orders/{id}/labelDownload the label (PDF)
DELETE /orders/{id}/labelCancel the label (refunded), the order goes back to open
POST /orders/{id}/cancelCancel an order that has not been shipped (cancels its label)
GET /orders/{id}/eventsHistory of the order
POST /orders/{id}/attachmentAttach a PDF for the parcel (multipart file or JSON content_base64), replaces an existing one
GET /orders/{id}/attachment, DELETE /orders/{id}/attachmentDownload or remove the document

To correct an order after the label was bought (for example a wrong address): cancel the label, change the order with PATCH, then buy a new label. Optional fields for cancelling: reason (shown to the warehouse) and without_refund: true if the label provider refuses to cancel.

json
{
  "warehouse_id": 12,
  "order_no": "1001",
  "shipping_address": {
    "name": "Jane Doe",
    "line1": "1 Main Street",
    "postal_code": "78701",
    "city": "Austin",
    "state": "TX",
    "country": "US",
    "email": "jane@example.com"
  },
  "items": [{ "sku": "TEE-L", "quantity": 2 }]
}

Returns ​

RequestDescription
GET /returns?status=announcedList returns. Statuses: announced, received
POST /returnsAnnounce a return (warehouse_id, optional order_id, reason, tracking, items)
GET /returns/{id}Return with expected, received and restocked quantities

Inbound shipments ​

RequestDescription
GET /inbound-shipmentsList inbound shipments
POST /inbound-shipmentsAnnounce an inbound shipment
GET /inbound-shipments/{id}Shipment with sent and received quantities
json
{
  "warehouse_id": 12,
  "reference": "PO-2026-118",
  "tracking": { "carrier": "UPS", "number": "1Z999AA10123456784" },
  "shipped_at": "2026-11-10",
  "items": [{ "sku": "TEE-L", "quantity": 120 }]
}

Webhooks ​

Instead of polling the API, let microfulfill notify your system. Add the URL under Settings → Webhooks and choose the events (or all). microfulfill sends a POST request with a JSON body for every event.

EventSent toWhen
order.createdMerchant, providerAn order was created
order.label_purchasedMerchant, providerThe label was bought, the order is ready
order.label_voidedMerchant, providerThe label was cancelled, the order is open again
order.packedMerchant, providerThe warehouse packed the order
order.on_holdMerchant, providerThe warehouse cannot ship the order; reason says why
order.resumedMerchant, providerThe warehouse continues an order that was on hold
order.shippedMerchant, providerThe order was handed to the carrier
order.cancelledMerchant, providerThe order was cancelled
inbound.receivedMerchantAn inbound shipment was received
return.announcedProviderA return was announced
return.receivedMerchantA return was received and inspected
json
{
  "id": "5d0c6f4e-0d64-4a3c-9a6b-2b6b2f3a1c11",
  "event": "order.shipped",
  "created_at": "2026-11-13T10:15:00+00:00",
  "team_id": 3,
  "data": { "order": { "id": 55, "order_no": "1001", "status": "shipped", "...": "..." } }
}

data contains the same objects as the API v1 responses (order, inbound_shipment, return).

Headers and signature ​

HeaderContent
X-Microfulfill-EventEvent name
X-Microfulfill-DeliveryUnique delivery id; the same for retries — use it to ignore duplicates
X-Microfulfill-Signaturet=<unix time>,v1=<HMAC-SHA256 of "<t>.<raw body>" with your signing secret>

Verify every request before you trust it (PHP example):

php
[$t, $v1] = array_map(fn ($part) => explode('=', $part, 2)[1], explode(',', $request->header('X-Microfulfill-Signature')));
$expected = hash_hmac('sha256', $t.'.'.$request->getContent(), $secret);

if (! hash_equals($expected, $v1) || abs(time() - (int) $t) > 300) {
    abort(401);
}

Answer with a 2xx status within 10 seconds. Other answers are retried after about 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours. After 50 failed deliveries in a row the webhook is paused. The Deliveries list in the settings shows every delivery and lets you send it again.

Errors ​

StatusMeaning
401Missing or invalid token
403No access to this organization or resource
404Not found
422Validation failed — see errors
429Too many requests

Legacy API ​

The endpoints of the previous system (POST /api/orders, GET /api/orders/{order_no}, POST /api/inventory, GET /api/inventory/{sku}, POST /api/inbound, GET /api/inbound/{id}) keep working with the same fields and responses. New integrations should use API v1.