For the complete documentation index, see llms.txt. This page is also available as Markdown.

Create Orders/Trackings

The Order API provides a comprehensive, idempotent endpoint for managing order and tracking data in the parcelLab system.

This API allows you to create new orders, update existing orders, add shipment trackings, cancel trackings, cancel orders, and cancel or reduce line items.

Endpoint Details

  • Method: PUT

  • Path: /v4/track/orders/

  • Full URL: https://api.parcellab.com/v4/track/orders/

  • Authentication: Header Authorization: Parcellab-API-Token <token>

This endpoint is idempotent, meaning you can safely call it multiple times with the same data without creating duplicate orders. If an order exists, it will be updated; if not, it will be created.

Authentication

Use the Authorization header with this format:

Authorization: Parcellab-API-Token <token>

API tokens can be created at https://app.parcellab.com/service/account/apitoken/. For PUT /v4/track/orders/, the token must include at least the write scope. Make sure to use the API token (non-encoded version, not the encoded token).

Error Handling

The API returns standard HTTP status codes:

  • 200 OK - Order successfully updated

  • 201 Created - New order created

  • 400 Bad Request - Invalid request data

  • 401 Unauthorized - Missing or invalid authentication

  • 403 Forbidden - Insufficient permissions

  • 404 Not Found - Order not found (for GET requests)

Validation Errors

Payload validation errors reject the whole request with 400 Bad Request. For example, an invalid email address returns:

{
  "type": "client_error",
  "errors": [
    {
      "code": "invalid",
      "detail": "Enter a valid email address.",
      "attr": "recipient_email"
    }
  ]
}

A mutation payload validation error can look like this:

{
  "type": "client_error",
  "errors": [
    {
      "detail": "Cannot cancel 3 of item ITEM-001. Only 2 available.",
      "attr": "mutations.0.line_items.0.quantity_to_cancel",
      "code": "invalid"
    }
  ]
}

The attr value identifies the invalid field. Nested payloads use dot notation with array indexes, such as mutations.0.tracking for the first mutation's tracking object.

If an add_tracking mutation tries to assign a tracking_number + courier pair that already belongs to another order in the same account, the request is also rejected with 400 Bad Request:

{
  "type": "client_error",
  "errors": [
    {
      "code": "invalid",
      "detail": "tracking_number + courier already belongs to order ORD-100045.",
      "attr": "mutations.0.tracking"
    }
  ]
}

To resolve this, check which order already owns the tracking pair. If the shipment belongs to that order, update the existing order. If the tracking was assigned to the wrong order, cancel the existing tracking assignment before adding it to the correct order.

Mutation Results

Accepted requests return 200 OK or 201 Created, but each mutation still includes its own result. Always check mutations[].result.success, mutations[].result.message, and mutations[].result.warnings. The top-level errors[] array is used when the request is rejected; mutations[].result.errors belongs to an accepted mutation result and is usually empty.

{
  "mutations": [
    {
      "type": "add_tracking",
      "tracking": {
        "tracking_number": "794600000001",
        "courier": "fedex"
      },
      "result": {
        "success": true,
        "message": "",
        "errors": {},
        "warnings": [
          "Dropped unrecognized fields from tracking payload: enterprise_code"
        ]
      }
    },
    {
      "type": "change_line_item_quantity",
      "line_item_id": "LINE-404",
      "quantity": 0,
      "result": {
        "success": false,
        "message": "Line item LINE-404 not found",
        "errors": {},
        "warnings": []
      }
    }
  ]
}

Some successful mutation results have an empty message, especially when the mutation is accepted for asynchronous tracking processing.

Example API Call

Use following samples as a starting point for the API interaction. Swap your token in the header (use your token in the same format) and set your account key in the payload.

For sample payloads at different stages of the order lifecycle, see below: Typical Use Cases for OMS and WMS Integration

curl
JavaScript fetch
Python requests

