> ## Documentation Index
> Fetch the complete documentation index at: https://docs.arcuserp.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Fulfillment and Returns

> Pack an order, buy a label, and fulfill in one POST. Create an RMA with inline items, auto-calculate restocking fees, and issue a refund atomically.

## Overview

Two resources -- **packages** and **returns** -- cover the post-order lifecycle. Both follow the atomic-create pattern: a single `POST` creates the parent resource plus all inline children in one transactional write, returning the fully hydrated response. You never need follow-up calls to add items or purchase a label.

| Resource     | Endpoint            | Inline children                                      |
| ------------ | ------------------- | ---------------------------------------------------- |
| Package      | `POST /v1/packages` | `items[]`, optional label purchase, optional fulfill |
| Return (RMA) | `POST /v1/returns`  | `items[]`, optional auto-refund, restocking fee      |

***

## Packages

### Canonical scenario: package with items + label + fulfill in one POST

This request creates the package, adds two line items, purchases a Shippo label, and fulfills the package (posts GL entries, sends shipment notification) in a single atomic call.

<CodeGroup>
  ```bash curl theme={null}
  curl -s -X POST "https://api.arcuserp.com/v1/packages" \
    -H "Authorization: Bearer ark_live_ent_wss_abc123xyz" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{
      "order_id": "ORDER_UUID",
      "carrier_service": "ups_ground",
      "items": [
        {
          "order_item_id": "ORDER_ITEM_UUID_1",
          "quantity": 2,
          "serial_numbers": ["SN001", "SN002"]
        },
        {
          "order_item_id": "ORDER_ITEM_UUID_2",
          "quantity": 1
        }
      ],
      "buy_label": true,
      "rate_object_id": "SHIPPO_RATE_OBJECT_ID",
      "fulfill_after_label": true
    }'
  ```

  ```typescript TypeScript theme={null}
  import Arcus from "@arcuserp/typescript-sdk";

  const client = new Arcus({ apiKey: "ark_live_ent_wss_abc123xyz" });

  const pkg = await client.packages.create({
    order_id: "ORDER_UUID",
    carrier_service: "ups_ground",
    items: [
      { order_item_id: "ORDER_ITEM_UUID_1", quantity: 2, serial_numbers: ["SN001", "SN002"] },
      { order_item_id: "ORDER_ITEM_UUID_2", quantity: 1 },
    ],
    buy_label: true,
    rate_object_id: "SHIPPO_RATE_OBJECT_ID",
    fulfill_after_label: true,
  });

  console.log(pkg.data.label_url);       // https://api.goshippo.com/...
  console.log(pkg.data.tracking_number); // 1Z999AA10123456784
  console.log(pkg.data.fulfilled_at);    // 2026-05-12T...
  ```

  ```python Python theme={null}
  from arcuserp import Arcus

  client = Arcus(api_key="ark_live_ent_wss_abc123xyz")

  pkg = client.packages.create(
      order_id="ORDER_UUID",
      carrier_service="ups_ground",
      items=[
          {"order_item_id": "ORDER_ITEM_UUID_1", "quantity": 2, "serial_numbers": ["SN001", "SN002"]},
          {"order_item_id": "ORDER_ITEM_UUID_2", "quantity": 1},
      ],
      buy_label=True,
      rate_object_id="SHIPPO_RATE_OBJECT_ID",
      fulfill_after_label=True,
  )

  print(pkg.data["label_url"])
  print(pkg.data["tracking_number"])
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "data": {
    "id": "PKG_UUID",
    "package_id": "SO-1234-B1",
    "order_id": "ORDER_UUID",
    "entity_id": "ENTITY_UUID",
    "carrier": "ups",
    "service": "ground",
    "status": "packed",
    "fulfillment_status": "fulfilled",
    "items": [
      {
        "id": "PKG_ITEM_UUID_1",
        "order_item_id": "ORDER_ITEM_UUID_1",
        "quantity": 2,
        "serial_numbers": ["SN001", "SN002"]
      },
      {
        "id": "PKG_ITEM_UUID_2",
        "order_item_id": "ORDER_ITEM_UUID_2",
        "quantity": 1
      }
    ],
    "label_url": "https://api.goshippo.com/labels/...",
    "tracking_number": "1Z999AA10123456784",
    "label_purchased_at": "2026-05-12T14:32:00Z",
    "fulfilled_at": "2026-05-12T14:32:05Z",
    "weight": 2.5,
    "length": 12,
    "width": 9,
    "height": 6,
    "created_at": "2026-05-12T14:31:58Z"
  }
}
```

