API Reference > Fulfillments API

Fulfillments API

Last Updated: 2026-07-02

List Fulfillments

GET/open/v1/fulfillmentsScope: fulfillment:read

Returns a paginated list of fulfillment records for the seller bound to your access token. A fulfillment represents one shipment of one or more items from an order; an order can have multiple fulfillments when it ships in several packages. You can filter by status or parent order, choose a sort direction, and walk large result sets efficiently with cursor pagination.

Only fulfillments belonging to your seller are ever returned — the seller scope is enforced server-side from your access token and cannot be overridden by any query parameter.

Query Parameters

ParameterTypeRequiredDescription
statusstringNo Filter by fulfillment status. One of pending, shipped, delivered, cancelled. Any other value returns VALIDATION_ERROR (HTTP 400).
order_idstringNoRestrict the list to fulfillments of a single order ID.
pageintegerNo1-based page number for offset pagination. Ignored when cursor is supplied. Default 1.
limitintegerNoPage size. Default 20.
sortstringNo Sort direction by created timestamp: created_at_desc (newest first, default) or created_at_asc. Any other value falls back to the default.
cursorstringNo Opaque keyset cursor returned as meta.nextCursor. When present, switches to cursor pagination: page and sort are ignored, and the response meta omits page/total. A malformed cursor returns VALIDATION_ERROR (HTTP 400).

Example Response — offset pagination (default)

{
  "success": true,
  "data": [
    {
      "id": "ful_01J9WX4K2Q",
      "status": "shipped",
      "orderId": "order_01J9MV7H8S",
      "trackingNumber": "PHJT20260416000123",
      "trackingCompany": "J&T Express",
      "created_at": "2026-04-16T08:00:00Z",
      "updated_at": "2026-04-16T08:05:00Z",
      "items": [
        { "itemId": "item_01J9MV7H8S", "quantity": 2 }
      ]
    }
  ],
  "meta": { "page": 1, "limit": 20, "total": 1 }
}

Example Response — cursor pagination

{
  "success": true,
  "data": [ /* …same Fulfillment objects… */ ],
  "meta": {
    "limit": 50,
    "nextCursor": "eyJ0cyI6IjIwMjYtMDQtMTZU..."
  }
}

Pagination modes: omit cursor for classic page/limit/total offset paging. To walk a very large set without counting rows, pass the previous meta.nextCursor back as ?cursor=; stop when nextCursor is absent.

Get Fulfillment

GET/open/v1/fulfillments/:idScope: fulfillment:read

Returns a single fulfillment by ID. Use the id returned from the list endpoint or from a fulfillment.* webhook payload.

The returned object is the partner-safe projection described in Fulfillment Object — carrier-internal fields (e.g. J&T mailno, bill_code, sortingcode) are stripped at the API boundary.

Example Response

{
  "success": true,
  "data": {
    "id": "ful_01J9WX4K2Q",
    "status": "shipped",
    "orderId": "order_01J9MV7H8S",
    "trackingNumber": "PHJT20260416000123",
    "trackingCompany": "J&T Express",
    "created_at": "2026-04-16T08:00:00Z",
    "updated_at": "2026-04-16T08:05:00Z",
    "items": [
      { "itemId": "item_01J9MV7H8S", "quantity": 2 }
    ]
  }
}

Not Found

Returns NOT_FOUND (HTTP 404) when the fulfillment does not exist or belongs to a different seller. A 404 (not 403) is returned for cross-seller IDs so that the existence of another seller's fulfillment cannot be inferred.

{
  "success": false,
  "error": { "code": "NOT_FOUND", "message": "Fulfillment not found" }
}

Create Fulfillment

POST/open/v1/fulfillmentsScope: fulfillment:write

Create a new fulfillment for an order you own. Specify which items are being fulfilled and optionally include tracking information. Supports partial fulfillment — you can fulfill different items in separate shipments.

The request body is validated strictly; unknown keys return VALIDATION_ERROR (HTTP 400). If the parent order does not exist or belongs to another seller, the endpoint returns NOT_FOUND (HTTP 404) for the order — it does not leak the order's existence.

This endpoint is idempotent: send an Idempotency-Key header to safely retry after a network failure without creating duplicate fulfillments.