Best Practices

  1. Use Idempotent Updates: The PUT endpoint is idempotent. Use it for both creation and updates to avoid duplicates.

  2. Partial Updates: For scalar order fields, only send the fields you want to update. Lists such as articles_order represent the current order-level list you send and can replace the stored list.

  3. Order Identification: Use either order_number + account or external_id to identify orders. When creating new orders, external_id is generated by parcelLab and returned in the response.

  4. Tracking Identification: Trackings require either tracking_number + courier or external_reference.

  5. Use change_line_item_quantity for line-item cancellations: To cancel or reduce an existing order line, send a change_line_item_quantity mutation with the new absolute quantity. Use quantity: 0 to cancel the line. For full-order cancellation, use the cancel_order mutation instead.

  6. Keep line identifiers unique per logical line: line_item_id should uniquely identify one logical order line. If the same SKU appears on multiple lines, do not reuse the same line_item_id; use the source system's stable line key so each line can be targeted independently.

  7. Keep order and tracking line identifiers aligned: If you send shipment-level tracking.articles, the identifiers in those articles should match the corresponding item in articles_order by line_item_id or order_item_id. This alignment is required for later line-item changes to be reflected correctly on the order status page and in downstream payloads.

  8. Check Mutation Results: An accepted request can still contain mutations[].result.success: false for individual mutation failures. Also check mutations[].result.warnings for accepted payload changes, such as dropped custom tracking fields.

  9. Validate Line Item Quantities: Before cancelling or reducing a line, make sure the new quantity is not below the quantity already shipped.

  10. Send complete address objects: Only include shipping_address or billing_address when all required address details are available. Include address_line, postal_code, city, country_iso3, and either last_name or company_name. If region_code is unknown or invalid for the country, omit it. Do not send partial address objects or unexpected address fields, as invalid address payloads can return 400 Bad Request.

  11. Tags: You can set tags on a tracking to identify custom attributes in filters and exports. For example: tags: ["loyalty_customer", "customer_loyalty:gold", "international_order", "dropship"].

Data Models

Order Schema

For upserts, send only the fields you want to set or update.

Generated/returned by parcelLab
  • external_id (UUID): generated by parcelLab on create.

  • mutations[].operation_id (UUID): generated if not provided.

  • mutations[].result: populated in responses with per-mutation processing status.

Send shipping_address or billing_address only when you can provide a complete address object. Each address object requires address_line, postal_code, city, and country_iso3; blank strings and null values are not accepted for these fields. If a complete shipping address is not available, omit shipping_address and send order-level fields such as destination_country_iso3, recipient_email, recipient_name, and, when available, recipient_postal_code on the tracking instead.

Line Item Order Schema

Represents individual products in articles_order.

Generated/managed by parcelLab
  • shipped_quantity

  • original_quantity

  • status

  • change_reason

  • updated_at

Tracking Schema

Represent an individual shipment, package or parcel used to fulfill (parts of) an order.

Customer-defined tracking keys must be sent inside tracking.additional_fields as key-value pairs. Do not send custom keys such as seller_organization_code or enterprise_code at the top level of the tracking object.

Example custom field structure
Generated/managed by parcelLab
  • cancelled_date can be set by parcelLab when a cancel_tracking mutation is processed.

Example tracking payload as JSON

Additional Data

parcelLab in general allows you to send additional attributes with custom data. For full data-element definitions, refer to the following page.

Data Model

Mutations

Mutations allow you to perform action-oriented changes on an order without replacing the full order payload. They can be used to add or cancel trackings, cancel an order, and change line items.

Supported mutation types are:

  • add_tracking

  • cancel_tracking

  • cancel_order

  • change_line_item_quantity

  • add_line_item

  • replace_line_item

Mutation validation and response behavior

Request-Blocking Validation

Some payload problems reject the whole request with 400 Bad Request. Examples include malformed order fields, invalid email addresses, invalid country or timezone codes, unsupported mutation types, missing tracking identifiers, and tracking payloads that do not match the required shape.

Other problems are reported per mutation in the response. In those cases the request can still return 200 OK or 201 Created, but the affected mutation has mutations[].result.success: false. Examples include changing a line item that is not present on the order, failing a current_quantity guard, or trying to add a line item whose line_item_id already exists.

As a rule of thumb, request validation blocks malformed payloads and conflicts that can be detected before processing starts. Business-rule failures while applying a valid mutation are returned in that mutation's result.

Warnings on Accepted Requests