### Inline create options

| Field                 | Type    | Description                                                                                                                                                                                          |
| --------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `order_id`            | string  | Required. Parent sales order UUID.                                                                                                                                                                   |
| `items[]`             | array   | Line items to add. Each item: `order_item_id` (required), `quantity` (required), `serial_numbers[]` (optional, for serialized products).                                                             |
| `carrier_service`     | string  | Compound carrier+service string (`ups_ground`, `fedex_priority_overnight`). Mapped to `carrier` + `service` fields automatically.                                                                    |
| `carrier` + `service` | strings | Alternative to `carrier_service`. Both must be provided.                                                                                                                                             |
| `buy_label`           | boolean | If `true`, purchase a Shippo label after package creation. Requires `rate_object_id`.                                                                                                                |
| `rate_object_id`      | string  | Required with `buy_label: true`. Fetch rates via `GET /v1/packages/:id/rates`.                                                                                                                       |
| `fulfill_after_label` | boolean | If `true`, transition the package to fulfilled after label purchase. Posts GL entries, deducts inventory, queues shipment notification. Only fires if `buy_label: true` and label purchase succeeds. |

### Separate label purchase (two-step flow)

If you prefer to separate rate shopping from package creation:

```bash theme={null}
# Step 1: Create package
PKG_ID=$(curl -s -X POST ".../v1/packages" \
  -d '{"order_id": "ORDER_UUID", "items": [...]}' | jq -r '.data.id')

# Step 2: Fetch rates
RATE_ID=$(curl -s ".../v1/packages/$PKG_ID/rates" | jq -r '.data[0].object_id')

# Step 3: Buy label
curl -s -X POST ".../v1/packages/$PKG_ID/buy-label" \
  -d "{\"rate_object_id\": \"$RATE_ID\"}"

# Step 4: Fulfill
curl -s -X POST ".../v1/packages/$PKG_ID/fulfill"
```

### Package lifecycle

```
draft -> packed -> shipped (fulfilled)
                -> void (label voided, returns to packed)
```

* `POST /v1/packages/:id/buy-label` -- purchase label (transitions to packed + label attached)
* `POST /v1/packages/:id/void-label` -- void active label, request Shippo refund
* `POST /v1/packages/:id/fulfill` -- mark shipped, post GL fulfillment entries
* `GET /v1/tracking-events?package_id=:id` -- live tracking events

### Shippo not configured

If the entity has no Shippo credentials, `buy_label: true` returns:

```json theme={null}
{
  "error": "connector_not_configured",
  "code": "connector_not_configured",
  "type": "connector_error",
  "hint": "Shippo is not configured for this entity. Add credentials in Settings > Integrations.",
  "package": { "id": "PKG_UUID", "..." }
}
```

The package is created and returned in the error body so you can retry `buy-label` after configuring Shippo.

<Note>
  **Webhook delivery:** the `package.shipped` event is queued when a package is fulfilled. Real-time webhook delivery to your endpoint is available in Q3 2026. In the interim, poll `GET /v1/tracking-events` to check shipment status.
</Note>

### Adding items to an existing package

`POST /v1/packages/:id/items` enforces two invariants before inserting:

1. **Item-on-order** -- the product must be on the parent order. Off-order adds return `422 product_not_on_order`.
2. **Quantity cap** -- requested qty must not exceed `ordered_qty - already_packed_qty` across ALL packages on the order. Over-cap adds return `422 package_quantity_exceeds_ordered`.

Either `order_item_id` OR `product_id` may be supplied; the canonical writer resolves the matching order\_item when only `product_id` is provided.

```json title="422 product_not_on_order" theme={null}
{
  "error": "product_not_on_order",
  "code": "product_not_on_order",
  "type": "validation_error",
  "param": "product_id",
  "hint": "This product is not on the parent order. To add it, edit the order first, then return to packing.",
  "product_id": "8a3f...",
  "order_id": "c7e1..."
}
```

