# Shipments

A shipment sends goods from your stock in our warehouse in bulk: to an address (`WHOLESALE`) or to an Amazon warehouse (`FBA`). Dropship orders to your buyers are separate: see [Orders](https://docs.rephut.com/developers/reference/orders).

There is no `updatedSince` for shipments: use webhooks and GET, or read the first pages again to find new ones (the list is newest first). See [Incremental sync](https://docs.rephut.com/developers/incremental-sync).

## How a shipment works

1. Optionally, check shipping options and prices with [`POST /v1/shipments/estimate`](https://docs.rephut.com/developers/reference/shipments#estimate-shipping-for-a-shipment-from-your-stock). Nothing is saved.
2. Ask for the shipment with [`POST /v1/shipments`](https://docs.rephut.com/developers/reference/shipments#request-a-shipment-from-your-stock) and an `Idempotency-Key`. The stock is set aside and the shipment is `DRAFT`. Until we send the quote you can cancel it with [`POST /v1/shipments/{id}/cancel`](https://docs.rephut.com/developers/reference/shipments#cancel-a-shipment-you-requested).
3. We pack it, set the shipping and send you the freight quote: the status becomes `PENDING_CONFIRMATION` (webhook `shipment.status_changed`).
4. Read it with [`GET /v1/shipments/{id}`](https://docs.rephut.com/developers/reference/shipments#get-a-shipment) and confirm it with [`POST /v1/shipments/{id}/confirm`](https://docs.rephut.com/developers/reference/shipments#confirm-a-shipment-s-freight-quote), sending the `version` you read. If the shipment changed in between you get `409 shipment_changed`: read it again and confirm the new version.
5. Confirming creates an amount due (`dueCents`); pay it in the portal. We then ship it and add the tracking numbers (webhooks `shipment.status_changed` and `shipment.tracking_updated`).

We also prepare shipments for you; they appear here once we send them to you for confirmation. To send us feedback on a quote instead of confirming it, use the portal.

## Shipment statuses

| Value | Meaning |
|---|---|
| `DRAFT` | Preparing: we are packing it and haven't sent the freight quote yet. A shipment you asked for can still be cancelled. |
| `PENDING_CONFIRMATION` | Awaiting your confirmation of the freight quote. Confirm it with POST /v1/shipments/{id}/confirm. |
| `AWAITING_PAYMENT` | Confirmed; waiting for your payment. Pay shipments in the portal. |
| `PAID` | Paid; waiting to ship. |
| `SHIPPED` | Shipped. `trackingNos` lists the tracking numbers. |
| `CANCELLED` | Cancelled. |

## Shipment kinds

| Value | Meaning |
|---|---|
| `WHOLESALE` | Ships to an address you give. |
| `FBA` | Ships to an Amazon FBA warehouse. |

## Transport modes

| Value | Meaning |
|---|---|
| `AIR` | Air freight. |
| `SEA` | Sea freight. |
| `EXPRESS` | Express. |
| `TRUCK` | Truck. |

## List shipments

`GET /v1/shipments`

Shipments we have sent you for confirmation, and the ones you asked for from your stock. Newest first.

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/shipments` |
| Permission | `shipments: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`. |

### 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 | Shipment id. |
| `number` | string | Our shipment number. |
| `kind` | string | Where it goes. See [Shipment kinds](#shipment-kinds). One of `WHOLESALE`, `FBA`. |
| `kindLabel` | string | English text for display. It may change; rely on the code next to it. |
| `status` | string | Shipment status. See [Shipment statuses](#shipment-statuses). One of `DRAFT`, `PENDING_CONFIRMATION`, `AWAITING_PAYMENT`, `PAID`, `SHIPPED`, `CANCELLED`. |
| `statusLabel` | string | English text for display. It may change; rely on the code next to it. |
| `boxCount` | integer | Number of boxes packed. |
| `currency` | string | Currency of every amount. Always `USD`. |
| `receivableCents` | integer | Total to pay for the shipment, in US cents. |
| `paidCents` | integer | Paid so far minus refunds, in US cents. |
| `createdAt` | string (date-time) | When the shipment was created. |

### 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/shipments?page=1' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY"
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/shipments?page=1", {
  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/shipments?page=1",
    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": "cm3s5hp0m0001ent2w3e4r5t",
      "number": "S2611090002",
      "kind": "WHOLESALE",
      "kindLabel": "Wholesale",
      "status": "PENDING_CONFIRMATION",
      "statusLabel": "Awaiting your confirmation",
      "boxCount": 2,
      "currency": "USD",
      "receivableCents": 19320,
      "paidCents": 0,
      "createdAt": "2026-11-09T04:00:00.000Z"
    }
  ],
  "meta": {
    "total": 1,
    "page": 1,
    "limit": 50,
    "pageCount": 1
  }
}
```

## Get a shipment

`GET /v1/shipments/{id}`

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/shipments/{id}` |
| Permission | `shipments: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 | Shipment id, from [`GET /v1/shipments`](https://docs.rephut.com/developers/reference/shipments#list-shipments) or the response of [`POST /v1/shipments`](https://docs.rephut.com/developers/reference/shipments#request-a-shipment-from-your-stock). 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 | Shipment id. |
| `number` | string | Our shipment number. |
| `kind` | string | Where it goes. See [Shipment kinds](#shipment-kinds). One of `WHOLESALE`, `FBA`. |
| `kindLabel` | string | English text for display. It may change; rely on the code next to it. |
| `status` | string | Shipment status. See [Shipment statuses](#shipment-statuses). One of `DRAFT`, `PENDING_CONFIRMATION`, `AWAITING_PAYMENT`, `PAID`, `SHIPPED`, `CANCELLED`. |
| `statusLabel` | string | English text for display. It may change; rely on the code next to it. |
| `createdAt` | string (date-time) | When the shipment was created. |
| `submittedAt` | string (date-time) or null | When we sent you the freight quote; null before that. |
| `requestedByCustomer` | boolean | `true` when you asked for the shipment (in the portal or the API); `false` when we prepared it for you. |
| `lines` | array of objects | Products in the shipment. |
| `lines[].sku` | string | Our SKU. |
| `lines[].name` | string | Product name in English. |
| `lines[].qty` | integer | Quantity. |
| `transportLabel` | string | How it travels, in English, for example Air freight. |
| `shippingOption` | string or null | Name of the shipping option; "Assigned by our team" when the channel has no public name; null until a channel is set. |
| `address` | object | Where it ships to. |
| `address.recipientName` | string | Recipient name. |
| `address.recipientPhone` | string | Recipient phone number. |
| `address.address1` | string | Address line 1. |
| `address.address2` | string | Address line 2. |
| `address.city` | string | City. |
| `address.province` | string | State or province. |
| `address.postalCode` | string | Postal code. |
| `address.countryCode` | string or null | Two-letter country code; null when not set. |
| `fbaWarehouseCode` | string | Amazon warehouse code for FBA shipments; empty otherwise. |
| `fbaShipmentId` | string | Your Amazon FBA shipment id; empty when not given. |
| `boxes` | array of objects | Packed boxes. |
| `boxes[].boxNo` | integer | Box number. |
| `boxes[].weightG` | integer | Box weight in grams. |
| `boxes[].lengthCm` | number | Length in centimetres. |
| `boxes[].widthCm` | number | Width in centimetres. |
| `boxes[].heightCm` | number | Height in centimetres. |
| `boxes[].lines` | array of objects | Products in this box. |
| `boxes[].lines[].sku` | string | Our SKU. |
| `boxes[].lines[].name` | string | Product name in English. |
| `boxes[].lines[].qty` | integer | Quantity. |
| `boxCount` | integer | Number of boxes packed. |
| `totalWeightG` | integer | Weight of all boxes in grams. |
| `chargeableG` | integer or null | Weight we charge for, in grams. Only set when the shipment is priced automatically from our channel rates (not when freight was entered by hand), and null until that price is worked out. |
| `currency` | string | Currency of every amount. Always `USD`. |
| `freightCents` | integer or null | Freight in US cents; null until quoted. |
| `fees` | array of objects | Other fees on the shipment. |
| `fees[].label` | string | Fee name. |
| `fees[].description` | string | What the fee is for. |
| `fees[].amountCents` | integer | Amount in US cents. |
| `receivableCents` | integer | Total to pay for the shipment, in US cents. |
| `paidCents` | integer | Paid so far minus refunds, in US cents. |
| `dueCents` | integer | Still to pay, in US cents; 0 when the shipment is cancelled. |
| `paymentInstructions` | array of objects | How to pay; empty when nothing is due. |
| `paymentInstructions[].name` | string | Payment method name. |
| `paymentInstructions[].instructions` | string | How to pay with this method. |
| `trackingNos` | array of strings | Tracking numbers; empty until shipped. Webhook `shipment.tracking_updated` tells you when they are added. |
| `shippedAt` | string (date-time) or null | When it shipped; null until then. |
| `confirmedAt` | string (date-time) or null | When the shipment was confirmed (by you or by our team for you); null before that. |
| `canConfirm` | boolean | `true` when you can confirm it now with [`POST /v1/shipments/{id}/confirm`](https://docs.rephut.com/developers/reference/shipments#confirm-a-shipment-s-freight-quote). |
| `canCancel` | boolean | `true` when you can cancel it now with [`POST /v1/shipments/{id}/cancel`](https://docs.rephut.com/developers/reference/shipments#cancel-a-shipment-you-requested): a shipment you asked for that we haven't quoted yet. |
| `version` | string | Send this back when you confirm the shipment. |
| `feedback` | string | Feedback about the quote you sent us from the portal; empty when none. |
| `feedbackAt` | string (date-time) or null | When you sent the feedback; null when none. |

### 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/shipments/cm3s5hp0m0001ent2w3e4r5t' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY"
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/shipments/cm3s5hp0m0001ent2w3e4r5t", {
  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/shipments/cm3s5hp0m0001ent2w3e4r5t",
    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": "cm3s5hp0m0001ent2w3e4r5t",
    "number": "S2611090002",
    "kind": "WHOLESALE",
    "kindLabel": "Wholesale",
    "status": "PENDING_CONFIRMATION",
    "statusLabel": "Awaiting your confirmation",
    "createdAt": "2026-11-09T04:00:00.000Z",
    "submittedAt": "2026-11-10T08:00:00.000Z",
    "requestedByCustomer": true,
    "lines": [
      {
        "sku": "FB-10231-BLK-M",
        "name": "Cotton crew neck T-shirt",
        "qty": 100
      }
    ],
    "transportLabel": "Air freight",
    "shippingOption": "US Air Freight",
    "address": {
      "recipientName": "Alex Morgan",
      "recipientPhone": "+1 555 0100",
      "address1": "40 Example Avenue",
      "address2": "Unit 2",
      "city": "Springfield",
      "province": "IL",
      "postalCode": "62701",
      "countryCode": "US"
    },
    "fbaWarehouseCode": "",
    "fbaShipmentId": "",
    "boxes": [
      {
        "boxNo": 1,
        "weightG": 10400,
        "lengthCm": 50,
        "widthCm": 40,
        "heightCm": 35,
        "lines": [
          {
            "sku": "FB-10231-BLK-M",
            "name": "Cotton crew neck T-shirt",
            "qty": 50
          }
        ]
      },
      {
        "boxNo": 2,
        "weightG": 10400,
        "lengthCm": 50,
        "widthCm": 40,
        "heightCm": 35,
        "lines": [
          {
            "sku": "FB-10231-BLK-M",
            "name": "Cotton crew neck T-shirt",
            "qty": 50
          }
        ]
      }
    ],
    "boxCount": 2,
    "totalWeightG": 20800,
    "chargeableG": 23400,
    "currency": "USD",
    "freightCents": 18720,
    "fees": [
      {
        "label": "Labeling",
        "description": "Carton labels",
        "amountCents": 600
      }
    ],
    "receivableCents": 19320,
    "paidCents": 0,
    "dueCents": 19320,
    "paymentInstructions": [
      {
        "name": "Bank transfer",
        "instructions": "Pay to the account shown in the portal under Wallet → Top up, quoting S2611090002."
      }
    ],
    "trackingNos": [],
    "shippedAt": null,
    "confirmedAt": null,
    "canConfirm": true,
    "canCancel": false,
    "version": "2026-11-10T08:00:00.000Z",
    "feedback": "",
    "feedbackAt": null
  }
}
```

## Estimate shipping for a shipment from your stock

`POST /v1/shipments/estimate`

Lists the shipping options for these products and destination, cheapest first, with prices estimated from the total product weight. Nothing is saved, and it works while your account is on hold.

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/shipments/estimate` |
| Permission | `shipments: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` | Yes | `application/json` |

### Request body

A JSON object. Fields not listed here are ignored.

| Field | Type | Required | Description |
|---|---|---|---|
| `countryCode` | string | Yes | Two-letter country code, for example US. |
| `postalCode` | string | No | Postal code. Some countries are priced by postal code zone; without it you get a price range. Up to 200 characters. Default: `""`. |
| `lines` | array of objects | Yes | Products from your stock (productId from GET /v1/inventory) with quantities. 1 to 200 items. |
| `lines[].productId` | string | Yes | Our product id from [`GET /v1/inventory`](https://docs.rephut.com/developers/reference/inventory#list-your-stock). Not empty. |
| `lines[].qty` | integer | Yes | Quantity. From 1 to 100,000. |

### Response

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

| Field | Type | Description |
|---|---|---|
| `available` | boolean | false when we can't price shipments right now; options is then empty. You can still create the shipment and let us choose. |
| `totalWeightG` | integer | Total weight of the products in your stock; products that aren't in your stock count as 0. |
| `currency` | string | Currency of every price. Always `USD`. |
| `options` | array of objects | Shipping options for the shipment, cheapest first. Empty when none fits the weight or destination. The price is a guide: we price the shipment again from the packed boxes. |
| `options[].channelId` | string | Send this as channelId to use this shipping option. |
| `options[].name` | string | Name of the shipping option in English. |
| `options[].priceCents` | integer | Estimated shipping in US cents. |
| `options[].priceMaxCents` | integer or null | Upper end of the estimate when the price depends on the postal code; null otherwise. |
| `options[].transitLabel` | string | Delivery time in English, for example 7–12 days. |
| `options[].chargeableG` | integer | Weight the price is based on, in grams. |
| `options[].cheapest` | boolean | `true` for the cheapest option. |
| `options[].fastest` | boolean | `true` for the fastest option. |
| `options[].transportMode` | string | How it travels. See [Transport modes](#transport-modes). One of `AIR`, `SEA`, `EXPRESS`, `TRUCK`. |
| `options[].transportLabel` | string | English text for display. It may change; rely on the code next to it. |

### 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. |
| `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/shipments/estimate' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "countryCode": "US",
  "postalCode": "62701",
  "lines": [
    {
      "productId": "cm3p4r0d0001xyz7k2m9q8w1",
      "qty": 100
    }
  ]
}'
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/shipments/estimate", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FULFILLBOT_API_KEY}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    "countryCode": "US",
    "postalCode": "62701",
    "lines": [
      {
        "productId": "cm3p4r0d0001xyz7k2m9q8w1",
        "qty": 100
      }
    ]
  }),
});
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/shipments/estimate",
    headers={
        "Authorization": f"Bearer {os.environ['FULFILLBOT_API_KEY']}",
    },
    json={
        "countryCode": "US",
        "postalCode": "62701",
        "lines": [
            {
                "productId": "cm3p4r0d0001xyz7k2m9q8w1",
                "qty": 100,
            },
        ],
    },
    timeout=30,
)
print(response.status_code, response.json())
```

```json example=response status=200
{
  "success": true,
  "data": {
    "available": true,
    "totalWeightG": 20000,
    "currency": "USD",
    "options": [
      {
        "channelId": "cm1ch0004ussea0000000004",
        "name": "US Sea Freight",
        "priceCents": 9800,
        "priceMaxCents": null,
        "transitLabel": "30–40 days",
        "chargeableG": 20000,
        "cheapest": true,
        "fastest": false,
        "transportMode": "SEA",
        "transportLabel": "Sea freight"
      },
      {
        "channelId": "cm1ch0003usair0000000003",
        "name": "US Air Freight",
        "priceCents": 18000,
        "priceMaxCents": null,
        "transitLabel": "10–15 days",
        "chargeableG": 22500,
        "cheapest": false,
        "fastest": true,
        "transportMode": "AIR",
        "transportLabel": "Air freight"
      }
    ]
  }
}
```

## Request a shipment from your stock

`POST /v1/shipments`

Creates a shipment like the portal's New shipment form and reserves the stock; not enough available stock returns 422 stock_short. We pack it, set the shipping and send you the freight quote; then confirm it with POST /v1/shipments/{id}/confirm. You can cancel it until we send the quote. Creating it doesn't charge your wallet. Send an Idempotency-Key so a retry can't create it twice.

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/shipments` |
| Permission | `shipments: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 |
|---|---|---|---|
| `kind` | string | Yes | WHOLESALE ships to an address, FBA to an Amazon warehouse. One of `WHOLESALE`, `FBA`. |
| `channelId` | string | No | A shipping option from POST /v1/shipments/estimate (options[].channelId). Leave empty to let us choose; then send transportMode. Up to 64 characters. Default: `""`. |
| `transportMode` | string | No | How we should ship it when channelId is empty: AIR, SEA, EXPRESS, TRUCK. Default: `""`. |
| `recipientName` | string | No | Recipient name. Required for WHOLESALE. Up to 200 characters. Default: `""`. |
| `recipientPhone` | string | No | Recipient phone number. Up to 200 characters. Default: `""`. |
| `address1` | string | No | Address line 1. Required for WHOLESALE. Up to 200 characters. Default: `""`. |
| `address2` | string | No | Address line 2. Up to 200 characters. Default: `""`. |
| `city` | string | No | City. Required for WHOLESALE. Up to 200 characters. Default: `""`. |
| `province` | string | No | State or province. Up to 200 characters. Default: `""`. |
| `postalCode` | string | No | Postal code. Up to 200 characters. Default: `""`. |
| `countryCode` | string | Yes | Two-letter country code, for example US. |
| `fbaWarehouseCode` | string | No | Amazon warehouse code; required for FBA. Up to 50 characters. Default: `""`. |
| `fbaShipmentId` | string | No | Your Amazon FBA shipment id, for FBA shipments; optional. Up to 100 characters. Default: `""`. |
| `note` | string | No | Note for our warehouse team. Up to 2,000 characters. Default: `""`. |
| `lines` | array of objects | Yes | Products from your stock (productId from GET /v1/inventory) with quantities; each product once. 1 to 200 items. |
| `lines[].productId` | string | Yes | Our product id from [`GET /v1/inventory`](https://docs.rephut.com/developers/reference/inventory#list-your-stock). Not empty. |
| `lines[].qty` | integer | Yes | Quantity to ship from your available stock. From 1 to 100,000. |

### Response

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

| Field | Type | Description |
|---|---|---|
| `id` | string | Shipment id. |
| `number` | string | Our shipment number. |
| `kind` | string | Where it goes. See [Shipment kinds](#shipment-kinds). One of `WHOLESALE`, `FBA`. |
| `kindLabel` | string | English text for display. It may change; rely on the code next to it. |
| `status` | string | Shipment status. See [Shipment statuses](#shipment-statuses). One of `DRAFT`, `PENDING_CONFIRMATION`, `AWAITING_PAYMENT`, `PAID`, `SHIPPED`, `CANCELLED`. |
| `statusLabel` | string | English text for display. It may change; rely on the code next to it. |
| `createdAt` | string (date-time) | When the shipment was created. |
| `submittedAt` | string (date-time) or null | When we sent you the freight quote; null before that. |
| `requestedByCustomer` | boolean | `true` when you asked for the shipment (in the portal or the API); `false` when we prepared it for you. |
| `lines` | array of objects | Products in the shipment. |
| `lines[].sku` | string | Our SKU. |
| `lines[].name` | string | Product name in English. |
| `lines[].qty` | integer | Quantity. |
| `transportLabel` | string | How it travels, in English, for example Air freight. |
| `shippingOption` | string or null | Name of the shipping option; "Assigned by our team" when the channel has no public name; null until a channel is set. |
| `address` | object | Where it ships to. |
| `address.recipientName` | string | Recipient name. |
| `address.recipientPhone` | string | Recipient phone number. |
| `address.address1` | string | Address line 1. |
| `address.address2` | string | Address line 2. |
| `address.city` | string | City. |
| `address.province` | string | State or province. |
| `address.postalCode` | string | Postal code. |
| `address.countryCode` | string or null | Two-letter country code; null when not set. |
| `fbaWarehouseCode` | string | Amazon warehouse code for FBA shipments; empty otherwise. |
| `fbaShipmentId` | string | Your Amazon FBA shipment id; empty when not given. |
| `boxes` | array of objects | Packed boxes. |
| `boxes[].boxNo` | integer | Box number. |
| `boxes[].weightG` | integer | Box weight in grams. |
| `boxes[].lengthCm` | number | Length in centimetres. |
| `boxes[].widthCm` | number | Width in centimetres. |
| `boxes[].heightCm` | number | Height in centimetres. |
| `boxes[].lines` | array of objects | Products in this box. |
| `boxes[].lines[].sku` | string | Our SKU. |
| `boxes[].lines[].name` | string | Product name in English. |
| `boxes[].lines[].qty` | integer | Quantity. |
| `boxCount` | integer | Number of boxes packed. |
| `totalWeightG` | integer | Weight of all boxes in grams. |
| `chargeableG` | integer or null | Weight we charge for, in grams. Only set when the shipment is priced automatically from our channel rates (not when freight was entered by hand), and null until that price is worked out. |
| `currency` | string | Currency of every amount. Always `USD`. |
| `freightCents` | integer or null | Freight in US cents; null until quoted. |
| `fees` | array of objects | Other fees on the shipment. |
| `fees[].label` | string | Fee name. |
| `fees[].description` | string | What the fee is for. |
| `fees[].amountCents` | integer | Amount in US cents. |
| `receivableCents` | integer | Total to pay for the shipment, in US cents. |
| `paidCents` | integer | Paid so far minus refunds, in US cents. |
| `dueCents` | integer | Still to pay, in US cents; 0 when the shipment is cancelled. |
| `paymentInstructions` | array of objects | How to pay; empty when nothing is due. |
| `paymentInstructions[].name` | string | Payment method name. |
| `paymentInstructions[].instructions` | string | How to pay with this method. |
| `trackingNos` | array of strings | Tracking numbers; empty until shipped. Webhook `shipment.tracking_updated` tells you when they are added. |
| `shippedAt` | string (date-time) or null | When it shipped; null until then. |
| `confirmedAt` | string (date-time) or null | When the shipment was confirmed (by you or by our team for you); null before that. |
| `canConfirm` | boolean | `true` when you can confirm it now with [`POST /v1/shipments/{id}/confirm`](https://docs.rephut.com/developers/reference/shipments#confirm-a-shipment-s-freight-quote). |
| `canCancel` | boolean | `true` when you can cancel it now with [`POST /v1/shipments/{id}/cancel`](https://docs.rephut.com/developers/reference/shipments#cancel-a-shipment-you-requested): a shipment you asked for that we haven't quoted yet. |
| `version` | string | Send this back when you confirm the shipment. |
| `feedback` | string | Feedback about the quote you sent us from the portal; empty when none. |
| `feedbackAt` | string (date-time) or null | When you sent the feedback; null when none. |

### 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. |
| `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. |
| `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. |
| `shipments_unavailable` | 422 | We can't take shipments right now. Please contact your account manager. |
| `stock_short` | 422 | Some products don't have enough available stock. Refresh the page to see the latest quantities and try again. |
| `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/shipments' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H 'Idempotency-Key: c41d9a7e-2f58-4b0c-a6e3-91d7f05b2c84' \
  -d '{
  "kind": "WHOLESALE",
  "channelId": "cm1ch0003usair0000000003",
  "transportMode": "",
  "recipientName": "Alex Morgan",
  "recipientPhone": "+1 555 0100",
  "address1": "40 Example Avenue",
  "address2": "Unit 2",
  "city": "Springfield",
  "province": "IL",
  "postalCode": "62701",
  "countryCode": "US",
  "fbaWarehouseCode": "",
  "fbaShipmentId": "",
  "note": "Two cartons, please.",
  "lines": [
    {
      "productId": "cm3p4r0d0001xyz7k2m9q8w1",
      "qty": 100
    }
  ]
}'
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/shipments", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FULFILLBOT_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "c41d9a7e-2f58-4b0c-a6e3-91d7f05b2c84",
  },
  body: JSON.stringify({
    "kind": "WHOLESALE",
    "channelId": "cm1ch0003usair0000000003",
    "transportMode": "",
    "recipientName": "Alex Morgan",
    "recipientPhone": "+1 555 0100",
    "address1": "40 Example Avenue",
    "address2": "Unit 2",
    "city": "Springfield",
    "province": "IL",
    "postalCode": "62701",
    "countryCode": "US",
    "fbaWarehouseCode": "",
    "fbaShipmentId": "",
    "note": "Two cartons, please.",
    "lines": [
      {
        "productId": "cm3p4r0d0001xyz7k2m9q8w1",
        "qty": 100
      }
    ]
  }),
});
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/shipments",
    headers={
        "Authorization": f"Bearer {os.environ['FULFILLBOT_API_KEY']}",
        "Idempotency-Key": "c41d9a7e-2f58-4b0c-a6e3-91d7f05b2c84",
    },
    json={
        "kind": "WHOLESALE",
        "channelId": "cm1ch0003usair0000000003",
        "transportMode": "",
        "recipientName": "Alex Morgan",
        "recipientPhone": "+1 555 0100",
        "address1": "40 Example Avenue",
        "address2": "Unit 2",
        "city": "Springfield",
        "province": "IL",
        "postalCode": "62701",
        "countryCode": "US",
        "fbaWarehouseCode": "",
        "fbaShipmentId": "",
        "note": "Two cartons, please.",
        "lines": [
            {
                "productId": "cm3p4r0d0001xyz7k2m9q8w1",
                "qty": 100,
            },
        ],
    },
    timeout=30,
)
print(response.status_code, response.json())
```

```json example=response status=201
{
  "success": true,
  "data": {
    "id": "cm3s5hp0m0001ent2w3e4r5t",
    "number": "S2611090002",
    "kind": "WHOLESALE",
    "kindLabel": "Wholesale",
    "status": "DRAFT",
    "statusLabel": "Preparing",
    "createdAt": "2026-11-09T04:00:00.000Z",
    "submittedAt": null,
    "requestedByCustomer": true,
    "lines": [
      {
        "sku": "FB-10231-BLK-M",
        "name": "Cotton crew neck T-shirt",
        "qty": 100
      }
    ],
    "transportLabel": "Air freight",
    "shippingOption": "US Air Freight",
    "address": {
      "recipientName": "Alex Morgan",
      "recipientPhone": "+1 555 0100",
      "address1": "40 Example Avenue",
      "address2": "Unit 2",
      "city": "Springfield",
      "province": "IL",
      "postalCode": "62701",
      "countryCode": "US"
    },
    "fbaWarehouseCode": "",
    "fbaShipmentId": "",
    "boxes": [],
    "boxCount": 0,
    "totalWeightG": 0,
    "chargeableG": null,
    "currency": "USD",
    "freightCents": null,
    "fees": [],
    "receivableCents": 0,
    "paidCents": 0,
    "dueCents": 0,
    "paymentInstructions": [],
    "trackingNos": [],
    "shippedAt": null,
    "confirmedAt": null,
    "canConfirm": false,
    "canCancel": true,
    "version": "2026-11-09T04:00:00.000Z",
    "feedback": "",
    "feedbackAt": null
  }
}
```

## Cancel a shipment you requested

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

Only shipments you requested that we haven't quoted yet (status DRAFT) can be cancelled; their stock is available again. Otherwise 409 shipment_not_cancellable: contact your account manager. No request body.

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/shipments/{id}/cancel` |
| Permission | `shipments: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 | Shipment id. An id that doesn't exist or isn't yours returns 404 `shipment_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). |
| `Idempotency-Key` | No | 1 to 255 visible ASCII characters, new for each new request. See [Idempotency](https://docs.rephut.com/developers/idempotency). |

### Request body

No request body.

### Response

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

| Field | Type | Description |
|---|---|---|
| `id` | string | Shipment id. |
| `number` | string | Our shipment number. |
| `kind` | string | Where it goes. See [Shipment kinds](#shipment-kinds). One of `WHOLESALE`, `FBA`. |
| `kindLabel` | string | English text for display. It may change; rely on the code next to it. |
| `status` | string | Shipment status. See [Shipment statuses](#shipment-statuses). One of `DRAFT`, `PENDING_CONFIRMATION`, `AWAITING_PAYMENT`, `PAID`, `SHIPPED`, `CANCELLED`. |
| `statusLabel` | string | English text for display. It may change; rely on the code next to it. |
| `createdAt` | string (date-time) | When the shipment was created. |
| `submittedAt` | string (date-time) or null | When we sent you the freight quote; null before that. |
| `requestedByCustomer` | boolean | `true` when you asked for the shipment (in the portal or the API); `false` when we prepared it for you. |
| `lines` | array of objects | Products in the shipment. |
| `lines[].sku` | string | Our SKU. |
| `lines[].name` | string | Product name in English. |
| `lines[].qty` | integer | Quantity. |
| `transportLabel` | string | How it travels, in English, for example Air freight. |
| `shippingOption` | string or null | Name of the shipping option; "Assigned by our team" when the channel has no public name; null until a channel is set. |
| `address` | object | Where it ships to. |
| `address.recipientName` | string | Recipient name. |
| `address.recipientPhone` | string | Recipient phone number. |
| `address.address1` | string | Address line 1. |
| `address.address2` | string | Address line 2. |
| `address.city` | string | City. |
| `address.province` | string | State or province. |
| `address.postalCode` | string | Postal code. |
| `address.countryCode` | string or null | Two-letter country code; null when not set. |
| `fbaWarehouseCode` | string | Amazon warehouse code for FBA shipments; empty otherwise. |
| `fbaShipmentId` | string | Your Amazon FBA shipment id; empty when not given. |
| `boxes` | array of objects | Packed boxes. |
| `boxes[].boxNo` | integer | Box number. |
| `boxes[].weightG` | integer | Box weight in grams. |
| `boxes[].lengthCm` | number | Length in centimetres. |
| `boxes[].widthCm` | number | Width in centimetres. |
| `boxes[].heightCm` | number | Height in centimetres. |
| `boxes[].lines` | array of objects | Products in this box. |
| `boxes[].lines[].sku` | string | Our SKU. |
| `boxes[].lines[].name` | string | Product name in English. |
| `boxes[].lines[].qty` | integer | Quantity. |
| `boxCount` | integer | Number of boxes packed. |
| `totalWeightG` | integer | Weight of all boxes in grams. |
| `chargeableG` | integer or null | Weight we charge for, in grams. Only set when the shipment is priced automatically from our channel rates (not when freight was entered by hand), and null until that price is worked out. |
| `currency` | string | Currency of every amount. Always `USD`. |
| `freightCents` | integer or null | Freight in US cents; null until quoted. |
| `fees` | array of objects | Other fees on the shipment. |
| `fees[].label` | string | Fee name. |
| `fees[].description` | string | What the fee is for. |
| `fees[].amountCents` | integer | Amount in US cents. |
| `receivableCents` | integer | Total to pay for the shipment, in US cents. |
| `paidCents` | integer | Paid so far minus refunds, in US cents. |
| `dueCents` | integer | Still to pay, in US cents; 0 when the shipment is cancelled. |
| `paymentInstructions` | array of objects | How to pay; empty when nothing is due. |
| `paymentInstructions[].name` | string | Payment method name. |
| `paymentInstructions[].instructions` | string | How to pay with this method. |
| `trackingNos` | array of strings | Tracking numbers; empty until shipped. Webhook `shipment.tracking_updated` tells you when they are added. |
| `shippedAt` | string (date-time) or null | When it shipped; null until then. |
| `confirmedAt` | string (date-time) or null | When the shipment was confirmed (by you or by our team for you); null before that. |
| `canConfirm` | boolean | `true` when you can confirm it now with [`POST /v1/shipments/{id}/confirm`](https://docs.rephut.com/developers/reference/shipments#confirm-a-shipment-s-freight-quote). |
| `canCancel` | boolean | `true` when you can cancel it now with [`POST /v1/shipments/{id}/cancel`](https://docs.rephut.com/developers/reference/shipments#cancel-a-shipment-you-requested): a shipment you asked for that we haven't quoted yet. |
| `version` | string | Send this back when you confirm the shipment. |
| `feedback` | string | Feedback about the quote you sent us from the portal; empty when none. |
| `feedbackAt` | string (date-time) or null | When you sent the feedback; null when none. |

### Errors

| Code | HTTP status | Meaning |
|---|---|---|
| `invalid_idempotency_key` | 400 | Idempotency-Key must be 1 to 255 visible ASCII characters. |
| `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. |
| `shipment_not_found` | 404 | This shipment could not be found. |
| `request_in_progress` | 409 | A request with this Idempotency-Key is still running or its result is unknown. |
| `shipment_not_cancellable` | 409 | This shipment can no longer be cancelled here because we've already sent you the freight quote. Please contact your account manager. |
| `payload_too_large` | 413 | The request body is larger than 1 MB. |
| `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/shipments/cm3s5hp0m0001ent2w3e4r5t/cancel' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY" \
  -H 'Idempotency-Key: 2e6a9f04-b7d1-4c38-a5e2-60f8c1d94b73'
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/shipments/cm3s5hp0m0001ent2w3e4r5t/cancel", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FULFILLBOT_API_KEY}`,
    "Idempotency-Key": "2e6a9f04-b7d1-4c38-a5e2-60f8c1d94b73",
  },
});
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/shipments/cm3s5hp0m0001ent2w3e4r5t/cancel",
    headers={
        "Authorization": f"Bearer {os.environ['FULFILLBOT_API_KEY']}",
        "Idempotency-Key": "2e6a9f04-b7d1-4c38-a5e2-60f8c1d94b73",
    },
    timeout=30,
)
print(response.status_code, response.json())
```

```json example=response status=200
{
  "success": true,
  "data": {
    "id": "cm3s5hp0m0001ent2w3e4r5t",
    "number": "S2611090002",
    "kind": "WHOLESALE",
    "kindLabel": "Wholesale",
    "status": "CANCELLED",
    "statusLabel": "Cancelled",
    "createdAt": "2026-11-09T04:00:00.000Z",
    "submittedAt": null,
    "requestedByCustomer": true,
    "lines": [
      {
        "sku": "FB-10231-BLK-M",
        "name": "Cotton crew neck T-shirt",
        "qty": 100
      }
    ],
    "transportLabel": "Air freight",
    "shippingOption": "US Air Freight",
    "address": {
      "recipientName": "Alex Morgan",
      "recipientPhone": "+1 555 0100",
      "address1": "40 Example Avenue",
      "address2": "Unit 2",
      "city": "Springfield",
      "province": "IL",
      "postalCode": "62701",
      "countryCode": "US"
    },
    "fbaWarehouseCode": "",
    "fbaShipmentId": "",
    "boxes": [],
    "boxCount": 0,
    "totalWeightG": 0,
    "chargeableG": null,
    "currency": "USD",
    "freightCents": null,
    "fees": [],
    "receivableCents": 0,
    "paidCents": 0,
    "dueCents": 0,
    "paymentInstructions": [],
    "trackingNos": [],
    "shippedAt": null,
    "confirmedAt": null,
    "canConfirm": false,
    "canCancel": false,
    "version": "2026-11-09T05:00:00.000Z",
    "feedback": "",
    "feedbackAt": null
  }
}
```

## Confirm a shipment's freight quote

`POST /v1/shipments/{id}/confirm`

Confirms the shipment as you saw it, like the Confirm button in the portal. Only shipments with status PENDING_CONFIRMATION can be confirmed (otherwise 409 shipment_not_decidable). Confirming a shipment doesn't charge your wallet. It creates an amount due that you pay separately.

| Property | Value |
|---|---|
| URL | `https://api.rephut.com/v1/shipments/{id}/confirm` |
| Permission | `shipments: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 | Shipment id. An id that doesn't exist or isn't yours returns 404 `shipment_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 |
|---|---|---|---|
| `version` | string | Yes | The shipment's version from GET /v1/shipments/{id}. If the shipment changed since, you get 409 shipment_changed: read it again and confirm the new version. Not empty. |

### Response

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

| Field | Type | Description |
|---|---|---|
| `id` | string | Shipment id. |
| `number` | string | Our shipment number. |
| `kind` | string | Where it goes. See [Shipment kinds](#shipment-kinds). One of `WHOLESALE`, `FBA`. |
| `kindLabel` | string | English text for display. It may change; rely on the code next to it. |
| `status` | string | Shipment status. See [Shipment statuses](#shipment-statuses). One of `DRAFT`, `PENDING_CONFIRMATION`, `AWAITING_PAYMENT`, `PAID`, `SHIPPED`, `CANCELLED`. |
| `statusLabel` | string | English text for display. It may change; rely on the code next to it. |
| `createdAt` | string (date-time) | When the shipment was created. |
| `submittedAt` | string (date-time) or null | When we sent you the freight quote; null before that. |
| `requestedByCustomer` | boolean | `true` when you asked for the shipment (in the portal or the API); `false` when we prepared it for you. |
| `lines` | array of objects | Products in the shipment. |
| `lines[].sku` | string | Our SKU. |
| `lines[].name` | string | Product name in English. |
| `lines[].qty` | integer | Quantity. |
| `transportLabel` | string | How it travels, in English, for example Air freight. |
| `shippingOption` | string or null | Name of the shipping option; "Assigned by our team" when the channel has no public name; null until a channel is set. |
| `address` | object | Where it ships to. |
| `address.recipientName` | string | Recipient name. |
| `address.recipientPhone` | string | Recipient phone number. |
| `address.address1` | string | Address line 1. |
| `address.address2` | string | Address line 2. |
| `address.city` | string | City. |
| `address.province` | string | State or province. |
| `address.postalCode` | string | Postal code. |
| `address.countryCode` | string or null | Two-letter country code; null when not set. |
| `fbaWarehouseCode` | string | Amazon warehouse code for FBA shipments; empty otherwise. |
| `fbaShipmentId` | string | Your Amazon FBA shipment id; empty when not given. |
| `boxes` | array of objects | Packed boxes. |
| `boxes[].boxNo` | integer | Box number. |
| `boxes[].weightG` | integer | Box weight in grams. |
| `boxes[].lengthCm` | number | Length in centimetres. |
| `boxes[].widthCm` | number | Width in centimetres. |
| `boxes[].heightCm` | number | Height in centimetres. |
| `boxes[].lines` | array of objects | Products in this box. |
| `boxes[].lines[].sku` | string | Our SKU. |
| `boxes[].lines[].name` | string | Product name in English. |
| `boxes[].lines[].qty` | integer | Quantity. |
| `boxCount` | integer | Number of boxes packed. |
| `totalWeightG` | integer | Weight of all boxes in grams. |
| `chargeableG` | integer or null | Weight we charge for, in grams. Only set when the shipment is priced automatically from our channel rates (not when freight was entered by hand), and null until that price is worked out. |
| `currency` | string | Currency of every amount. Always `USD`. |
| `freightCents` | integer or null | Freight in US cents; null until quoted. |
| `fees` | array of objects | Other fees on the shipment. |
| `fees[].label` | string | Fee name. |
| `fees[].description` | string | What the fee is for. |
| `fees[].amountCents` | integer | Amount in US cents. |
| `receivableCents` | integer | Total to pay for the shipment, in US cents. |
| `paidCents` | integer | Paid so far minus refunds, in US cents. |
| `dueCents` | integer | Still to pay, in US cents; 0 when the shipment is cancelled. |
| `paymentInstructions` | array of objects | How to pay; empty when nothing is due. |
| `paymentInstructions[].name` | string | Payment method name. |
| `paymentInstructions[].instructions` | string | How to pay with this method. |
| `trackingNos` | array of strings | Tracking numbers; empty until shipped. Webhook `shipment.tracking_updated` tells you when they are added. |
| `shippedAt` | string (date-time) or null | When it shipped; null until then. |
| `confirmedAt` | string (date-time) or null | When the shipment was confirmed (by you or by our team for you); null before that. |
| `canConfirm` | boolean | `true` when you can confirm it now with [`POST /v1/shipments/{id}/confirm`](https://docs.rephut.com/developers/reference/shipments#confirm-a-shipment-s-freight-quote). |
| `canCancel` | boolean | `true` when you can cancel it now with [`POST /v1/shipments/{id}/cancel`](https://docs.rephut.com/developers/reference/shipments#cancel-a-shipment-you-requested): a shipment you asked for that we haven't quoted yet. |
| `version` | string | Send this back when you confirm the shipment. |
| `feedback` | string | Feedback about the quote you sent us from the portal; empty when none. |
| `feedbackAt` | string (date-time) or null | When you sent the feedback; null when none. |

### 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. |
| `shipment_not_found` | 404 | This shipment could not be found. |
| `request_in_progress` | 409 | A request with this Idempotency-Key is still running or its result is unknown. |
| `shipment_changed` | 409 | This shipment was updated. Please review it again. |
| `shipment_not_decidable` | 409 | This shipment can no longer be confirmed or commented on. It may have been updated or already confirmed. Please refresh the page. |
| `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/shipments/cm3s5hp0m0001ent2w3e4r5t/confirm' \
  -H "Authorization: Bearer $FULFILLBOT_API_KEY" \
  -H "Content-Type: application/json" \
  -H 'Idempotency-Key: 9a1c5e3d-4f72-4b86-8d09-e7b2a6c0f135' \
  -d '{
  "version": "2026-11-10T08:00:00.000Z"
}'
```

```js example=node
const response = await fetch("https://api.rephut.com/v1/shipments/cm3s5hp0m0001ent2w3e4r5t/confirm", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.FULFILLBOT_API_KEY}`,
    "Content-Type": "application/json",
    "Idempotency-Key": "9a1c5e3d-4f72-4b86-8d09-e7b2a6c0f135",
  },
  body: JSON.stringify({
    "version": "2026-11-10T08:00:00.000Z"
  }),
});
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/shipments/cm3s5hp0m0001ent2w3e4r5t/confirm",
    headers={
        "Authorization": f"Bearer {os.environ['FULFILLBOT_API_KEY']}",
        "Idempotency-Key": "9a1c5e3d-4f72-4b86-8d09-e7b2a6c0f135",
    },
    json={
        "version": "2026-11-10T08:00:00.000Z",
    },
    timeout=30,
)
print(response.status_code, response.json())
```

```json example=response status=200
{
  "success": true,
  "data": {
    "id": "cm3s5hp0m0001ent2w3e4r5t",
    "number": "S2611090002",
    "kind": "WHOLESALE",
    "kindLabel": "Wholesale",
    "status": "AWAITING_PAYMENT",
    "statusLabel": "Awaiting payment",
    "createdAt": "2026-11-09T04:00:00.000Z",
    "submittedAt": "2026-11-10T08:00:00.000Z",
    "requestedByCustomer": true,
    "lines": [
      {
        "sku": "FB-10231-BLK-M",
        "name": "Cotton crew neck T-shirt",
        "qty": 100
      }
    ],
    "transportLabel": "Air freight",
    "shippingOption": "US Air Freight",
    "address": {
      "recipientName": "Alex Morgan",
      "recipientPhone": "+1 555 0100",
      "address1": "40 Example Avenue",
      "address2": "Unit 2",
      "city": "Springfield",
      "province": "IL",
      "postalCode": "62701",
      "countryCode": "US"
    },
    "fbaWarehouseCode": "",
    "fbaShipmentId": "",
    "boxes": [
      {
        "boxNo": 1,
        "weightG": 10400,
        "lengthCm": 50,
        "widthCm": 40,
        "heightCm": 35,
        "lines": [
          {
            "sku": "FB-10231-BLK-M",
            "name": "Cotton crew neck T-shirt",
            "qty": 50
          }
        ]
      },
      {
        "boxNo": 2,
        "weightG": 10400,
        "lengthCm": 50,
        "widthCm": 40,
        "heightCm": 35,
        "lines": [
          {
            "sku": "FB-10231-BLK-M",
            "name": "Cotton crew neck T-shirt",
            "qty": 50
          }
        ]
      }
    ],
    "boxCount": 2,
    "totalWeightG": 20800,
    "chargeableG": 23400,
    "currency": "USD",
    "freightCents": 18720,
    "fees": [
      {
        "label": "Labeling",
        "description": "Carton labels",
        "amountCents": 600
      }
    ],
    "receivableCents": 19320,
    "paidCents": 0,
    "dueCents": 19320,
    "paymentInstructions": [
      {
        "name": "Bank transfer",
        "instructions": "Pay to the account shown in the portal under Wallet → Top up, quoting S2611090002."
      }
    ],
    "trackingNos": [],
    "shippedAt": null,
    "confirmedAt": "2026-11-10T09:30:00.000Z",
    "canConfirm": false,
    "canCancel": false,
    "version": "2026-11-10T09:30:00.000Z",
    "feedback": "",
    "feedbackAt": null
  }
}
```