Non-fatal validation warnings are returned in mutations[].result.warnings. Current warning cases include:

  • Unrecognized top-level fields inside an add_tracking.tracking payload are dropped. Send custom tracking data inside tracking.additional_fields instead.

  • Updating an existing order with both articles_order and mutations can warn when the incoming articles_order list replaces the stored order-level list and omits existing line items.

For accepted requests, inspect every mutations[].result entry. The HTTP status tells you whether the request payload was accepted; the mutation result tells you whether each requested action was applied or whether it produced warnings.

Best-Effort Tracking Conflict Validation

For add_tracking, parcelLab checks the tracking_number + courier pair when both values are present. If the same pair already belongs to a different order in the same account, the request is rejected with 400 Bad Request.

This validation is intentionally scoped:

  • It only runs for add_tracking mutations.

  • It only runs when the mutation contains both tracking_number and courier.

  • It checks for conflicts within the same account.

  • It allows the same pair when it resolves to the same order.

The check is best effort and eventually consistent. It catches common integration mistakes at request time, but it does not replace downstream uniqueness checks. If the latest tracking data is not available during request validation, the request can still be accepted and later processing may still prevent the duplicate tracking from being persisted. A 2xx response means parcelLab accepted the order API request for processing; use the order response, later status lookups, and configured status updates to confirm the final tracking state.

Example rejected response:

AddOrUpdateTrackingMutation

Adds or updates a tracking of an order. Update an existing tracking by running this mutation and using the same values for keys courier and tracking_number, as the combined value of those fields are idempotent.

CancelTrackingMutation

Cancel a tracking by identifying it by its courier and tracking_number.

ChangeLineItemQuantityMutation

Cancel or reduce the open quantity of an order line item. Send the new absolute quantity for the line item. Quantities already assigned to a tracking are considered fulfilled and cannot be cancelled anymore. When current_quantity is provided, parcelLab compares it with the stored quantity and fails the mutation if the values differ.

This is a line-item mutation. Use it when a specific line should be reduced or cancelled by setting its quantity to 0.

If all order lines are changed to quantity: 0, downstream portals may end up displaying the order as cancelled because no active items remain. For an explicit full-order cancellation, use the cancel_order mutation instead of relying only on line-item mutations.

CancelOrderMutation

Cancel an entire order. The server sets order_status to Cancelled and generates cancelled_date when it is not provided. If the payload also contains conflicting top-level order_status, cancelled_date, or cancelled_reason fields, the mutation values take precedence.

Both cancelled_reason and cancelled_date are optional. Valid values for cancelled_reason are customer, inventory, payment, and other.

cancel_order can be combined with other mutations in the same request. For example, sending cancel_order together with add_tracking will process both the tracking update and the order cancellation.

If shipment records already exist and should also be marked as cancelled, send cancel_tracking mutations for those trackings in the same request.

AddLineItemMutation

Adds a line item to an order.

ReplaceLineItemMutation

Use replace_line_item when one order line is substituted by another. parcelLab marks old_line_item_id as cancelled and adds new_line_item as the replacement.

Do not use replace_line_item for a quantity-only cancellation. To cancel or reduce an existing line without adding a replacement, use change_line_item_quantity with the target quantity. Use quantity: 0 to cancel the line.

Typical Use Cases for OMS and WMS Integration

You can use the Order API to update parcelLab about all updates on the order from placement to cancellation or fulfillment.

Integration Flow

  1. Order Placement: Create the order with all known information

  2. Cancellations: Handle line-item cancellations with change_line_item_quantity or full-order cancellations with cancel_order

  3. Fulfillment: Add trackings as items ship

  4. Delivery: System automatically updates based on carrier events (outside of this API)

Sample Mutations

Create a New Order

Request:

Response:

Update an Existing Order

Only send the fields you want to update.

Request:

Add Tracking to an Order

Request:

Cancel a Tracking

Request:

Cancel an Order

Request:

Notes on Order Cancellation:

  • cancelled_date is generated server-side when not provided

  • The mutation sets order_status to Cancelled

  • If the payload contains conflicting top-level cancellation fields, the mutation values take precedence

  • To also cancel existing shipment records, include cancel_tracking mutations in the same request

Cancel or Reduce Line Items

Use change_line_item_quantity to set the new absolute quantity for an existing order line. Use quantity: 0 to cancel the line, or a lower positive quantity for a partial cancellation.