```json title="422 package_quantity_exceeds_ordered" theme={null}
{
  "error": "package_quantity_exceeds_ordered",
  "code": "package_quantity_exceeds_ordered",
  "type": "validation_error",
  "param": "quantity",
  "hint": "You can pack at most 2 more units of this product on this order (3 already packed; 5 ordered).",
  "ordered_qty": 5,
  "already_packed_qty": 3,
  "remaining_qty": 2,
  "product_id": "8a3f..."
}
```

To preview what's still packable on an order, call `GET /v1/orders/:id/items?packable=true`. The response includes one row per `(order_id, product_id)` with `ordered_qty`, `already_packed_qty`, `remaining_qty`.

***

## Handling pack leftovers

When Pack with Arcus (PackPilot) cannot fit one or more items into any available box, those items are returned as *leftovers* rather than silently dropped. Each leftover is persisted to `fulfillment.pack_leftovers` and surfaced to the warehouse operator on the Pack Order page and the Order Detail > Fulfillment tab.

### Listing leftovers for an order

```bash theme={null}
GET /v1/fulfillment/orders/:order_id/leftovers
```

Returns `{ data: [...rows], total, unresolved_count }`. Each row includes:

| Field               | Type             | Description                                                                                                                 |
| ------------------- | ---------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `reason`            | `string`         | Rejection code (see table below). Always present; defaults to `unknown_packpilot_rejection` when PackPilot omits a code.    |
| `reason_detail`     | `object \| null` | Structured dim comparison. Only present for `packpilot_item_too_large`. Shape: `{ item_dims, max_box_dims, axis_failure }`. |
| `quantity`          | `string`         | Units that could not be packed.                                                                                             |
| `resolved_at`       | `string \| null` | ISO-8601 timestamp when resolved; null if still unresolved.                                                                 |
| `resolution_action` | `string \| null` | How the operator resolved it: `packed_manually`, `dismissed`, `backordered`, or `split_to_new_order`.                       |

### Rejection reason codes

| Code                          | Root cause                                                    | Operator action                                                        |
| ----------------------------- | ------------------------------------------------------------- | ---------------------------------------------------------------------- |
| `packpilot_category_mismatch` | No box's Allowed Categories list includes the item's category | Edit a box and add the item's category to its Allowed Categories list  |
| `packpilot_item_too_large`    | Item dimensions exceed every allowed box on at least one axis | Add a larger box in Products or correct the item's shipping dimensions |
| `packpilot_weight_exceeded`   | Item weight exceeds max\_weight on every viable box           | Increase box max weight or use a sturdier box type                     |
| `packpilot_no_boxes`          | Entity has zero active box products                           | Add boxes in Products                                                  |
| `packpilot_circuit_open`      | PackPilot circuit breaker open (too many recent errors)       | Retry in a few minutes or create packages manually                     |
| `unknown_packpilot_rejection` | PackPilot did not return a reason code                        | Inspect the item and resolve manually                                  |

### Dim comparison (packpilot\_item\_too\_large)

When the rejection is `packpilot_item_too_large`, the `reason_detail` object provides exact dimensions so you can diagnose without a second lookup:

```json theme={null}
{
  "reason": "packpilot_item_too_large",
  "reason_detail": {
    "item_dims": [2.4, 1.4, 9.0],
    "max_box_dims": [8.0, 8.0, 4.0],
    "axis_failure": 2
  }
}
```

`axis_failure` is the index of the first failing axis (0=length, 1=width, 2=height). In the example above, index 2 means the item's height (9.0 in) does not fit the box's inner height (4.0 in).

### Resolving a leftover

```bash theme={null}
POST /v1/fulfillment/leftovers/:id/resolve
```

Four resolution actions are supported:

* `packed_manually` -- add the units to an existing package. Requires `package_id`. Optional partial `quantity`.
* `dismissed` -- operator override with a mandatory `notes` field (audit trail).
* `backordered` -- promote to a customer backorder. Optional `quantity` (partial) and `expected_date`.
* `split_to_new_order` -- create a new sales order for the full leftover quantity, linked to the original.

