# Orders

Dropship orders: we pick, pack and ship each order from your stock in our warehouse to your buyer. Orders come from connected stores, file imports, the portal and the API; all of them are listed here.

## How an order moves

- An order that is missing something is saved with status `NEEDS_ATTENTION`, and `problems` says what to fix. Otherwise it goes on to fulfilment.
- Stock is reserved automatically when you save the order: it becomes `RESERVED`, or `BACKORDERED` when stock is short. Our warehouse then weighs (`WEIGHED`) and ships (`SHIPPED`) it. `CANCELLED` ends it.
- Only orders with status `NEEDS_ATTENTION` can be changed or cancelled through the API (`409 order_not_editable` otherwise). For other orders, contact your account manager.
- Webhook `order.status_changed` reports every status change, and `order.awaiting_payment` tells you when the shipping fee is waiting for funds ([Webhook events](https://docs.rephut.com/developers/webhooks/events)).

## Order statuses

| Value | Meaning |
|---|---|
| `NEEDS_ATTENTION` | Something must be fixed before we can fulfil the order; `problems` says what. Fix it with PATCH or cancel it. `statusLabel` names the first problem. |
| `READY` | Ready for fulfilment: the order has no problems and waits for our warehouse. This is usually brief. An order also returns here if an unpaid shipping hold expires and its stock is released. |
| `BACKORDERED` | Waiting for stock: some products are short; `backorder` lists them and the purchase orders bringing them. |
| `RESERVED` | Being packed: the stock is set aside and our warehouse is packing the order. If your available balance didn't cover the shipping and handling fee when the parcel was weighed, the order stays here and `payment` shows the amount due. |
| `WEIGHED` | Ready to ship: the parcel is weighed and the shipping and handling fee is charged to your wallet. |
| `SHIPPED` | Shipped. `fulfilment` has the tracking number and the amounts charged. An order your store marked as fulfilled has `shippedInStore: true`, is not charged and may have no `fulfilment`; the store can reopen it. |
| `CANCELLED` | Cancelled. `cancelledAt` says when. |

## Order problems

`problems` lists why an order needs attention. Rely on `code`; `message` is English text that may change.

| Value | Meaning |
|---|---|
| `NO_LINES` | The order has no items. |
| `UNBOUND_LINE` | A line's SKU isn't mapped to one of your products. Map it with POST /v1/sku-mappings or set the line's `productId`. |
| `MISSING_RECIPIENT` | The recipient's name or address is incomplete. |
| `UNKNOWN_COUNTRY` | The country is missing or isn't one we ship to. |
| `NO_CHANNEL` | No shipping option is chosen and your shipping preference doesn't pick one for this country. |
| `CHANNEL_UNAVAILABLE` | The chosen shipping option can't take this order, for example because of the country or the weight. |

## Creating orders

1. Map your SKUs ([SKU mappings](https://docs.rephut.com/developers/reference/sku-mappings)), or put our `productId` on each line.
2. Check the order with [`POST /v1/orders/preview`](https://docs.rephut.com/developers/reference/orders#check-a-new-order-without-saving-it): problems, shipping options and prices, the wallet hold and stock shortages. Nothing is saved.
3. Create it with [`POST /v1/orders`](https://docs.rephut.com/developers/reference/orders#create-an-order) and an `Idempotency-Key`.
4. If it comes back `NEEDS_ATTENTION`, fix it with [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention) (send the whole order again) or cancel it with [`POST /v1/orders/{id}/cancel`](https://docs.rephut.com/developers/reference/orders#cancel-an-order-that-needs-attention).

Shipping: leave `channelId` empty to use your shipping preference for the country (set in the portal), or send a `channelId` from `shippingOptions` in the preview. `storeOrderNo` must be unique among your orders (`409 store_order_no_taken`); leave it empty to use our order number. For an order from a connected store, `storeOrderNo` can't be changed: a different value is rejected with `store_order_no_locked`.

Read [Charges and your wallet](https://docs.rephut.com/developers/charges) first: an order without problems holds its estimated shipping and handling fee in your wallet once its stock is fully reserved (a `BACKORDERED` order holds nothing yet). The full walk-through is in [From order to delivery](https://docs.rephut.com/developers/guides/order-to-shipment).

## List orders

`GET /v1/orders`

Newest first (by creation time). Filter by one `status`, or by `updatedSince` to get orders changed at or after that time. To sync without webhooks, note the time just before you request page 1, read every page with the same `updatedSince`, then use that time minus a safety margin (for example 5 minutes) as `updatedSince` next time. Expect to see some orders again and match them on `id`. Any edit through `PATCH /v1/orders/{id}` changes `updatedAt`.

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/orders` |
| Permission | `orders:read` |
| Access | Read: counts towards the read limit and works while your account is on hold |
| Idempotency-Key | Not used |
| Success | 200 OK |

### Query parameters

| Field | Type | Required | Description |
|---|---|---|---|
| `page` | integer | No | Page number. See [Pagination](https://docs.rephut.com/developers/pagination). From 1 to 10,000. Default: `1`. |
| `status` | string | No | One order status, uppercase, for example SHIPPED. One of `NEEDS_ATTENTION`, `READY`, `BACKORDERED`, `RESERVED`, `WEIGHED`, `SHIPPED`, `CANCELLED`. |
| `updatedSince` | string (date-time) | No | ISO 8601 date-time with a time zone, for example 2026-11-01T00:00:00Z. Send a + in an offset as %2B (2026-11-01T08:00:00%2B08:00), or it arrives as a space and is rejected. |

### Headers

| Header | Required | Value |
|---|---|---|
| `Authorization` | Yes | `Bearer <your API key>`. See [Authentication](https://docs.rephut.com/developers/authentication). |

### Response

`200` with `data`, a list of objects with the fields below (up to one page), and `meta` with `total`, `page`, `limit` and `pageCount`. See [Pagination](https://docs.rephut.com/developers/pagination).

| Field | Type | Description |
|---|---|---|
| `id` | string | Order id. |
| `number` | string | Our order number. |
| `storeOrderNo` | string | Your store's order number; empty when you didn't give one. |
| `status` | string | Order status. See [Order statuses](#order-statuses). One of `NEEDS_ATTENTION`, `READY`, `BACKORDERED`, `RESERVED`, `WEIGHED`, `SHIPPED`, `CANCELLED`. |
| `statusLabel` | string | English text for display. It may change; rely on the code next to it. For `NEEDS_ATTENTION` it names the first problem, for example "SKU not mapped". |
| `recipientName` | string | Recipient name. |
| `country` | string | Destination country in English. If we don't recognise it, the text the order has, or the code itself when the code is unknown; "—" when there is neither. |
| `countryCode` | string or null | Two-letter destination country code; null when we don't recognise the country. |
| `itemCount` | integer | Total quantity of all lines. |
| `lines` | array of objects | The items in the order. |
| `lines[].productId` | string or null | Our product id; null while the line is not matched to a product. |
| `lines[].productSku` | string or null | Our SKU of the matched product; null while the line isn't matched. |
| `lines[].productName` | string or null | Name of the matched product; null while the line isn't matched. |
| `lines[].customerSku` | string | Your SKU on this line. |
| `lines[].title` | string | Item name from your store or file. |
| `lines[].qty` | integer | Quantity. |
| `createdAt` | string (date-time) | When the order was created. |
| `updatedAt` | string (date-time) | When the order last changed. Any edit through [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention) changes it. |

### Errors

| Code | HTTP status | Meaning |
|---|---|---|
| `invalid_api_key` | 401 | The API key is missing, malformed, revoked or no longer valid. |
| `account_action_required` | 403 | Sign in to the portal and finish the required account steps first. |
| `api_not_enabled` | 403 | API access is not enabled for this account. |
| `insufficient_scope` | 403 | The API key does not have the permission this call needs. |
| `validation_failed` | 422 | The input is not valid. `details` lists each field and the reason. |
| `rate_limited` | 429 | Too many requests. Wait for the number of seconds in Retry-After. |
| `internal_error` | 500 | Unexpected error. Quote X-Request-Id when you contact support. |

Handle errors by `code`. See [Errors](https://docs.rephut.com/developers/errors).

```bash example=curl
curl 'https://api.rephut.com/v1/orders?updatedSince=2026-11-01T00%3A00%3A00Z' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY"
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/orders?updatedSince=2026-11-01T00%3A00%3A00Z", {
  headers: {
    Authorization: `Bearer ${process.env.FULFILLBOT_API_KEY}`,
  },
});
const result = await response.json();
console.log(response.status, result);
```

```python example=python
import os

import requests

response = requests.get(
    "https://api.rephut.com/v1/orders?updatedSince=2026-11-01T00%3A00%3A00Z",
    headers={
        "Authorization": f"Bearer {os.environ['FULFILLBOT_API_KEY']}",
    },
    timeout=30,
)
print(response.status_code, response.json())
```

```json example=response status=200
{
  "success": true,
  "data": [
    {
      "id": "cm3o7rd0e0001h2j3k4l5m6n",
      "number": "D2611090012",
      "storeOrderNo": "1042",
      "status": "READY",
      "statusLabel": "Ready for fulfilment",
      "recipientName": "Alex Morgan",
      "country": "United States",
      "countryCode": "US",
      "itemCount": 2,
      "lines": [
        {
          "productId": "cm3p4r0d0001xyz7k2m9q8w1",
          "productSku": "FB-10231-BLK-M",
          "productName": "Cotton crew neck T-shirt",
          "customerSku": "TEE-BLK-M",
          "title": "Crew neck T-shirt, black, M",
          "qty": 2
        }
      ],
      "createdAt": "2026-11-09T02:00:00.000Z",
      "updatedAt": "2026-11-09T02:00:00.000Z"
    },
    {
      "id": "cm3o7rd0e0000h2j3k4l5m6m",
      "number": "D2611050007",
      "storeOrderNo": "1039",
      "status": "SHIPPED",
      "statusLabel": "Shipped",
      "recipientName": "Alex Morgan",
      "country": "United Kingdom",
      "countryCode": "GB",
      "itemCount": 1,
      "lines": [
        {
          "productId": "cm3p4r0d0001xyz7k2m9q8w1",
          "productSku": "FB-10231-BLK-M",
          "productName": "Cotton crew neck T-shirt",
          "customerSku": "TEE-BLK-M",
          "title": "Crew neck T-shirt, black, M",
          "qty": 1
        }
      ],
      "createdAt": "2026-11-05T01:20:00.000Z",
      "updatedAt": "2026-11-07T10:05:00.000Z"
    }
  ],
  "meta": {
    "total": 2,
    "page": 1,
    "limit": 50,
    "pageCount": 1
  }
}
```

## Get an order

`GET /v1/orders/{id}`

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/orders/{id}` |
| Permission | `orders:read` |
| Access | Read: counts towards the read limit and works while your account is on hold |
| Idempotency-Key | Not used |
| Success | 200 OK |

### Path parameters

| Name | Type | Description |
|---|---|---|
| `id` | string | Order id, from [`GET /v1/orders`](https://docs.rephut.com/developers/reference/orders#list-orders) or the response of [`POST /v1/orders`](https://docs.rephut.com/developers/reference/orders#create-an-order). An id that doesn't exist, isn't yours or isn't a valid id returns 404 `not_found`. |

### Headers

| Header | Required | Value |
|---|---|---|
| `Authorization` | Yes | `Bearer <your API key>`. See [Authentication](https://docs.rephut.com/developers/authentication). |

### Response

`200` with `data`, an object with the fields below.

| Field | Type | Description |
|---|---|---|
| `id` | string | Order id. |
| `number` | string | Our order number. |
| `storeOrderNo` | string | Your store's order number; empty when you didn't give one. |
| `status` | string | Order status. See [Order statuses](#order-statuses). One of `NEEDS_ATTENTION`, `READY`, `BACKORDERED`, `RESERVED`, `WEIGHED`, `SHIPPED`, `CANCELLED`. |
| `statusLabel` | string | English text for display. It may change; rely on the code next to it. For `NEEDS_ATTENTION` it names the first problem, for example "SKU not mapped". |
| `sourceLabel` | string | Where the order came from: `Store` (a connected store), `Imported` (a file import) or `Manual` (the portal form or the API). English text; it may change. |
| `createdAt` | string (date-time) | When the order was created. |
| `updatedAt` | string (date-time) | When the order last changed. Any edit through [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention) changes it. |
| `cancelledAt` | string (date-time) or null | When the order was cancelled; null otherwise. |
| `recipient` | object | Who we ship to. |
| `recipient.name` | string | Recipient name. |
| `recipient.company` | string | Company; empty when none. |
| `recipient.phone` | string | Recipient phone number. |
| `recipient.email` | string | Recipient email address. |
| `recipient.address1` | string | Address line 1. |
| `recipient.address2` | string | Address line 2. |
| `recipient.city` | string | City. |
| `recipient.province` | string | State or province. |
| `recipient.postalCode` | string | Postal code. |
| `recipient.country` | string | Country in English. If we don't recognise it, the text the order has, or the code itself when the code is unknown; "—" when there is neither. |
| `recipient.countryCode` | string or null | Two-letter country code; null when we don't recognise the country. |
| `buyerNote` | string | Note from your buyer. |
| `lines` | array of objects | The items in the order. |
| `lines[].productId` | string or null | Our product id; null while the line is not matched to a product. |
| `lines[].productSku` | string or null | Our SKU of the matched product; null while the line isn't matched. |
| `lines[].productName` | string or null | Name of the matched product; null while the line isn't matched. |
| `lines[].customerSku` | string | Your SKU on this line. |
| `lines[].title` | string | Item name from your store or file. |
| `lines[].qty` | integer | Quantity. |
| `shippingChannelLabel` | string | The shipping option the order uses, in English; `Assigned by our team` when we chose it, `—` when none is set yet. |
| `shippedInStore` | boolean | `true` when you fulfilled the order yourself in your store, so we don't ship it. |
| `currency` | string | Currency of every amount in this order. Always `USD`. |
| `estimatedShippingCents` | integer or null | Estimated shipping in US cents; null when it can't be estimated yet, and when `shippedInStore` is `true`. |
| `weightEstimated` | boolean | `true` when a product's weight is unknown and the estimate uses an assumed weight. Only before the parcel is weighed; `false` afterwards. |
| `problems` | array of objects | Why the order needs attention; empty otherwise. Rely on `code`; `message` is English text that may change. |
| `problems[].code` | string | Problem code. See [Order problems](#order-problems). One of `NO_LINES`, `UNBOUND_LINE`, `MISSING_RECIPIENT`, `UNKNOWN_COUNTRY`, `NO_CHANNEL`, `CHANNEL_UNAVAILABLE`. |
| `problems[].message` | string | English text for display. It may change; rely on the code next to it. |
| `fulfilment` | object or null | What our warehouse did with the parcel. Present when the status is `RESERVED`, `WEIGHED` or `SHIPPED`; null otherwise. |
| `fulfilment.trackingNo` | string | Tracking number; empty until the parcel ships. |
| `fulfilment.trackingUrl` | string or null | Link to track the parcel; null when we have none. |
| `fulfilment.shippedAt` | string (date-time) or null | When the parcel shipped; null until then. |
| `fulfilment.actualWeightG` | integer or null | Weighed parcel weight in grams; null until weighed. |
| `fulfilment.freightCents` | integer or null | Shipping charged, in US cents; null until charged. |
| `fulfilment.handlingFeeCents` | integer or null | Handling fee charged, in US cents; null until charged. |
| `fulfilment.surchargeCents` | integer or null | Extra shipping charged later (for example a carrier adjustment), in US cents; null when none. |
| `fulfilment.totalChargedCents` | integer or null | Everything charged for this order, in US cents; null until charged. |
| `payment` | object or null | Shipping charge waiting for wallet funds; null when nothing is due. |
| `payment.freightCents` | integer | Shipping fee waiting to be paid, in US cents. |
| `payment.handlingFeeCents` | integer | Handling fee waiting to be paid, in US cents. |
| `payment.dueCents` | integer | Total waiting to be paid, in US cents. |
| `payment.availableCents` | integer | Your available balance plus the amount already held for this order, in US cents. |
| `payment.topUpCents` | integer | At least this much to top up. |
| `shippingHold` | object or null | The estimated shipping and handling fee held from your available balance while the order waits to ship; null when nothing is held. See [Charges and your wallet](https://docs.rephut.com/developers/charges). |
| `shippingHold.heldCents` | integer | Amount held, in US cents. |
| `shippingHold.topUpCents` | integer | Top up at least this much when your balance doesn't cover the hold; 0 when it does. |
| `backorder` | object or null | What the order is waiting for when some products are short; null otherwise. |
| `backorder.lines` | array of objects | Products short for this order. |
| `backorder.lines[].productSku` | string | Our SKU. |
| `backorder.lines[].productName` | string | Product name. |
| `backorder.lines[].orderedQty` | integer | Quantity ordered. |
| `backorder.lines[].waitingQty` | integer | Quantity still waiting for stock. |
| `backorder.purchaseOrders` | array of objects | Purchase orders that bring the missing stock. |
| `backorder.purchaseOrders[].id` | string | Purchase order id. Fetch it with [`GET /v1/purchase-orders/{id}`](https://docs.rephut.com/developers/reference/purchase-orders#get-a-purchase-order). |
| `backorder.purchaseOrders[].number` | string | Purchase order number. |
| `backorder.purchaseOrders[].statusLabel` | string | English text for display. It may change; rely on the code next to it. |

### Errors

| Code | HTTP status | Meaning |
|---|---|---|
| `invalid_api_key` | 401 | The API key is missing, malformed, revoked or no longer valid. |
| `account_action_required` | 403 | Sign in to the portal and finish the required account steps first. |
| `api_not_enabled` | 403 | API access is not enabled for this account. |
| `insufficient_scope` | 403 | The API key does not have the permission this call needs. |
| `not_found` | 404 | The resource does not exist or does not belong to this account. |
| `validation_failed` | 422 | The input is not valid. `details` lists each field and the reason. |
| `rate_limited` | 429 | Too many requests. Wait for the number of seconds in Retry-After. |
| `internal_error` | 500 | Unexpected error. Quote X-Request-Id when you contact support. |

Handle errors by `code`. See [Errors](https://docs.rephut.com/developers/errors).

```bash example=curl
curl 'https://api.rephut.com/v1/orders/cm3o7rd0e0001h2j3k4l5m6n' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY"
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/orders/cm3o7rd0e0001h2j3k4l5m6n", {
  headers: {
    Authorization: `Bearer ${process.env.FULFILLBOT_API_KEY}`,
  },
});
const result = await response.json();
console.log(response.status, result);
```

```python example=python
import os

import requests

response = requests.get(
    "https://api.rephut.com/v1/orders/cm3o7rd0e0001h2j3k4l5m6n",
    headers={
        "Authorization": f"Bearer {os.environ['FULFILLBOT_API_KEY']}",
    },
    timeout=30,
)
print(response.status_code, response.json())
```

```json example=response status=200
{
  "success": true,
  "data": {
    "id": "cm3o7rd0e0001h2j3k4l5m6n",
    "number": "D2611090012",
    "storeOrderNo": "1042",
    "status": "READY",
    "statusLabel": "Ready for fulfilment",
    "sourceLabel": "Manual",
    "createdAt": "2026-11-09T02:00:00.000Z",
    "updatedAt": "2026-11-09T02:00:00.000Z",
    "cancelledAt": null,
    "recipient": {
      "name": "Alex Morgan",
      "company": "",
      "phone": "+1 555 0100",
      "email": "alex@example.com",
      "address1": "12 Example Street",
      "address2": "Apt 4",
      "city": "Springfield",
      "province": "IL",
      "postalCode": "62701",
      "country": "United States",
      "countryCode": "US"
    },
    "buyerNote": "Please leave at the door.",
    "lines": [
      {
        "productId": "cm3p4r0d0001xyz7k2m9q8w1",
        "productSku": "FB-10231-BLK-M",
        "productName": "Cotton crew neck T-shirt",
        "customerSku": "TEE-BLK-M",
        "title": "Crew neck T-shirt, black, M",
        "qty": 2
      }
    ],
    "shippingChannelLabel": "US Standard",
    "shippedInStore": false,
    "currency": "USD",
    "estimatedShippingCents": 640,
    "weightEstimated": false,
    "problems": [],
    "fulfilment": null,
    "payment": null,
    "shippingHold": null,
    "backorder": null
  }
}
```

## Check a new order without saving it

`POST /v1/orders/preview`

Takes the same body as POST /v1/orders and returns what creating it would do: problems, the shipping options and their prices, the wallet hold and stock shortages. Nothing is saved, and it works while your account is on hold. It checks new orders only. The body is optional (an empty body is checked as an empty order).

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/orders/preview` |
| Permission | `orders:read` |
| Access | Read: counts towards the read limit and works while your account is on hold |
| Idempotency-Key | Not used |
| Success | 200 OK |

### Headers

| Header | Required | Value |
|---|---|---|
| `Authorization` | Yes | `Bearer <your API key>`. See [Authentication](https://docs.rephut.com/developers/authentication). |
| `Content-Type` | When you send a body | `application/json` |

### Request body

A JSON object. The body is optional: an empty body is treated as `{}`.

| Field | Type | Required | Description |
|---|---|---|---|
| `storeOrderNo` | string | No | Your store's order number, unique among your orders. Leave empty to use our order number. Default: `""`. Up to 100 characters, after spaces at both ends are removed. |
| `recipientName` | string | No | Recipient's full name. An order without it needs attention. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `recipientCompany` | string | No | Company name, if any. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `recipientPhone` | string | No | Recipient's phone number. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `recipientEmail` | string | No | Recipient's email address. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `address1` | string | No | Street address. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `address2` | string | No | Apartment, suite or building, if any. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `city` | string | No | City. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `province` | string | No | State, province or region. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `postalCode` | string | No | Postal code. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `countryCode` | string | No | Two-letter country code, for example US. Default: `""`. |
| `channelId` | string | No | A shipping option from POST /v1/orders/preview (shippingOptions[].channelId). Leave empty to use your shipping preference for the country. Up to 64 characters. Default: `""`. |
| `buyerNote` | string | No | Note from your buyer, kept with the order. Line breaks are kept. Default: `""`. Up to 2,000 characters, after spaces at both ends are removed. |
| `lines` | array of objects | No | The items in the order. Up to 200 items. |
| `lines[].customerSku` | string | No | Your SKU for this item. When productId is empty we match it to a product through your SKU mappings. Default: `""`. Up to 100 characters, after spaces at both ends are removed. |
| `lines[].title` | string | No | Item name, for example from your store. Default: `""`. Up to 500 characters, after spaces at both ends are removed. |
| `lines[].qty` | integer | Yes | How many units. From 1 to 100,000. |
| `lines[].productId` | string | No | Our product id from GET /v1/products. Leave empty to match by customerSku. Up to 64 characters. Default: `""`. |

### Response

`200` with `data`, an object with the fields below.

| Field | Type | Description |
|---|---|---|
| `willReserve` | boolean | true when the order has no problems: creating it reserves stock, holds the estimated shipping and handling fee in your wallet, and you can't change it afterwards. |
| `problems` | array of objects | Why the order would need attention; empty when there are none. Rely on `code`; `message` is English text that may change. |
| `problems[].code` | string | Problem code. See [Order problems](#order-problems). One of `NO_LINES`, `UNBOUND_LINE`, `MISSING_RECIPIENT`, `UNKNOWN_COUNTRY`, `NO_CHANNEL`, `CHANNEL_UNAVAILABLE`. |
| `problems[].message` | string | English text for display. It may change; rely on the code next to it. |
| `shippingChannelLabel` | string | The shipping option the order would use, in English. |
| `currency` | string | Currency of every amount. Always `USD`. |
| `estimatedShippingCents` | integer or null | Estimated shipping for the option in `shippingChannelLabel`, in US cents; null when it can't be estimated. |
| `weightEstimated` | boolean | true when a product's weight is unknown and we estimated it. |
| `lines` | array of objects | One entry per line you sent, in the same order. |
| `lines[].productId` | string or null | The product the line matched; null when it doesn't match one of your products. |
| `lines[].productLabel` | string or null | Name of the matched product; null when the line doesn't match one of your products. |
| `shippingOptions` | array of objects or null | Shipping options for this order, cheapest first; null until the order has a country and at least one matched product. |
| `shippingOptions[].channelId` | string | Send this as channelId to use this shipping option. |
| `shippingOptions[].name` | string | Name of the shipping option in English. |
| `shippingOptions[].priceCents` | integer | Estimated shipping in US cents. |
| `shippingOptions[].priceMaxCents` | integer or null | Upper end of the estimate when the price depends on the postal code; null otherwise. |
| `shippingOptions[].transitLabel` | string | Delivery time in English, for example 7–12 days. |
| `shippingOptions[].chargeableG` | integer | Weight the price is based on, in grams. |
| `shippingOptions[].cheapest` | boolean | `true` for the cheapest option. |
| `shippingOptions[].fastest` | boolean | `true` for the fastest option. |
| `preferredShipping` | object or null | The option your shipping preference would pick when channelId is empty; null when no preference applies. |
| `preferredShipping.name` | string | Name of that shipping option. |
| `preferredShipping.priceCents` | integer or null | Its estimated price in US cents; null when it can't be priced. |
| `charges` | object | What creating the order would do to your wallet. See [Charges and your wallet](https://docs.rephut.com/developers/charges). |
| `charges.shippingCents` | integer or null | Estimated shipping in US cents; null when it can't be estimated. |
| `charges.handlingFeeCents` | integer | Handling fee in US cents. |
| `charges.holdCents` | integer or null | Held from your available balance when the order is created with no problems; null when nothing is held. |
| `charges.availableCents` | integer | Your available wallet balance now. |
| `charges.topUpCents` | integer | Top up at least this much to cover the hold; 0 when your balance is enough. |
| `shortages` | array of objects | Products with less available stock than ordered: the order waits for stock before packing. |
| `shortages[].productLabel` | string | Product name. |
| `shortages[].wantedQty` | integer | Quantity in the order. |
| `shortages[].availableQty` | integer | Quantity available now. |

### Errors

| Code | HTTP status | Meaning |
|---|---|---|
| `invalid_json` | 400 | The request body is not valid JSON. |
| `invalid_api_key` | 401 | The API key is missing, malformed, revoked or no longer valid. |
| `account_action_required` | 403 | Sign in to the portal and finish the required account steps first. |
| `api_not_enabled` | 403 | API access is not enabled for this account. |
| `insufficient_scope` | 403 | The API key does not have the permission this call needs. |
| `payload_too_large` | 413 | The request body is larger than 1 MB. |
| `unsupported_media_type` | 415 | The request body must be sent as application/json. |
| `order_product_not_allowed` | 422 | Choose products from My products. Other products can't be added to your orders here. |
| `orders_unavailable` | 422 | We can't take orders right now. Please contact your account manager. |
| `preference_channel_unavailable` | 422 | This shipping option is no longer available. Please choose another one. |
| `validation_failed` | 422 | The input is not valid. `details` lists each field and the reason. |
| `rate_limited` | 429 | Too many requests. Wait for the number of seconds in Retry-After. |
| `internal_error` | 500 | Unexpected error. Quote X-Request-Id when you contact support. |

Handle errors by `code`. See [Errors](https://docs.rephut.com/developers/errors).

```bash example=curl
curl -X POST 'https://api.rephut.com/v1/orders/preview' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "storeOrderNo": "1042",
  "recipientName": "Alex Morgan",
  "recipientCompany": "",
  "recipientPhone": "+1 555 0100",
  "recipientEmail": "alex@example.com",
  "address1": "12 Example Street",
  "address2": "Apt 4",
  "city": "Springfield",
  "province": "IL",
  "postalCode": "62701",
  "countryCode": "US",
  "channelId": "",
  "buyerNote": "Please leave at the door.",
  "lines": [
    {
      "customerSku": "TEE-BLK-M",
      "title": "Crew neck T-shirt, black, M",
      "qty": 2,
      "productId": ""
    }
  ]
}'
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/orders/preview", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FULFILLBOT_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "storeOrderNo": "1042",
    "recipientName": "Alex Morgan",
    "recipientCompany": "",
    "recipientPhone": "+1 555 0100",
    "recipientEmail": "alex@example.com",
    "address1": "12 Example Street",
    "address2": "Apt 4",
    "city": "Springfield",
    "province": "IL",
    "postalCode": "62701",
    "countryCode": "US",
    "channelId": "",
    "buyerNote": "Please leave at the door.",
    "lines": [
      {
        "customerSku": "TEE-BLK-M",
        "title": "Crew neck T-shirt, black, M",
        "qty": 2,
        "productId": ""
      }
    ]
  }),
});
const result = await response.json();
console.log(response.status, result);
```

```python example=python
import os

import requests

response = requests.post(
    "https://api.rephut.com/v1/orders/preview",
    headers={
        "Authorization": f"Bearer {os.environ['FULFILLBOT_API_KEY']}",
    },
    json={
        "storeOrderNo": "1042",
        "recipientName": "Alex Morgan",
        "recipientCompany": "",
        "recipientPhone": "+1 555 0100",
        "recipientEmail": "alex@example.com",
        "address1": "12 Example Street",
        "address2": "Apt 4",
        "city": "Springfield",
        "province": "IL",
        "postalCode": "62701",
        "countryCode": "US",
        "channelId": "",
        "buyerNote": "Please leave at the door.",
        "lines": [
            {
                "customerSku": "TEE-BLK-M",
                "title": "Crew neck T-shirt, black, M",
                "qty": 2,
                "productId": "",
            },
        ],
    },
    timeout=30,
)
print(response.status_code, response.json())
```

```json example=response status=200
{
  "success": true,
  "data": {
    "willReserve": true,
    "problems": [],
    "shippingChannelLabel": "US Standard",
    "currency": "USD",
    "estimatedShippingCents": 640,
    "weightEstimated": false,
    "lines": [
      {
        "productId": "cm3p4r0d0001xyz7k2m9q8w1",
        "productLabel": "Cotton crew neck T-shirt (FB-10231-BLK-M)"
      }
    ],
    "shippingOptions": [
      {
        "channelId": "cm1ch0001usstd0000000001",
        "name": "US Standard",
        "priceCents": 640,
        "priceMaxCents": null,
        "transitLabel": "7–12 days",
        "chargeableG": 420,
        "cheapest": true,
        "fastest": false
      },
      {
        "channelId": "cm1ch0002usexp0000000002",
        "name": "US Express",
        "priceCents": 1380,
        "priceMaxCents": null,
        "transitLabel": "4–6 days",
        "chargeableG": 420,
        "cheapest": false,
        "fastest": true
      }
    ],
    "preferredShipping": {
      "name": "US Standard",
      "priceCents": 640
    },
    "charges": {
      "shippingCents": 640,
      "handlingFeeCents": 200,
      "holdCents": 840,
      "availableCents": 173160,
      "topUpCents": 0
    },
    "shortages": []
  }
}
```

## Create an order

`POST /v1/orders`

Saves the order like the portal's order form and returns it. Creating an order doesn't charge your wallet. When the order has no problems, we reserve its stock and hold the estimated shipping and handling fee from your available balance. When our warehouse weighs the parcel, the shipping and handling fee is charged to your wallet automatically. If auto purchase is on and an order is short of stock, we place and pay for a purchase order automatically, within the limits you set. An order with problems is saved with status NEEDS_ATTENTION; fix it with PATCH /v1/orders/{id}. Check it first with POST /v1/orders/preview. Send an Idempotency-Key so a retry can't create the order twice.

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/orders` |
| Permission | `orders:write` |
| Access | Write: counts towards the write limits and is refused with `account_on_hold` while your account is on hold |
| Idempotency-Key | Required |
| Success | 201 Created |

### Headers

| Header | Required | Value |
|---|---|---|
| `Authorization` | Yes | `Bearer <your API key>`. See [Authentication](https://docs.rephut.com/developers/authentication). |
| `Content-Type` | Yes | `application/json` |
| `Idempotency-Key` | Yes | 1 to 255 visible ASCII characters, new for each new request. See [Idempotency](https://docs.rephut.com/developers/idempotency). |

### Request body

A JSON object. Fields not listed here are ignored.

| Field | Type | Required | Description |
|---|---|---|---|
| `storeOrderNo` | string | No | Your store's order number, unique among your orders. Leave empty to use our order number. Default: `""`. Up to 100 characters, after spaces at both ends are removed. |
| `recipientName` | string | No | Recipient's full name. An order without it needs attention. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `recipientCompany` | string | No | Company name, if any. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `recipientPhone` | string | No | Recipient's phone number. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `recipientEmail` | string | No | Recipient's email address. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `address1` | string | No | Street address. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `address2` | string | No | Apartment, suite or building, if any. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `city` | string | No | City. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `province` | string | No | State, province or region. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `postalCode` | string | No | Postal code. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `countryCode` | string | No | Two-letter country code, for example US. Default: `""`. |
| `channelId` | string | No | A shipping option from POST /v1/orders/preview (shippingOptions[].channelId). Leave empty to use your shipping preference for the country. Up to 64 characters. Default: `""`. |
| `buyerNote` | string | No | Note from your buyer, kept with the order. Line breaks are kept. Default: `""`. Up to 2,000 characters, after spaces at both ends are removed. |
| `lines` | array of objects | No | The items in the order. Up to 200 items. |
| `lines[].customerSku` | string | No | Your SKU for this item. When productId is empty we match it to a product through your SKU mappings. Default: `""`. Up to 100 characters, after spaces at both ends are removed. |
| `lines[].title` | string | No | Item name, for example from your store. Default: `""`. Up to 500 characters, after spaces at both ends are removed. |
| `lines[].qty` | integer | Yes | How many units. From 1 to 100,000. |
| `lines[].productId` | string | No | Our product id from GET /v1/products. Leave empty to match by customerSku. Up to 64 characters. Default: `""`. |

### Response

`201` with `data`, an object with the fields below.

| Field | Type | Description |
|---|---|---|
| `id` | string | Order id. |
| `number` | string | Our order number. |
| `storeOrderNo` | string | Your store's order number; empty when you didn't give one. |
| `status` | string | Order status. See [Order statuses](#order-statuses). One of `NEEDS_ATTENTION`, `READY`, `BACKORDERED`, `RESERVED`, `WEIGHED`, `SHIPPED`, `CANCELLED`. |
| `statusLabel` | string | English text for display. It may change; rely on the code next to it. For `NEEDS_ATTENTION` it names the first problem, for example "SKU not mapped". |
| `sourceLabel` | string | Where the order came from: `Store` (a connected store), `Imported` (a file import) or `Manual` (the portal form or the API). English text; it may change. |
| `createdAt` | string (date-time) | When the order was created. |
| `updatedAt` | string (date-time) | When the order last changed. Any edit through [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention) changes it. |
| `cancelledAt` | string (date-time) or null | When the order was cancelled; null otherwise. |
| `recipient` | object | Who we ship to. |
| `recipient.name` | string | Recipient name. |
| `recipient.company` | string | Company; empty when none. |
| `recipient.phone` | string | Recipient phone number. |
| `recipient.email` | string | Recipient email address. |
| `recipient.address1` | string | Address line 1. |
| `recipient.address2` | string | Address line 2. |
| `recipient.city` | string | City. |
| `recipient.province` | string | State or province. |
| `recipient.postalCode` | string | Postal code. |
| `recipient.country` | string | Country in English. If we don't recognise it, the text the order has, or the code itself when the code is unknown; "—" when there is neither. |
| `recipient.countryCode` | string or null | Two-letter country code; null when we don't recognise the country. |
| `buyerNote` | string | Note from your buyer. |
| `lines` | array of objects | The items in the order. |
| `lines[].productId` | string or null | Our product id; null while the line is not matched to a product. |
| `lines[].productSku` | string or null | Our SKU of the matched product; null while the line isn't matched. |
| `lines[].productName` | string or null | Name of the matched product; null while the line isn't matched. |
| `lines[].customerSku` | string | Your SKU on this line. |
| `lines[].title` | string | Item name from your store or file. |
| `lines[].qty` | integer | Quantity. |
| `shippingChannelLabel` | string | The shipping option the order uses, in English; `Assigned by our team` when we chose it, `—` when none is set yet. |
| `shippedInStore` | boolean | `true` when you fulfilled the order yourself in your store, so we don't ship it. |
| `currency` | string | Currency of every amount in this order. Always `USD`. |
| `estimatedShippingCents` | integer or null | Estimated shipping in US cents; null when it can't be estimated yet, and when `shippedInStore` is `true`. |
| `weightEstimated` | boolean | `true` when a product's weight is unknown and the estimate uses an assumed weight. Only before the parcel is weighed; `false` afterwards. |
| `problems` | array of objects | Why the order needs attention; empty otherwise. Rely on `code`; `message` is English text that may change. |
| `problems[].code` | string | Problem code. See [Order problems](#order-problems). One of `NO_LINES`, `UNBOUND_LINE`, `MISSING_RECIPIENT`, `UNKNOWN_COUNTRY`, `NO_CHANNEL`, `CHANNEL_UNAVAILABLE`. |
| `problems[].message` | string | English text for display. It may change; rely on the code next to it. |
| `fulfilment` | object or null | What our warehouse did with the parcel. Present when the status is `RESERVED`, `WEIGHED` or `SHIPPED`; null otherwise. |
| `fulfilment.trackingNo` | string | Tracking number; empty until the parcel ships. |
| `fulfilment.trackingUrl` | string or null | Link to track the parcel; null when we have none. |
| `fulfilment.shippedAt` | string (date-time) or null | When the parcel shipped; null until then. |
| `fulfilment.actualWeightG` | integer or null | Weighed parcel weight in grams; null until weighed. |
| `fulfilment.freightCents` | integer or null | Shipping charged, in US cents; null until charged. |
| `fulfilment.handlingFeeCents` | integer or null | Handling fee charged, in US cents; null until charged. |
| `fulfilment.surchargeCents` | integer or null | Extra shipping charged later (for example a carrier adjustment), in US cents; null when none. |
| `fulfilment.totalChargedCents` | integer or null | Everything charged for this order, in US cents; null until charged. |
| `payment` | object or null | Shipping charge waiting for wallet funds; null when nothing is due. |
| `payment.freightCents` | integer | Shipping fee waiting to be paid, in US cents. |
| `payment.handlingFeeCents` | integer | Handling fee waiting to be paid, in US cents. |
| `payment.dueCents` | integer | Total waiting to be paid, in US cents. |
| `payment.availableCents` | integer | Your available balance plus the amount already held for this order, in US cents. |
| `payment.topUpCents` | integer | At least this much to top up. |
| `shippingHold` | object or null | The estimated shipping and handling fee held from your available balance while the order waits to ship; null when nothing is held. See [Charges and your wallet](https://docs.rephut.com/developers/charges). |
| `shippingHold.heldCents` | integer | Amount held, in US cents. |
| `shippingHold.topUpCents` | integer | Top up at least this much when your balance doesn't cover the hold; 0 when it does. |
| `backorder` | object or null | What the order is waiting for when some products are short; null otherwise. |
| `backorder.lines` | array of objects | Products short for this order. |
| `backorder.lines[].productSku` | string | Our SKU. |
| `backorder.lines[].productName` | string | Product name. |
| `backorder.lines[].orderedQty` | integer | Quantity ordered. |
| `backorder.lines[].waitingQty` | integer | Quantity still waiting for stock. |
| `backorder.purchaseOrders` | array of objects | Purchase orders that bring the missing stock. |
| `backorder.purchaseOrders[].id` | string | Purchase order id. Fetch it with [`GET /v1/purchase-orders/{id}`](https://docs.rephut.com/developers/reference/purchase-orders#get-a-purchase-order). |
| `backorder.purchaseOrders[].number` | string | Purchase order number. |
| `backorder.purchaseOrders[].statusLabel` | string | English text for display. It may change; rely on the code next to it. |

### Errors

| Code | HTTP status | Meaning |
|---|---|---|
| `idempotency_key_required` | 400 | This request needs an Idempotency-Key header. |
| `invalid_idempotency_key` | 400 | Idempotency-Key must be 1 to 255 visible ASCII characters. |
| `invalid_json` | 400 | The request body is not valid JSON. |
| `invalid_api_key` | 401 | The API key is missing, malformed, revoked or no longer valid. |
| `account_action_required` | 403 | Sign in to the portal and finish the required account steps first. |
| `account_on_hold` | 403 | The account is on hold. Writes are refused; reads still work. |
| `api_not_enabled` | 403 | API access is not enabled for this account. |
| `insufficient_scope` | 403 | The API key does not have the permission this call needs. |
| `request_in_progress` | 409 | A request with this Idempotency-Key is still running or its result is unknown. |
| `store_order_no_taken` | 409 | You already have an order with this store order number. |
| `payload_too_large` | 413 | The request body is larger than 1 MB. |
| `unsupported_media_type` | 415 | The request body must be sent as application/json. |
| `idempotency_key_reused` | 422 | This Idempotency-Key was already used with a different request. |
| `order_product_not_allowed` | 422 | Choose products from My products. Other products can't be added to your orders here. |
| `orders_unavailable` | 422 | We can't take orders right now. Please contact your account manager. |
| `preference_channel_unavailable` | 422 | This shipping option is no longer available. Please choose another one. |
| `request_rejected` | 422 | The request was rejected by a business rule. |
| `validation_failed` | 422 | The input is not valid. `details` lists each field and the reason. |
| `rate_limited` | 429 | Too many requests. Wait for the number of seconds in Retry-After. |
| `internal_error` | 500 | Unexpected error. Quote X-Request-Id when you contact support. |

Handle errors by `code`. See [Errors](https://docs.rephut.com/developers/errors).

```bash example=curl
curl -X POST 'https://api.rephut.com/v1/orders' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H 'Idempotency-Key: 5d0f6c1e-8a4b-4c1d-9b7e-2f3a4b5c6d7e' \
  -d '{
  "storeOrderNo": "1042",
  "recipientName": "Alex Morgan",
  "recipientCompany": "",
  "recipientPhone": "+1 555 0100",
  "recipientEmail": "alex@example.com",
  "address1": "12 Example Street",
  "address2": "Apt 4",
  "city": "Springfield",
  "province": "IL",
  "postalCode": "62701",
  "countryCode": "US",
  "channelId": "",
  "buyerNote": "Please leave at the door.",
  "lines": [
    {
      "customerSku": "TEE-BLK-M",
      "title": "Crew neck T-shirt, black, M",
      "qty": 2,
      "productId": ""
    }
  ]
}'
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/orders", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FULFILLBOT_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "5d0f6c1e-8a4b-4c1d-9b7e-2f3a4b5c6d7e",
  },
  body: JSON.stringify({
    "storeOrderNo": "1042",
    "recipientName": "Alex Morgan",
    "recipientCompany": "",
    "recipientPhone": "+1 555 0100",
    "recipientEmail": "alex@example.com",
    "address1": "12 Example Street",
    "address2": "Apt 4",
    "city": "Springfield",
    "province": "IL",
    "postalCode": "62701",
    "countryCode": "US",
    "channelId": "",
    "buyerNote": "Please leave at the door.",
    "lines": [
      {
        "customerSku": "TEE-BLK-M",
        "title": "Crew neck T-shirt, black, M",
        "qty": 2,
        "productId": ""
      }
    ]
  }),
});
const result = await response.json();
console.log(response.status, result);
```

```python example=python
import os

import requests

response = requests.post(
    "https://api.rephut.com/v1/orders",
    headers={
        "Authorization": f"Bearer {os.environ['FULFILLBOT_API_KEY']}",
        "Idempotency-Key": "5d0f6c1e-8a4b-4c1d-9b7e-2f3a4b5c6d7e",
    },
    json={
        "storeOrderNo": "1042",
        "recipientName": "Alex Morgan",
        "recipientCompany": "",
        "recipientPhone": "+1 555 0100",
        "recipientEmail": "alex@example.com",
        "address1": "12 Example Street",
        "address2": "Apt 4",
        "city": "Springfield",
        "province": "IL",
        "postalCode": "62701",
        "countryCode": "US",
        "channelId": "",
        "buyerNote": "Please leave at the door.",
        "lines": [
            {
                "customerSku": "TEE-BLK-M",
                "title": "Crew neck T-shirt, black, M",
                "qty": 2,
                "productId": "",
            },
        ],
    },
    timeout=30,
)
print(response.status_code, response.json())
```

```json example=response status=201
{
  "success": true,
  "data": {
    "id": "cm3o7rd0e0001h2j3k4l5m6n",
    "number": "D2611090012",
    "storeOrderNo": "1042",
    "status": "READY",
    "statusLabel": "Ready for fulfilment",
    "sourceLabel": "Manual",
    "createdAt": "2026-11-09T02:00:00.000Z",
    "updatedAt": "2026-11-09T02:00:00.000Z",
    "cancelledAt": null,
    "recipient": {
      "name": "Alex Morgan",
      "company": "",
      "phone": "+1 555 0100",
      "email": "alex@example.com",
      "address1": "12 Example Street",
      "address2": "Apt 4",
      "city": "Springfield",
      "province": "IL",
      "postalCode": "62701",
      "country": "United States",
      "countryCode": "US"
    },
    "buyerNote": "Please leave at the door.",
    "lines": [
      {
        "productId": "cm3p4r0d0001xyz7k2m9q8w1",
        "productSku": "FB-10231-BLK-M",
        "productName": "Cotton crew neck T-shirt",
        "customerSku": "TEE-BLK-M",
        "title": "Crew neck T-shirt, black, M",
        "qty": 2
      }
    ],
    "shippingChannelLabel": "US Standard",
    "shippedInStore": false,
    "currency": "USD",
    "estimatedShippingCents": 640,
    "weightEstimated": false,
    "problems": [],
    "fulfilment": null,
    "payment": null,
    "shippingHold": null,
    "backorder": null
  }
}
```

## Change an order that needs attention

`PATCH /v1/orders/{id}`

Replaces the whole order with the body, like saving the portal's order form: send every field and every line, as for POST /v1/orders; fields you leave out are cleared. Only orders with status NEEDS_ATTENTION can be changed (otherwise 409 order_not_editable). Orders from a connected store keep the store's order number. Creating an order doesn't charge your wallet. When the order has no problems, we reserve its stock and hold the estimated shipping and handling fee from your available balance.

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/orders/{id}` |
| Permission | `orders:write` |
| Access | Write: counts towards the write limits and is refused with `account_on_hold` while your account is on hold |
| Idempotency-Key | Optional, recommended |
| Success | 200 OK |

### Path parameters

| Name | Type | Description |
|---|---|---|
| `id` | string | Order id. An id that doesn't exist or isn't yours returns 404 `order_not_found`; an id that isn't a valid id returns 404 `not_found`. |

### Headers

| Header | Required | Value |
|---|---|---|
| `Authorization` | Yes | `Bearer <your API key>`. See [Authentication](https://docs.rephut.com/developers/authentication). |
| `Content-Type` | Yes | `application/json` |
| `Idempotency-Key` | No | 1 to 255 visible ASCII characters, new for each new request. See [Idempotency](https://docs.rephut.com/developers/idempotency). |

### Request body

A JSON object. Fields not listed here are ignored.

| Field | Type | Required | Description |
|---|---|---|---|
| `storeOrderNo` | string | No | Your store's order number, unique among your orders. Leave empty to use our order number. Default: `""`. Up to 100 characters, after spaces at both ends are removed. |
| `recipientName` | string | No | Recipient's full name. An order without it needs attention. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `recipientCompany` | string | No | Company name, if any. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `recipientPhone` | string | No | Recipient's phone number. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `recipientEmail` | string | No | Recipient's email address. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `address1` | string | No | Street address. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `address2` | string | No | Apartment, suite or building, if any. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `city` | string | No | City. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `province` | string | No | State, province or region. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `postalCode` | string | No | Postal code. Default: `""`. Up to 200 characters, after spaces at both ends are removed. |
| `countryCode` | string | No | Two-letter country code, for example US. Default: `""`. |
| `channelId` | string | No | A shipping option from POST /v1/orders/preview (shippingOptions[].channelId). Leave empty to use your shipping preference for the country. Up to 64 characters. Default: `""`. |
| `buyerNote` | string | No | Note from your buyer, kept with the order. Line breaks are kept. Default: `""`. Up to 2,000 characters, after spaces at both ends are removed. |
| `lines` | array of objects | No | The items in the order. Up to 200 items. |
| `lines[].customerSku` | string | No | Your SKU for this item. When productId is empty we match it to a product through your SKU mappings. Default: `""`. Up to 100 characters, after spaces at both ends are removed. |
| `lines[].title` | string | No | Item name, for example from your store. Default: `""`. Up to 500 characters, after spaces at both ends are removed. |
| `lines[].qty` | integer | Yes | How many units. From 1 to 100,000. |
| `lines[].productId` | string | No | Our product id from GET /v1/products. Leave empty to match by customerSku. Up to 64 characters. Default: `""`. |

### Response

`200` with `data`, an object with the fields below.

| Field | Type | Description |
|---|---|---|
| `id` | string | Order id. |
| `number` | string | Our order number. |
| `storeOrderNo` | string | Your store's order number; empty when you didn't give one. |
| `status` | string | Order status. See [Order statuses](#order-statuses). One of `NEEDS_ATTENTION`, `READY`, `BACKORDERED`, `RESERVED`, `WEIGHED`, `SHIPPED`, `CANCELLED`. |
| `statusLabel` | string | English text for display. It may change; rely on the code next to it. For `NEEDS_ATTENTION` it names the first problem, for example "SKU not mapped". |
| `sourceLabel` | string | Where the order came from: `Store` (a connected store), `Imported` (a file import) or `Manual` (the portal form or the API). English text; it may change. |
| `createdAt` | string (date-time) | When the order was created. |
| `updatedAt` | string (date-time) | When the order last changed. Any edit through [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention) changes it. |
| `cancelledAt` | string (date-time) or null | When the order was cancelled; null otherwise. |
| `recipient` | object | Who we ship to. |
| `recipient.name` | string | Recipient name. |
| `recipient.company` | string | Company; empty when none. |
| `recipient.phone` | string | Recipient phone number. |
| `recipient.email` | string | Recipient email address. |
| `recipient.address1` | string | Address line 1. |
| `recipient.address2` | string | Address line 2. |
| `recipient.city` | string | City. |
| `recipient.province` | string | State or province. |
| `recipient.postalCode` | string | Postal code. |
| `recipient.country` | string | Country in English. If we don't recognise it, the text the order has, or the code itself when the code is unknown; "—" when there is neither. |
| `recipient.countryCode` | string or null | Two-letter country code; null when we don't recognise the country. |
| `buyerNote` | string | Note from your buyer. |
| `lines` | array of objects | The items in the order. |
| `lines[].productId` | string or null | Our product id; null while the line is not matched to a product. |
| `lines[].productSku` | string or null | Our SKU of the matched product; null while the line isn't matched. |
| `lines[].productName` | string or null | Name of the matched product; null while the line isn't matched. |
| `lines[].customerSku` | string | Your SKU on this line. |
| `lines[].title` | string | Item name from your store or file. |
| `lines[].qty` | integer | Quantity. |
| `shippingChannelLabel` | string | The shipping option the order uses, in English; `Assigned by our team` when we chose it, `—` when none is set yet. |
| `shippedInStore` | boolean | `true` when you fulfilled the order yourself in your store, so we don't ship it. |
| `currency` | string | Currency of every amount in this order. Always `USD`. |
| `estimatedShippingCents` | integer or null | Estimated shipping in US cents; null when it can't be estimated yet, and when `shippedInStore` is `true`. |
| `weightEstimated` | boolean | `true` when a product's weight is unknown and the estimate uses an assumed weight. Only before the parcel is weighed; `false` afterwards. |
| `problems` | array of objects | Why the order needs attention; empty otherwise. Rely on `code`; `message` is English text that may change. |
| `problems[].code` | string | Problem code. See [Order problems](#order-problems). One of `NO_LINES`, `UNBOUND_LINE`, `MISSING_RECIPIENT`, `UNKNOWN_COUNTRY`, `NO_CHANNEL`, `CHANNEL_UNAVAILABLE`. |
| `problems[].message` | string | English text for display. It may change; rely on the code next to it. |
| `fulfilment` | object or null | What our warehouse did with the parcel. Present when the status is `RESERVED`, `WEIGHED` or `SHIPPED`; null otherwise. |
| `fulfilment.trackingNo` | string | Tracking number; empty until the parcel ships. |
| `fulfilment.trackingUrl` | string or null | Link to track the parcel; null when we have none. |
| `fulfilment.shippedAt` | string (date-time) or null | When the parcel shipped; null until then. |
| `fulfilment.actualWeightG` | integer or null | Weighed parcel weight in grams; null until weighed. |
| `fulfilment.freightCents` | integer or null | Shipping charged, in US cents; null until charged. |
| `fulfilment.handlingFeeCents` | integer or null | Handling fee charged, in US cents; null until charged. |
| `fulfilment.surchargeCents` | integer or null | Extra shipping charged later (for example a carrier adjustment), in US cents; null when none. |
| `fulfilment.totalChargedCents` | integer or null | Everything charged for this order, in US cents; null until charged. |
| `payment` | object or null | Shipping charge waiting for wallet funds; null when nothing is due. |
| `payment.freightCents` | integer | Shipping fee waiting to be paid, in US cents. |
| `payment.handlingFeeCents` | integer | Handling fee waiting to be paid, in US cents. |
| `payment.dueCents` | integer | Total waiting to be paid, in US cents. |
| `payment.availableCents` | integer | Your available balance plus the amount already held for this order, in US cents. |
| `payment.topUpCents` | integer | At least this much to top up. |
| `shippingHold` | object or null | The estimated shipping and handling fee held from your available balance while the order waits to ship; null when nothing is held. See [Charges and your wallet](https://docs.rephut.com/developers/charges). |
| `shippingHold.heldCents` | integer | Amount held, in US cents. |
| `shippingHold.topUpCents` | integer | Top up at least this much when your balance doesn't cover the hold; 0 when it does. |
| `backorder` | object or null | What the order is waiting for when some products are short; null otherwise. |
| `backorder.lines` | array of objects | Products short for this order. |
| `backorder.lines[].productSku` | string | Our SKU. |
| `backorder.lines[].productName` | string | Product name. |
| `backorder.lines[].orderedQty` | integer | Quantity ordered. |
| `backorder.lines[].waitingQty` | integer | Quantity still waiting for stock. |
| `backorder.purchaseOrders` | array of objects | Purchase orders that bring the missing stock. |
| `backorder.purchaseOrders[].id` | string | Purchase order id. Fetch it with [`GET /v1/purchase-orders/{id}`](https://docs.rephut.com/developers/reference/purchase-orders#get-a-purchase-order). |
| `backorder.purchaseOrders[].number` | string | Purchase order number. |
| `backorder.purchaseOrders[].statusLabel` | string | English text for display. It may change; rely on the code next to it. |

### Errors

| Code | HTTP status | Meaning |
|---|---|---|
| `invalid_idempotency_key` | 400 | Idempotency-Key must be 1 to 255 visible ASCII characters. |
| `invalid_json` | 400 | The request body is not valid JSON. |
| `invalid_api_key` | 401 | The API key is missing, malformed, revoked or no longer valid. |
| `account_action_required` | 403 | Sign in to the portal and finish the required account steps first. |
| `account_on_hold` | 403 | The account is on hold. Writes are refused; reads still work. |
| `api_not_enabled` | 403 | API access is not enabled for this account. |
| `insufficient_scope` | 403 | The API key does not have the permission this call needs. |
| `not_found` | 404 | The resource does not exist or does not belong to this account. |
| `order_not_found` | 404 | This order could not be found. |
| `order_not_editable` | 409 | This order can no longer be changed or cancelled here. Refresh the page to see its latest status. |
| `request_in_progress` | 409 | A request with this Idempotency-Key is still running or its result is unknown. |
| `store_order_no_locked` | 409 | Orders from your store keep the store's order number, so it can't be changed here. |
| `store_order_no_taken` | 409 | You already have an order with this store order number. |
| `payload_too_large` | 413 | The request body is larger than 1 MB. |
| `unsupported_media_type` | 415 | The request body must be sent as application/json. |
| `idempotency_key_reused` | 422 | This Idempotency-Key was already used with a different request. |
| `order_product_not_allowed` | 422 | Choose products from My products. Other products can't be added to your orders here. |
| `orders_unavailable` | 422 | We can't take orders right now. Please contact your account manager. |
| `preference_channel_unavailable` | 422 | This shipping option is no longer available. Please choose another one. |
| `request_rejected` | 422 | The request was rejected by a business rule. |
| `validation_failed` | 422 | The input is not valid. `details` lists each field and the reason. |
| `rate_limited` | 429 | Too many requests. Wait for the number of seconds in Retry-After. |
| `internal_error` | 500 | Unexpected error. Quote X-Request-Id when you contact support. |

Handle errors by `code`. See [Errors](https://docs.rephut.com/developers/errors).

```bash example=curl
curl -X PATCH 'https://api.rephut.com/v1/orders/cm3o7rd0e0001h2j3k4l5m6n' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H 'Idempotency-Key: a3c9e1f2-7b6d-4e85-8f10-3c2b1a0d9e8f' \
  -d '{
  "storeOrderNo": "1042",
  "recipientName": "Alex Morgan",
  "recipientCompany": "",
  "recipientPhone": "+1 555 0100",
  "recipientEmail": "alex@example.com",
  "address1": "12 Example Street",
  "address2": "Apt 4",
  "city": "Springfield",
  "province": "IL",
  "postalCode": "62701",
  "countryCode": "US",
  "channelId": "",
  "buyerNote": "Please leave at the door.",
  "lines": [
    {
      "customerSku": "TEE-BLK-M",
      "title": "Crew neck T-shirt, black, M",
      "qty": 2,
      "productId": ""
    }
  ]
}'
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/orders/cm3o7rd0e0001h2j3k4l5m6n", {
  method: "PATCH",
  headers: {
    Authorization: `Bearer ${process.env.FULFILLBOT_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "a3c9e1f2-7b6d-4e85-8f10-3c2b1a0d9e8f",
  },
  body: JSON.stringify({
    "storeOrderNo": "1042",
    "recipientName": "Alex Morgan",
    "recipientCompany": "",
    "recipientPhone": "+1 555 0100",
    "recipientEmail": "alex@example.com",
    "address1": "12 Example Street",
    "address2": "Apt 4",
    "city": "Springfield",
    "province": "IL",
    "postalCode": "62701",
    "countryCode": "US",
    "channelId": "",
    "buyerNote": "Please leave at the door.",
    "lines": [
      {
        "customerSku": "TEE-BLK-M",
        "title": "Crew neck T-shirt, black, M",
        "qty": 2,
        "productId": ""
      }
    ]
  }),
});
const result = await response.json();
console.log(response.status, result);
```

```python example=python
import os

import requests

response = requests.patch(
    "https://api.rephut.com/v1/orders/cm3o7rd0e0001h2j3k4l5m6n",
    headers={
        "Authorization": f"Bearer {os.environ['FULFILLBOT_API_KEY']}",
        "Idempotency-Key": "a3c9e1f2-7b6d-4e85-8f10-3c2b1a0d9e8f",
    },
    json={
        "storeOrderNo": "1042",
        "recipientName": "Alex Morgan",
        "recipientCompany": "",
        "recipientPhone": "+1 555 0100",
        "recipientEmail": "alex@example.com",
        "address1": "12 Example Street",
        "address2": "Apt 4",
        "city": "Springfield",
        "province": "IL",
        "postalCode": "62701",
        "countryCode": "US",
        "channelId": "",
        "buyerNote": "Please leave at the door.",
        "lines": [
            {
                "customerSku": "TEE-BLK-M",
                "title": "Crew neck T-shirt, black, M",
                "qty": 2,
                "productId": "",
            },
        ],
    },
    timeout=30,
)
print(response.status_code, response.json())
```

```json example=response status=200
{
  "success": true,
  "data": {
    "id": "cm3o7rd0e0001h2j3k4l5m6n",
    "number": "D2611090012",
    "storeOrderNo": "1042",
    "status": "READY",
    "statusLabel": "Ready for fulfilment",
    "sourceLabel": "Manual",
    "createdAt": "2026-11-09T02:00:00.000Z",
    "updatedAt": "2026-11-09T02:30:00.000Z",
    "cancelledAt": null,
    "recipient": {
      "name": "Alex Morgan",
      "company": "",
      "phone": "+1 555 0100",
      "email": "alex@example.com",
      "address1": "12 Example Street",
      "address2": "Apt 4",
      "city": "Springfield",
      "province": "IL",
      "postalCode": "62701",
      "country": "United States",
      "countryCode": "US"
    },
    "buyerNote": "Please leave at the door.",
    "lines": [
      {
        "productId": "cm3p4r0d0001xyz7k2m9q8w1",
        "productSku": "FB-10231-BLK-M",
        "productName": "Cotton crew neck T-shirt",
        "customerSku": "TEE-BLK-M",
        "title": "Crew neck T-shirt, black, M",
        "qty": 2
      }
    ],
    "shippingChannelLabel": "US Standard",
    "shippedInStore": false,
    "currency": "USD",
    "estimatedShippingCents": 640,
    "weightEstimated": false,
    "problems": [],
    "fulfilment": null,
    "payment": null,
    "shippingHold": null,
    "backorder": null
  }
}
```

## Cancel an order that needs attention

`POST /v1/orders/{id}/cancel`

Only orders with status NEEDS_ATTENTION can be cancelled here (otherwise 409 order_not_editable); for other orders contact your account manager. The body is optional.

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/orders/{id}/cancel` |
| Permission | `orders:write` |
| Access | Write: counts towards the write limits and is refused with `account_on_hold` while your account is on hold |
| Idempotency-Key | Optional, recommended |
| Success | 200 OK |

### Path parameters

| Name | Type | Description |
|---|---|---|
| `id` | string | Order id. An id that doesn't exist or isn't yours returns 404 `order_not_found`; an id that isn't a valid id returns 404 `not_found`. |

### Headers

| Header | Required | Value |
|---|---|---|
| `Authorization` | Yes | `Bearer <your API key>`. See [Authentication](https://docs.rephut.com/developers/authentication). |
| `Content-Type` | When you send a body | `application/json` |
| `Idempotency-Key` | No | 1 to 255 visible ASCII characters, new for each new request. See [Idempotency](https://docs.rephut.com/developers/idempotency). |

### Request body

A JSON object. The body is optional: an empty body is treated as `{}`.

| Field | Type | Required | Description |
|---|---|---|---|
| `reason` | string | No | Why you cancel the order, up to 500 characters. Optional; our team can see it. Default: `""`. |

### Response

`200` with `data`, an object with the fields below.

| Field | Type | Description |
|---|---|---|
| `id` | string | Order id. |
| `number` | string | Our order number. |
| `storeOrderNo` | string | Your store's order number; empty when you didn't give one. |
| `status` | string | Order status. See [Order statuses](#order-statuses). One of `NEEDS_ATTENTION`, `READY`, `BACKORDERED`, `RESERVED`, `WEIGHED`, `SHIPPED`, `CANCELLED`. |
| `statusLabel` | string | English text for display. It may change; rely on the code next to it. For `NEEDS_ATTENTION` it names the first problem, for example "SKU not mapped". |
| `sourceLabel` | string | Where the order came from: `Store` (a connected store), `Imported` (a file import) or `Manual` (the portal form or the API). English text; it may change. |
| `createdAt` | string (date-time) | When the order was created. |
| `updatedAt` | string (date-time) | When the order last changed. Any edit through [`PATCH /v1/orders/{id}`](https://docs.rephut.com/developers/reference/orders#change-an-order-that-needs-attention) changes it. |
| `cancelledAt` | string (date-time) or null | When the order was cancelled; null otherwise. |
| `recipient` | object | Who we ship to. |
| `recipient.name` | string | Recipient name. |
| `recipient.company` | string | Company; empty when none. |
| `recipient.phone` | string | Recipient phone number. |
| `recipient.email` | string | Recipient email address. |
| `recipient.address1` | string | Address line 1. |
| `recipient.address2` | string | Address line 2. |
| `recipient.city` | string | City. |
| `recipient.province` | string | State or province. |
| `recipient.postalCode` | string | Postal code. |
| `recipient.country` | string | Country in English. If we don't recognise it, the text the order has, or the code itself when the code is unknown; "—" when there is neither. |
| `recipient.countryCode` | string or null | Two-letter country code; null when we don't recognise the country. |
| `buyerNote` | string | Note from your buyer. |
| `lines` | array of objects | The items in the order. |
| `lines[].productId` | string or null | Our product id; null while the line is not matched to a product. |
| `lines[].productSku` | string or null | Our SKU of the matched product; null while the line isn't matched. |
| `lines[].productName` | string or null | Name of the matched product; null while the line isn't matched. |
| `lines[].customerSku` | string | Your SKU on this line. |
| `lines[].title` | string | Item name from your store or file. |
| `lines[].qty` | integer | Quantity. |
| `shippingChannelLabel` | string | The shipping option the order uses, in English; `Assigned by our team` when we chose it, `—` when none is set yet. |
| `shippedInStore` | boolean | `true` when you fulfilled the order yourself in your store, so we don't ship it. |
| `currency` | string | Currency of every amount in this order. Always `USD`. |
| `estimatedShippingCents` | integer or null | Estimated shipping in US cents; null when it can't be estimated yet, and when `shippedInStore` is `true`. |
| `weightEstimated` | boolean | `true` when a product's weight is unknown and the estimate uses an assumed weight. Only before the parcel is weighed; `false` afterwards. |
| `problems` | array of objects | Why the order needs attention; empty otherwise. Rely on `code`; `message` is English text that may change. |
| `problems[].code` | string | Problem code. See [Order problems](#order-problems). One of `NO_LINES`, `UNBOUND_LINE`, `MISSING_RECIPIENT`, `UNKNOWN_COUNTRY`, `NO_CHANNEL`, `CHANNEL_UNAVAILABLE`. |
| `problems[].message` | string | English text for display. It may change; rely on the code next to it. |
| `fulfilment` | object or null | What our warehouse did with the parcel. Present when the status is `RESERVED`, `WEIGHED` or `SHIPPED`; null otherwise. |
| `fulfilment.trackingNo` | string | Tracking number; empty until the parcel ships. |
| `fulfilment.trackingUrl` | string or null | Link to track the parcel; null when we have none. |
| `fulfilment.shippedAt` | string (date-time) or null | When the parcel shipped; null until then. |
| `fulfilment.actualWeightG` | integer or null | Weighed parcel weight in grams; null until weighed. |
| `fulfilment.freightCents` | integer or null | Shipping charged, in US cents; null until charged. |
| `fulfilment.handlingFeeCents` | integer or null | Handling fee charged, in US cents; null until charged. |
| `fulfilment.surchargeCents` | integer or null | Extra shipping charged later (for example a carrier adjustment), in US cents; null when none. |
| `fulfilment.totalChargedCents` | integer or null | Everything charged for this order, in US cents; null until charged. |
| `payment` | object or null | Shipping charge waiting for wallet funds; null when nothing is due. |
| `payment.freightCents` | integer | Shipping fee waiting to be paid, in US cents. |
| `payment.handlingFeeCents` | integer | Handling fee waiting to be paid, in US cents. |
| `payment.dueCents` | integer | Total waiting to be paid, in US cents. |
| `payment.availableCents` | integer | Your available balance plus the amount already held for this order, in US cents. |
| `payment.topUpCents` | integer | At least this much to top up. |
| `shippingHold` | object or null | The estimated shipping and handling fee held from your available balance while the order waits to ship; null when nothing is held. See [Charges and your wallet](https://docs.rephut.com/developers/charges). |
| `shippingHold.heldCents` | integer | Amount held, in US cents. |
| `shippingHold.topUpCents` | integer | Top up at least this much when your balance doesn't cover the hold; 0 when it does. |
| `backorder` | object or null | What the order is waiting for when some products are short; null otherwise. |
| `backorder.lines` | array of objects | Products short for this order. |
| `backorder.lines[].productSku` | string | Our SKU. |
| `backorder.lines[].productName` | string | Product name. |
| `backorder.lines[].orderedQty` | integer | Quantity ordered. |
| `backorder.lines[].waitingQty` | integer | Quantity still waiting for stock. |
| `backorder.purchaseOrders` | array of objects | Purchase orders that bring the missing stock. |
| `backorder.purchaseOrders[].id` | string | Purchase order id. Fetch it with [`GET /v1/purchase-orders/{id}`](https://docs.rephut.com/developers/reference/purchase-orders#get-a-purchase-order). |
| `backorder.purchaseOrders[].number` | string | Purchase order number. |
| `backorder.purchaseOrders[].statusLabel` | string | English text for display. It may change; rely on the code next to it. |

### Errors

| Code | HTTP status | Meaning |
|---|---|---|
| `invalid_idempotency_key` | 400 | Idempotency-Key must be 1 to 255 visible ASCII characters. |
| `invalid_json` | 400 | The request body is not valid JSON. |
| `invalid_api_key` | 401 | The API key is missing, malformed, revoked or no longer valid. |
| `account_action_required` | 403 | Sign in to the portal and finish the required account steps first. |
| `account_on_hold` | 403 | The account is on hold. Writes are refused; reads still work. |
| `api_not_enabled` | 403 | API access is not enabled for this account. |
| `insufficient_scope` | 403 | The API key does not have the permission this call needs. |
| `not_found` | 404 | The resource does not exist or does not belong to this account. |
| `order_not_found` | 404 | This order could not be found. |
| `order_not_editable` | 409 | This order can no longer be changed or cancelled here. Refresh the page to see its latest status. |
| `request_in_progress` | 409 | A request with this Idempotency-Key is still running or its result is unknown. |
| `payload_too_large` | 413 | The request body is larger than 1 MB. |
| `unsupported_media_type` | 415 | The request body must be sent as application/json. |
| `idempotency_key_reused` | 422 | This Idempotency-Key was already used with a different request. |
| `request_rejected` | 422 | The request was rejected by a business rule. |
| `validation_failed` | 422 | The input is not valid. `details` lists each field and the reason. |
| `rate_limited` | 429 | Too many requests. Wait for the number of seconds in Retry-After. |
| `internal_error` | 500 | Unexpected error. Quote X-Request-Id when you contact support. |

Handle errors by `code`. See [Errors](https://docs.rephut.com/developers/errors).

```bash example=curl
curl -X POST 'https://api.rephut.com/v1/orders/cm3o7rd0e0001h2j3k4l5m6n/cancel' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H 'Idempotency-Key: 0b8e4d7a-5c13-4f92-86a1-d3e7f9c2b410' \
  -d '{
  "reason": "The buyer cancelled in our store."
}'
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/orders/cm3o7rd0e0001h2j3k4l5m6n/cancel", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FULFILLBOT_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "0b8e4d7a-5c13-4f92-86a1-d3e7f9c2b410",
  },
  body: JSON.stringify({
    "reason": "The buyer cancelled in our store."
  }),
});
const result = await response.json();
console.log(response.status, result);
```

```python example=python
import os

import requests

response = requests.post(
    "https://api.rephut.com/v1/orders/cm3o7rd0e0001h2j3k4l5m6n/cancel",
    headers={
        "Authorization": f"Bearer {os.environ['FULFILLBOT_API_KEY']}",
        "Idempotency-Key": "0b8e4d7a-5c13-4f92-86a1-d3e7f9c2b410",
    },
    json={
        "reason": "The buyer cancelled in our store.",
    },
    timeout=30,
)
print(response.status_code, response.json())
```

```json example=response status=200
{
  "success": true,
  "data": {
    "id": "cm3o7rd0e0001h2j3k4l5m6n",
    "number": "D2611090012",
    "storeOrderNo": "1042",
    "status": "CANCELLED",
    "statusLabel": "Cancelled",
    "sourceLabel": "Manual",
    "createdAt": "2026-11-09T02:00:00.000Z",
    "updatedAt": "2026-11-09T03:00:00.000Z",
    "cancelledAt": "2026-11-09T03:00:00.000Z",
    "recipient": {
      "name": "Alex Morgan",
      "company": "",
      "phone": "+1 555 0100",
      "email": "alex@example.com",
      "address1": "12 Example Street",
      "address2": "Apt 4",
      "city": "Springfield",
      "province": "IL",
      "postalCode": "62701",
      "country": "United States",
      "countryCode": "US"
    },
    "buyerNote": "Please leave at the door.",
    "lines": [
      {
        "productId": "cm3p4r0d0001xyz7k2m9q8w1",
        "productSku": "FB-10231-BLK-M",
        "productName": "Cotton crew neck T-shirt",
        "customerSku": "TEE-BLK-M",
        "title": "Crew neck T-shirt, black, M",
        "qty": 2
      }
    ],
    "shippingChannelLabel": "US Standard",
    "shippedInStore": false,
    "currency": "USD",
    "estimatedShippingCents": 640,
    "weightEstimated": false,
    "problems": [],
    "fulfilment": null,
    "payment": null,
    "shippingHold": null,
    "backorder": null
  }
}
```