Request:

Notes on Line Item Cancellation:

  • Cancellations are tracked at the order level in articles_order

  • The system validates quantity constraints (including shipped quantity guards)

  • When line items are cancelled, updates are sent to all trackings in the order

  • Line item mutation metadata is stored on the item (for example status, original_quantity, change_reason, updated_at)

Complex Multi-Operation Update

You can combine multiple operations in a single request.

Request:

OMS Mixed Status Example (2 Cancelled, 3 Shipped, 1 Remaining)

Scenario:

  • order_number: OMS-100045

  • Line LINE-1 was ordered with quantity 6

  • OMS then cancels 2

  • FedEx ships 3

  • 1 remains open/backordered for later shipment

Step 1: Create the order (minimal but valid create payload):

Step 2: OMS partial cancellation (64) using change_line_item_quantity:

Step 3: First FedEx shipment (3 units) using add_tracking:

Step 4: Remaining quantity (1) is implicit.

No additional mutation is required to represent "not yet shipped/backordered". At this point:

  • Ordered/open quantity for LINE-1 is 4

  • Shipped quantity recorded across trackings is 3

  • Remaining not-yet-shipped quantity is 1

Step 5a (later): If the remaining 1 ships, send another add_tracking:

Step 5b (alternative): If the remaining 1 is cancelled instead of shipped:

Other Minimal Mutation Payloads (Same order_number Pattern)

Add a line item:

Replace a line item (substitution):

Cancel an existing tracking (e.g., label voided):

FAQs

Where do I get an API token?

Create it in the parcelLab App:

  • Create a token with at least read and write scope

  • Use the non-encoded token value in your API calls

    • If the UI shows both an “encoded” and “non-encoded” value, pick the non-encoded one

  • Send it via this header: Authorization: Parcellab-API-Token <token>

Where do I find my Account ID?

Two common ways:

In the parcelLab App (recommended)

From an encoded token (optional)

  • If you have an encoded token, it can be Base64-decoded into the format: <account_id>:<token>

Why am I getting a 401 Unauthorized error when requesting with my token?

This is almost always one of these:

  • Wrong header format. It must be exactly: Authorization: Parcellab-API-Token <token>

  • Using Bearer instead of Parcellab-API-Token

  • Copy/paste issues (extra whitespace, surrounding quotes)

  • Token scope missing write (required for this endpoint)

  • Account mismatch: The token belongs to a different account than the account you send in the payload

I sent a tracking update with courier and tracking_number and got a positive response, but the tracking record does not show up. What should I check?

Check the response body first, especially mutations[].result.

  • If mutations[].result.success is false, use the message field to identify why the mutation was not applied.

  • If mutations[].result.warnings contains entries, review them before retrying. For example, custom tracking fields sent outside tracking.additional_fields can be dropped from the accepted payload.

  • If the response looks positive but the tracking still does not appear, check whether the same tracking_number + courier already exists on another order in the same account.

parcelLab validates duplicate tracking_number + courier assignments for add_tracking requests when possible. This check is best effort and eventually consistent: a duplicate can be blocked immediately with 400 Bad Request, or the request can be accepted before later processing prevents the duplicate tracking from being persisted.

Why did my request fail with 400 Bad Request?

A 400 Bad Request means the payload failed request-blocking validation and the request was not accepted for processing. The response includes an errors[] list with a detail message and an attr path pointing to the invalid field.

Common causes include invalid email, country, currency, language, or timezone values; missing tracking identifiers; unsupported mutation types; malformed address or line-item payloads; and tracking_number + courier pairs that already belong to another order in the same account.

Fix the field identified by attr and retry the request. If the error points to mutations.0.tracking, it refers to the tracking object in the first mutation.

I sent data and got a 2xx response, but cannot see my data in the app?

Check these in order:

  • You’re in the right account in the app (account switcher in the top-left)

  • The account in your payload matches the account you’re viewing

  • You’re looking at the right area and filters (orders, timeframe, status, etc.)

  • For mutation requests, check mutations[].result.success, mutations[].result.message, and mutations[].result.warnings in the response.

If everything matches and it still doesn’t show up, wait a few minutes. Under times of heavy load ingestion can be deferred in favor of critical events.

Last updated

Was this helpful?