***

## Returns (RMAs)

### Canonical scenario: return with items + restocking fee + auto-refund

This creates the RMA, adds line items, and issues the refund in a single atomic call.

<CodeGroup>
  ```bash curl theme={null}
  curl -s -X POST "https://api.arcuserp.com/v1/returns" \
    -H "Authorization: Bearer ark_live_ent_wss_abc123xyz" \
    -H "Content-Type: application/json" \
    -H "Idempotency-Key: $(uuidgen)" \
    -d '{
      "original_order_id": "ORDER_UUID",
      "reason": "wrong_item",
      "refund_amount": 150.00,
      "items": [
        {
          "order_item_id": "ORDER_ITEM_UUID_1",
          "quantity": 1,
          "condition": "unused"
        }
      ],
      "restocking_fee": { "type": "percentage", "value": 10 },
      "auto_refund": true,
      "refund_payment_id": "PAYMENT_UUID"
    }'
  ```

  ```typescript TypeScript theme={null}
  import Arcus from "@arcuserp/typescript-sdk";

  const client = new Arcus({ apiKey: "ark_live_ent_wss_abc123xyz" });

  const rma = await client.returns.create({
    original_order_id: "ORDER_UUID",
    reason: "wrong_item",
    refund_amount: 150.00,
    items: [
      { order_item_id: "ORDER_ITEM_UUID_1", quantity: 1, condition: "unused" },
    ],
    restocking_fee: { type: "percentage", value: 10 },
    auto_refund: true,
    refund_payment_id: "PAYMENT_UUID",
  });

  console.log(rma.data.return_number);     // RMA-00042
  console.log(rma.data.restocking_fee);    // 15  (10% of 150)
  console.log(rma.data.items.length);      // 1
  console.log(rma.data.refund?.id);        // refund record id
  ```

  ```python Python theme={null}
  from arcuserp import Arcus

  client = Arcus(api_key="ark_live_ent_wss_abc123xyz")

  rma = client.returns.create(
      original_order_id="ORDER_UUID",
      reason="wrong_item",
      refund_amount=150.00,
      items=[
          {"order_item_id": "ORDER_ITEM_UUID_1", "quantity": 1, "condition": "unused"},
      ],
      restocking_fee={"type": "percentage", "value": 10},
      auto_refund=True,
      refund_payment_id="PAYMENT_UUID",
  )

  print(rma.data["return_number"])  # RMA-00042
  print(rma.data["restocking_fee"]) # 15
  ```
</CodeGroup>

### Response

```json theme={null}
{
  "data": {
    "id": "RETURN_UUID",
    "return_number": "RMA-00042",
    "entity_id": "ENTITY_UUID",
    "original_order_id": "ORDER_UUID",
    "status": "authorized",
    "reason": "wrong_item",
    "refund_amount": 150.0,
    "restocking_fee": 15.0,
    "restocking_fee_auto_applied": false,
    "items": [
      {
        "id": "RETURN_ITEM_UUID",
        "order_item_id": "ORDER_ITEM_UUID_1",
        "quantity_authorized": 1,
        "condition": "unused",
        "disposition": "pending"
      }
    ],
    "refund": {
      "id": "REFUND_UUID",
      "amount": 135.0,
      "payment_method": "card",
      "status": "succeeded"
    },
    "created_at": "2026-05-12T14:32:00Z"
  }
}
```

### Inline create options

| Field                | Type             | Description                                                                                                                                                  |
| -------------------- | ---------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `original_order_id`  | string           | Optional. Link to the originating sales order.                                                                                                               |
| `reason`             | string           | Free-text reason for the return.                                                                                                                             |
| `refund_amount`      | number           | Total dollar amount to refund (before restocking fee).                                                                                                       |
| `items[]`            | array            | Line items to return. Fields: `order_item_id` (recommended), `quantity` (alias for `quantity_authorized`), `condition` (enum: `new/used/damaged/defective`). |
| `restocking_fee`     | number or object | Flat dollar amount OR `{ "type": "percentage", "value": 10 }` (10% of `refund_amount`). Also accepts `{ "type": "flat", "value": 30 }`.                      |
| `auto_refund`        | boolean          | If `true`, issue the refund immediately after RMA creation. Requires `refund_payment_id`.                                                                    |
| `refund_payment_id`  | string           | Required with `auto_refund: true`. The original payment UUID to refund against.                                                                              |
| `refund_method`      | string           | Optional. `original` (default), `store_credit`, `check`, `cash`.                                                                                             |
| `auto_authorize`     | boolean          | Ignored (RMA is always created in `authorized` state unless entity settings require inspection).                                                             |
| `override_window`    | boolean          | If `true`, bypasses the entity's return-window-days guard.                                                                                                   |
| `customer_initiated` | boolean          | Set to `true` when the return was initiated via the customer portal.                                                                                         |