Request Body

FieldTypeRequiredDescription
orderIdstringYesThe order to create the fulfillment for. Must belong to your seller. Max 64 chars.
itemsarrayYesArray of { itemId, quantity } objects (1–100 items, each quantity 1–1,000,000).
trackingNumberstringNoCarrier tracking number. Max 120 chars.
trackingCompanystringNoCarrier / shipping company name. Max 80 chars.

Example Request

curl -X POST "https://api.mallplus.com/open/v1/fulfillments" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f3c9e2a-1b4d-4c8a-9e2f-1234567890ab" \
  -d '{
    "orderId": "order_01J9MV7H8S",
    "items": [{ "itemId": "item_01J9MV7H8S", "quantity": 1 }],
    "trackingNumber": "PHJT20260416000123",
    "trackingCompany": "J&T Express"
  }'

Example Response (HTTP 201)

{
  "success": true,
  "data": {
    "id": "ful_01J9WX4K2Q",
    "status": "pending",
    "orderId": "order_01J9MV7H8S",
    "trackingNumber": "PHJT20260416000123",
    "trackingCompany": "J&T Express",
    "created_at": "2026-04-16T08:00:00Z",
    "items": [{ "itemId": "item_01J9MV7H8S", "quantity": 1 }]
  }
}

Webhook: a successful create fires fulfillment.created.

Update Fulfillment

PUT/open/v1/fulfillments/:idScope: fulfillment:write

Update the status and/or tracking information of an existing fulfillment. Use this to mark a fulfillment as shipped, delivered, or cancelled, and to add or update tracking details. All body fields are optional; send only the ones you want to change.

As with the other endpoints, cross-seller writes return NOT_FOUND (HTTP 404), and the body is validated strictly.

Request Body

FieldTypeRequiredDescription
statusstringNo One of pending, shipped, delivered, cancelled.
trackingNumberstringNoUpdated carrier tracking number. Max 120 chars.
trackingCompanystringNoUpdated carrier / shipping company name. Max 80 chars.

Example Request

curl -X PUT "https://api.mallplus.com/open/v1/fulfillments/ful_01J9WX4K2Q" \
  -H "Authorization: Bearer <access_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "status": "shipped",
    "trackingNumber": "PHJT20260416000123",
    "trackingCompany": "J&T Express"
  }'

Webhooks: every successful update fires fulfillment.updated. When the status field on the updated fulfillment is shipped, a separate fulfillment.shipped event is also fired so partners can listen for the shipping-specific signal (mirrors the shipped-event pattern used by Shopee/Lazada/TikTok). Note this is level-triggered on the resulting status, not edge-triggered on a state transition: the event fires on every update whose resulting status is shipped, including a repeat PUT sent on an already-shipped fulfillment (for example, correcting only the tracking company). Make your handler idempotent on fulfillment.shipped.

Fulfillment Object

The partner-safe projection of a fulfillment returned by every endpoint on this page. Field names are camelCase. Carrier-internal metadata (e.g. J&T mailno, bill_code, sortingcode, jt_*) is dropped at the API boundary; only tracking_number and tracking_company are forwarded when present inside the internal metadata blob.

FieldTypeDescription
idstringFulfillment ID.
statusstringOne of pending, shipped, delivered, cancelled.
orderIdstringParent order ID. Omitted when not known.
trackingNumberstringCarrier tracking number. Omitted when not set.
trackingCompanystringCarrier / shipping company name. Omitted when not set.
created_atstring (ISO 8601)Creation timestamp. Omitted when not known.
updated_atstring (ISO 8601)Last-update timestamp. Omitted when not known.
itemsarrayArray of { itemId, quantity } objects. Omitted when empty.
metadataobjectAllowlisted passthrough. Only tracking_number and tracking_company keys ever appear here. Omitted when empty.

Carrier-agnostic contract: the Open API never exposes carrier-specific tracking events or internal identifiers. J&T pickup, AWB/label retrieval, pickup-slot booking, and shipment cancellation are handled internally by MallPlus and are not yet exposed as partner-facing endpoints. When you need to ship an order, create a fulfillment (above) with your own tracking number — MallPlus records it as-is.