### Restocking fee formats

```json theme={null}
// Flat dollar amount
"restocking_fee": 25.00

// 10% of refund_amount
"restocking_fee": { "type": "percentage", "value": 10 }

// Explicit flat type (same as flat number)
"restocking_fee": { "type": "flat", "value": 25.00 }
```

All three formats store the computed flat amount in `returns.restocking_fee` and return it as a JS number.

### RMA lifecycle

```
authorized -> expected (label sent to customer) -> received -> inspecting? -> restocked / written_off / sent_to_vendor -> closed
authorized -> cancelled
```

* `POST /v1/returns/:id/receive` -- record inbound goods, restore inventory
* `POST /v1/returns/:id/inspect` -- flag for QC inspection (if entity settings require)
* `POST /v1/returns/:id/disposition` -- restock / write off / send to vendor
* `POST /v1/returns/:id/refund` -- issue explicit refund (if not done inline)
* `POST /v1/returns/:id/cancel` -- cancel the RMA, release holds

### Receiving and disposition (separate calls)

After the physical goods arrive:

```bash theme={null}
# Receive the goods (restores inventory)
curl -X POST ".../v1/returns/$RMA_ID/receive" \
  -d '{ "items": [{ "return_item_id": "ITEM_UUID", "quantity_received": 1 }] }'

# Disposition (posts COGS reversal GL entries)
curl -X POST ".../v1/returns/$RMA_ID/disposition" \
  -d '{ "items": [{ "return_item_id": "ITEM_UUID", "outcome": "restock" }] }'
```

### Return-window enforcement

If `entities.settings.return_window_days` is set, returns outside the window are rejected with `422 outside_return_window`. Pass `override_window: true` to bypass (requires `returns:write` scope).

***

## Validating serials on return

For serialized products, Arcus can verify that the scanned serial was actually sold on the order being returned before persisting it. This prevents swap fraud and inventory drift.

### Entity setting

Configure the validation mode in Settings > Shipping & Fulfillment > Returns, or via the API:

```bash theme={null}
curl -X PATCH ".../v1/entities/$ENTITY_ID/settings" \
  -H "Authorization: Bearer $API_KEY" \
  -d '{ "settings": { "returns": { "serial_validation_mode": "hard" } } }'
```

| Mode   | Behavior                                                                       |
| ------ | ------------------------------------------------------------------------------ |
| `off`  | Skip validation (default, backwards-compatible).                               |
| `warn` | Validate, log mismatches server-side, let the receive/disposition proceed.     |
| `hard` | Validate and reject with `422` if the serial was not on the original shipment. |

### Probing a serial before submit

Call `POST /v1/serials/validate-for-return` to check a serial before submitting the receive. The endpoint returns a verdict without blocking the caller:

```bash theme={null}
curl -X POST "https://api.arcuserp.com/v1/serials/validate-for-return" \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "serial_number": "SN001",
    "original_order_id": "ORDER_UUID",
    "product_id": "PRODUCT_UUID"
  }'
```

Response shapes:

```json theme={null}
// Serial is valid for this return
{ "data": { "valid": true, "serial_id": "SN_UUID", "status": "sold", "order_number": "SO-0042" } }

// Serial does not match the original order
{ "data": { "valid": false, "code": "serial_not_on_original_order", "hint": "Serial SN001 is linked to a different order." } }

// No original_order_id was supplied (cannot validate)
{ "data": { "valid": null, "reason": "no_original_order" } }
```

### Error codes (hard mode)

When `serial_validation_mode` is `hard` and a receive/disposition is submitted with a mismatched serial, the endpoint returns `422` with a structured error:

| Code                           | Meaning                                                      |
| ------------------------------ | ------------------------------------------------------------ |
| `serial_not_found`             | No serial with that number exists for this entity + product. |
| `serial_not_on_original_order` | Serial exists but was not sold on this RMA's source order.   |
| `serial_not_sold`              | Serial is not in `sold` status (cannot be returned).         |
| `serial_already_returned`      | Serial has already been returned on a previous RMA.          |

***

## Scopes required

| Operation                                                   | Scope                                 |
| ----------------------------------------------------------- | ------------------------------------- |
| List / read packages or returns                             | `fulfillment:read` / `returns:read`   |
| Create / update / fulfill packages                          | `fulfillment:write`                   |
| Create / receive / disposition returns                      | `returns:write`                       |
| Refund (via `POST /v1/returns/:id/refund` or `auto_refund`) | `returns:write` + `payments:write`    |
| Create exchange order                                       | `returns:write` + `orders:write`      |
| Create return label                                         | `returns:write` + `fulfillment:write` |
| Capture serials (full or partial)                           | `serials:write`                       |

***

## Serial capture during pack

Serialized products require one serial number per unit before a package can be fulfilled. Arcus supports two capture flows: full capture (all serials at once) and partial capture (progressively scan as you pick).

### Full capture

Pass `raw_barcodes[]` in the capture request. The backend runs barcode parsing (prefix stripping, format normalization, lot extraction) automatically.

```bash curl theme={null}
curl -s -X POST "https://api.arcuserp.com/v1/serials/capture" \
  -H "Authorization: Bearer ark_live_ent_wss_abc123xyz" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "package_item_id": "PKG_ITEM_UUID",
    "product_id": "PRODUCT_UUID",
    "raw_barcodes": ["1234567890123", "9876543210987"]
  }'
```

Response: `{ data: { captured_count, total_required, remaining, serials[], partial } }`.

### Partial capture (N of total)

Use `POST /v1/serials/capture-partial` when picking units one at a time before all units are in hand. The endpoint accepts the same shape as `/v1/serials/capture` and returns a `partial: true` flag when `remaining > 0`.

```bash curl theme={null}
curl -s -X POST "https://api.arcuserp.com/v1/serials/capture-partial" \
  -H "Authorization: Bearer ark_live_ent_wss_abc123xyz" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: $(uuidgen)" \
  -d '{
    "package_item_id": "PKG_ITEM_UUID",
    "product_id": "PRODUCT_UUID",
    "raw_barcodes": ["1234567890123"]
  }'
```

```json theme={null}
{
  "data": {
    "captured_count": 1,
    "total_required": 3,
    "remaining": 2,
    "partial": true,
    "serials": [{ "id": "...", "serial_number": "1234567890123", "status": "allocated" }]
  }
}
```

Call the endpoint again as each additional unit is scanned. When `remaining` reaches 0 and `partial: false`, the line is ready to fulfill.

### Release a serial (before order closes)

`DELETE /v1/serials/:id/release` removes a serial from its package item so it can be re-scanned or re-assigned. This endpoint is blocked once the order is in a terminal state:

| Condition                                                | Response                                  |
| -------------------------------------------------------- | ----------------------------------------- |
| Order `fulfilled`, `cancelled`, `archived`, or `expired` | `422 serial_release_blocked_order_locked` |
| Package `fulfillment_status = fulfilled`                 | `422 serial_release_blocked_order_locked` |

When an order is already fulfilled, use the RMA flow (`POST /v1/returns`) to return the serial unit rather than releasing it directly.

***

## Error handling

All errors follow the `{ error, code, type, hint }` envelope. Partial-success responses (e.g. package created but label failed) include the created resource in the response body alongside the error:

```json theme={null}
{
  "error": "label_purchase_failed",
  "code": "label_purchase_failed",
  "type": "connector_error",
  "hint": "Package was created but label purchase failed. Retry via POST /v1/packages/:id/buy-label.",
  "package": { "id": "PKG_UUID", "..." }
}
```

This lets you recover without duplicating the package -- just retry `buy-label` using the `package.id` from the error body.
