# Pirate Ship API

> Every Pirate Ship endpoint on Cotlet: inputs, JSON Schema, cURL, Python and Node.js examples and an example response for each call.

Source: https://cotlet.dev/docs/api/pirate-ship

Docs sections: [Get started](https://cotlet.dev/docs.md) · [REST API](https://cotlet.dev/docs/api.md) · [MCP](https://cotlet.dev/docs/mcp.md) · [SDKs & CLI](https://cotlet.dev/docs/sdks.md) · [Integrations](https://cotlet.dev/docs/integrations.md)

14 endpoints for Pirate Ship. Each one is a POST with a JSON body, called with your API key.

- Base URL: `https://cotlet.dev/api/v1/pirate-ship`
- Connects with: Your own Pirate Ship account, with a payment method or Account Balance for postage
- Integration page: https://cotlet.dev/integrations/pirate-ship-api-mcp-server

## Endpoints

| Endpoint | Access | Description |
|---|---|---|
| `get_rates` | read | USPS and UPS services with price for one package, cheapest first. |
| `get_batch_rates` | read | Rates for every shipment in a batch, like Get Rates with all rows ticked. |
| `list_import_orders` | read | Unshipped orders from your connected store integrations, as in the Import grid. |
| `list_shipments` | read | Your label purchases from the Ship page grid: recipient, carrier, service, cost, tracking number and status. |
| `get_shipment` | read | One shipment with its service, cost, insurance, tracking number and status. |
| `get_account_balance` | read | Account Balance, Pending Refunds and Pending Returns from the Reports page. |
| `list_saved_packages` | read | Your Saved Packages from Settings: size, weight and the extra services stored with each. |
| `export_shipments` | read | Your shipments grid for the current filter as a spreadsheet download link. |
| `create_batch` | write | A batch from spreadsheet-style rows or import orders. Nothing is bought yet. |
| `buy_label` | write | Buy one label from a rate, paid from your Account Balance or payment method. |
| `buy_batch` | write | Buy labels for every shipment in a batch with one service, like Buy Labels. |
| `refund_label` | write | Request a refund for an unused label, like Refund Label. |
| `create_scan_form` | write | USPS End of Day SCAN Form for the labels of one Ship Date, as a PDF link. |
| `schedule_pickup` | write | Ask USPS or UPS to collect your packages, like Schedule a Pickup. |

## Get rates

`POST https://cotlet.dev/api/v1/pirate-ship/get_rates`

Access: read.

Returns each service Pirate Ship offers for this package with its price. Rates change with weight, size, ZIP and packaging, so call it again if any of them changes. Nothing is bought.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `to_address` | object | yes | name, street1, street2, city, state, zip, country. |
| `from_address_id` | string | no | Ship From Address ID. Default: your default Ship From Address. |
| `package` | object | no | weight_oz, and length, width, height in inches. Use this or saved_package_id. |
| `saved_package_id` | string | no | ID of one of your Saved Packages from list_saved_packages. Its size, weight and extra services such as insurance apply. |
| `packaging` | string | no | Type of Packaging as the app lists it, e.g. a box or a poly mailer. Priority Mail packaging works only with Priority Mail. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/get_rates \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to_address":"to address"}'
```

**Python**

```python
result = client.call("pirate-ship", "get_rates", {"to_address":"to address"})
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'get_rates', {"to_address":"to address"});
```

**CLI**

```bash
cotlet call pirate-ship get_rates --data '{"to_address":"to address"}'
```

### Example response

```json
{
  "rates": [
    {
      "rate_id": "r_01UG",
      "carrier": "USPS",
      "service": "Ground Advantage",
      "price_usd": 7.12
    },
    {
      "rate_id": "r_02PM",
      "carrier": "USPS",
      "service": "Priority Mail",
      "price_usd": 9.86
    },
    {
      "rate_id": "r_03UP",
      "carrier": "UPS",
      "service": "Ground",
      "price_usd": 11.4
    }
  ]
}
```

## Get batch rates

`POST https://cotlet.dev/api/v1/pirate-ship/get_batch_rates`

Access: read.

Returns each service with its total for the batch and the price per shipment, so the agent can pick one service for all labels. Shipments with an address problem are listed separately and are skipped until fixed. Nothing is bought.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `batch_id` | string | yes | Batch ID from create_batch. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/get_batch_rates \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"batch_id":"batch_8c41d2"}'
```

**Python**

```python
result = client.call("pirate-ship", "get_batch_rates", {"batch_id":"batch_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'get_batch_rates', {"batch_id":"batch_8c41d2"});
```

**CLI**

```bash
cotlet call pirate-ship get_batch_rates --data '{"batch_id":"batch_8c41d2"}'
```

### Example response

```json
{
  "batch_id": "b_7Q4K2M",
  "shipments": 4,
  "services": [
    {
      "carrier": "USPS",
      "service": "USPS Ground Advantage",
      "total_usd": 32
    },
    {
      "carrier": "UPS",
      "service": "UPS Ground",
      "total_usd": 41.6
    }
  ],
  "address_problems": []
}
```

## List import orders

`POST https://cotlet.dev/api/v1/pirate-ship/list_import_orders`

Access: read.

Returns orders the Import grid shows, which by default are the unfulfilled or unshipped ones. Orders sync on their own; refresh asks for new ones right away. Each order has the ID that create_batch accepts.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `source` | string | no | Only orders from this connected store, like the Source filter. Default: all. |
| `refresh` | boolean | no | Press Refresh Data first. Default: false. |
| `limit` | integer | no | Maximum orders to return. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/list_import_orders \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("pirate-ship", "list_import_orders")
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'list_import_orders');
```

**CLI**

```bash
cotlet call pirate-ship list_import_orders
```

### Example response

```json
{
  "orders": [
    {
      "order_id": "o_4412",
      "source": "Shopify",
      "order_number": "#1042",
      "ship_to": "J. Rivera, Austin, TX 78701"
    },
    {
      "order_id": "o_4413",
      "source": "Shopify",
      "order_number": "#1043",
      "ship_to": "M. Chen, Portland, OR 97205"
    },
    {
      "order_id": "o_4415",
      "source": "eBay",
      "order_number": "22-11044-31",
      "ship_to": "S. Okafor, Atlanta, GA 30303"
    }
  ]
}
```

## List shipments

`POST https://cotlet.dev/api/v1/pirate-ship/list_shipments`

Access: read.

Returns the grid rows that match, newest first. Batches appear as their labels. Use the shipment_id with get_shipment or refund_label.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `search` | string | no | Recipient, Order ID or tracking number, as in the grid search. |
| `status` | string | no | Tracking status as the grid filter names it. |
| `service` | string | no | Shipping service as the grid filter names it. |
| `since` | date | no | Only labels with a Ship Date on or after this date. |
| `cursor` | string | no | Next page cursor from the previous call. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/list_shipments \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("pirate-ship", "list_shipments")
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'list_shipments');
```

**CLI**

```bash
cotlet call pirate-ship list_shipments
```

### Example response

```json
{
  "shipments": [
    {
      "shipment_id": "s_83F2A1",
      "ship_date": "2026-10-08",
      "recipient": "J. Rivera",
      "carrier": "USPS",
      "service": "Ground Advantage",
      "cost_usd": 7.12,
      "tracking": "9400 1112 0000 0000 0001 23",
      "status": "In Transit"
    },
    {
      "shipment_id": "s_83F2A2",
      "ship_date": "2026-10-08",
      "recipient": "M. Chen",
      "carrier": "USPS",
      "service": "Ground Advantage",
      "cost_usd": 8.45,
      "tracking": "9400 1112 0000 0000 0001 30",
      "status": "Delivered"
    }
  ],
  "next_cursor": null
}
```

## Get shipment

`POST https://cotlet.dev/api/v1/pirate-ship/get_shipment`

Access: read.

Returns what the label page shows, including the Email Recipients line that says whether a tracking email was sent. It sends nothing to the customer.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `shipment_id` | string | yes | Shipment ID from list_shipments, buy_label or buy_batch. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/get_shipment \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"shipment_id":"shipment_8c41d2"}'
```

**Python**

```python
result = client.call("pirate-ship", "get_shipment", {"shipment_id":"shipment_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'get_shipment', {"shipment_id":"shipment_8c41d2"});
```

**CLI**

```bash
cotlet call pirate-ship get_shipment --data '{"shipment_id":"shipment_8c41d2"}'
```

### Example response

```json
{
  "shipment_id": "s_83F2A1",
  "ship_date": "2026-10-08",
  "recipient": "J. Rivera",
  "carrier": "USPS",
  "service": "Ground Advantage",
  "cost_usd": 7.12,
  "insurance": "none",
  "tracking": "9400 1112 0000 0000 0001 23",
  "status": "In Transit",
  "tracking_email_sent": false
}
```

## Get account balance

`POST https://cotlet.dev/api/v1/pirate-ship/get_account_balance`

Access: read.

Returns the funds available for labels and the refunds the carrier has not approved yet. Funds come from added credit, approved refunds and adjustments. Call it before buy_batch to see that the balance covers the batch.

### Parameters

No parameters.

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/get_account_balance \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("pirate-ship", "get_account_balance")
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'get_account_balance');
```

**CLI**

```bash
cotlet call pirate-ship get_account_balance
```

### Example response

```json
{
  "account_balance_usd": 84.5,
  "pending_refunds_usd": 7.12,
  "pending_returns_usd": 0
}
```

## List saved packages

`POST https://cotlet.dev/api/v1/pirate-ship/list_saved_packages`

Access: read.

Returns the presets you can pass to get_rates, create_batch and buy_label. A Saved Package holds size and weight, and can also hold extras such as Signature Confirmation or insurance.

### Parameters

No parameters.

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/list_saved_packages \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("pirate-ship", "list_saved_packages")
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'list_saved_packages');
```

**CLI**

```bash
cotlet call pirate-ship list_saved_packages
```

### Example response

```json
{
  "saved_packages": [
    {
      "saved_package_id": "pkg_small_box",
      "name": "Small box",
      "weight_oz": 12,
      "length_in": 9,
      "width_in": 6,
      "height_in": 3,
      "extras": []
    },
    {
      "saved_package_id": "pkg_insured_mailer",
      "name": "Insured mailer",
      "weight_oz": 6,
      "length_in": 12,
      "width_in": 9,
      "height_in": 1,
      "extras": [
        "insurance"
      ]
    }
  ]
}
```

## Export shipments

`POST https://cotlet.dev/api/v1/pirate-ship/export_shipments`

Access: read.

Does what the Export button does: all rows of the current filter or search, with the columns you set in the grid. A blank search exports every label purchase to date.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `search` | string | no | Same search as list_shipments. |
| `status` | string | no | Tracking status as the grid filter names it. |
| `service` | string | no | Shipping service as the grid filter names it. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/export_shipments \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("pirate-ship", "export_shipments")
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'export_shipments');
```

**CLI**

```bash
cotlet call pirate-ship export_shipments
```

### Example response

```json
{
  "rows": 2,
  "download_url": "https://cotlet.dev/api/v1/files/exports/pirate-ship-shipments-2026-10-11.xlsx"
}
```

## Create batch

`POST https://cotlet.dev/api/v1/pirate-ship/create_batch`

Access: write.

Does the work of a spreadsheet upload or an import from a store. Rows without their own weight and size use the package or saved package you pass. Insurance and Extra Services come only from a saved package, and one batch holds one package type. The call reaches no one and spends no money, so it does not ask for your OK.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `rows` | object[] | no | Recipients: name, address, address2, city, state, zip, country, company, email, order_id, weight, length, width, height. Name, address, city, state and zip are required. |
| `order_ids` | string[] | no | Order IDs from list_import_orders, instead of rows. |
| `package` | object | no | weight_oz, and length, width, height in inches. Use this or saved_package_id. |
| `saved_package_id` | string | no | ID of one of your Saved Packages from list_saved_packages. Its size, weight and extra services such as insurance apply. |
| `rubber_stamp` | string[] | no | Up to 3 row fields to print in the corner of each label, e.g. order_id. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/create_batch \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"order_ids":["o_4412","o_4413","o_4415"],"saved_package_id":"pkg_small_box","rubber_stamp":["order_id"]}'
```

**Python**

```python
result = client.call("pirate-ship", "create_batch", {"order_ids":["o_4412","o_4413","o_4415"],"saved_package_id":"pkg_small_box","rubber_stamp":["order_id"]})
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'create_batch', {"order_ids":["o_4412","o_4413","o_4415"],"saved_package_id":"pkg_small_box","rubber_stamp":["order_id"]});
```

**CLI**

```bash
cotlet call pirate-ship create_batch --data '{"order_ids":["o_4412","o_4413","o_4415"],"saved_package_id":"pkg_small_box","rubber_stamp":["order_id"]}'
```

### Example response

```json
{
  "batch_id": "b_9X2LQ1",
  "shipments": 3,
  "rows_with_address_errors": 0,
  "bought": false
}
```

## Buy label

`POST https://cotlet.dev/api/v1/pirate-ship/buy_label`

Access: write. Confirm: waits for the user's OK before it runs.

Spends money: the balance is used first and a payment method must be on file. Returns the shipment ID, the tracking number and the label to print. A bought label can be refunded with refund_label within 28 days. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `rate_id` | string | yes | Rate ID from get_rates. |
| `ship_date` | date | no | Default: today. |
| `extras` | string[] | no | Extra Services such as Signature Confirmation, as the app names them. |
| `insured_value_usd` | number | no | Declared value for added insurance. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/buy_label \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"rate_id":"rate_8c41d2"}'
```

**Python**

```python
result = client.call("pirate-ship", "buy_label", {"rate_id":"rate_8c41d2"})
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'buy_label', {"rate_id":"rate_8c41d2"});
```

**CLI**

```bash
cotlet call pirate-ship buy_label --data '{"rate_id":"rate_8c41d2"}'
```

### Example response

```json
{
  "shipment_id": "s_91AB02",
  "carrier": "USPS",
  "service": "Ground Advantage",
  "cost_usd": 7.12,
  "tracking": "9400 1112 0000 0000 0002 09",
  "labels_url": "https://cotlet.dev/api/v1/files/labels/s_91AB02.pdf"
}
```

## Buy batch

`POST https://cotlet.dev/api/v1/pirate-ship/buy_batch`

Access: write. Confirm: waits for the user's OK before it runs.

Spends money for every label in the batch at once. Returns each shipment with its cost and tracking number, the total, and one link to print all labels. For store orders, Pirate Ship then sends the tracking numbers back to the store. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `batch_id` | string | yes | Batch ID from create_batch. |
| `service` | string | yes | Service from get_batch_rates, e.g. USPS Ground Advantage. |
| `ship_date` | date | no | Default: today. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/buy_batch \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"batch_id":"b_7Q4K2M","service":"USPS Ground Advantage","ship_date":"2026-10-12"}'
```

**Python**

```python
result = client.call("pirate-ship", "buy_batch", {"batch_id":"b_7Q4K2M","service":"USPS Ground Advantage","ship_date":"2026-10-12"})
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'buy_batch', {"batch_id":"b_7Q4K2M","service":"USPS Ground Advantage","ship_date":"2026-10-12"});
```

**CLI**

```bash
cotlet call pirate-ship buy_batch --data '{"batch_id":"b_7Q4K2M","service":"USPS Ground Advantage","ship_date":"2026-10-12"}'
```

### Example response

```json
{
  "batch_id": "b_7Q4K2M",
  "status": "purchased",
  "service": "USPS Ground Advantage",
  "ship_date": "2026-10-12",
  "labels": [
    {
      "shipment_id": "s_91B001",
      "order_id": "#1042",
      "cost_usd": 7.12,
      "tracking": "9400 1112 0000 0000 0003 11"
    },
    {
      "shipment_id": "s_91B002",
      "order_id": "#1043",
      "cost_usd": 7.12,
      "tracking": "9400 1112 0000 0000 0003 28"
    },
    {
      "shipment_id": "s_91B003",
      "order_id": "#1044",
      "cost_usd": 8.45,
      "tracking": "9400 1112 0000 0000 0003 35"
    },
    {
      "shipment_id": "s_91B004",
      "order_id": "#1045",
      "cost_usd": 9.31,
      "tracking": "9400 1112 0000 0000 0003 42"
    }
  ],
  "total_usd": 32,
  "labels_url": "https://cotlet.dev/api/v1/files/labels/b_7Q4K2M.pdf"
}
```

## Refund label

`POST https://cotlet.dev/api/v1/pirate-ship/refund_label`

Access: write. Confirm: waits for the user's OK before it runs.

Works within 28 days of buying the label. USPS labels you have not printed refund instantly, printed ones wait for USPS to confirm they were never scanned, and UPS refunds are usually approved within about an hour. Approved money goes to your Account Balance. A refunded label can no longer be used. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `shipment_id` | string | yes | Shipment ID from list_shipments, buy_label or buy_batch. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/refund_label \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"shipment_id":"s_84C001"}'
```

**Python**

```python
result = client.call("pirate-ship", "refund_label", {"shipment_id":"s_84C001"})
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'refund_label', {"shipment_id":"s_84C001"});
```

**CLI**

```bash
cotlet call pirate-ship refund_label --data '{"shipment_id":"s_84C001"}'
```

### Example response

```json
{
  "shipment_id": "s_83F2A1",
  "carrier": "USPS",
  "status": "refunded",
  "amount_usd": 7.12,
  "credited_to": "Account Balance"
}
```

## Create SCAN Form

`POST https://cotlet.dev/api/v1/pirate-ship/create_scan_form`

Access: write.

Covers USPS labels only. Labels bought after 8:00 PM local time belong to the next day. A created form cannot be edited, so a later label needs a second form. It reaches no one and costs nothing, so it does not ask for your OK.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `ship_date` | date | no | Ship Date group on the End of Day page. Default: today. |
| `batch_id` | string | no | Only the labels of this batch. |
| `from_address_id` | string | no | Ship From Address, when you ship from more than one. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/create_scan_form \
  -H "Authorization: Bearer $COTLET_API_KEY"
```

**Python**

```python
result = client.call("pirate-ship", "create_scan_form")
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'create_scan_form');
```

**CLI**

```bash
cotlet call pirate-ship create_scan_form
```

### Example response

```json
{
  "ship_date": "2026-10-12",
  "usps_labels": 24,
  "scan_form_url": "https://cotlet.dev/api/v1/files/scan-forms/2026-10-12.pdf"
}
```

## Schedule pickup

`POST https://cotlet.dev/api/v1/pirate-ship/schedule_pickup`

Access: write. Confirm: waits for the user's OK before it runs.

USPS pickups are free and run every day except Sunday. UPS pickups are always requested by hand, and the help center lists a fee for them, so check get_account_balance first. The call books a driver visit at your address. By default you confirm each call; you can switch that off for this tool.

### Parameters

| Name | Type | Required | Description |
|---|---|---|---|
| `carrier` | enum | yes | usps or ups. |
| `date` | date | yes | Pickup date. |
| `package_count` | integer | yes | Number of packages. |
| `total_weight_oz` | number | no | Total weight of the packages, required for USPS. |
| `location` | string | no | Where the carrier finds the packages, e.g. front door. |

### Request

**cURL**

```bash
curl -X POST https://cotlet.dev/api/v1/pirate-ship/schedule_pickup \
  -H "Authorization: Bearer $COTLET_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"carrier":"usps","date":"2026-10-13","package_count":24,"total_weight_oz":496,"location":"front porch"}'
```

**Python**

```python
result = client.call("pirate-ship", "schedule_pickup", {"carrier":"usps","date":"2026-10-13","package_count":24,"total_weight_oz":496,"location":"front porch"})
```

**Node.js**

```js
const result = await cotlet.call('pirate-ship', 'schedule_pickup', {"carrier":"usps","date":"2026-10-13","package_count":24,"total_weight_oz":496,"location":"front porch"});
```

**CLI**

```bash
cotlet call pirate-ship schedule_pickup --data '{"carrier":"usps","date":"2026-10-13","package_count":24,"total_weight_oz":496,"location":"front porch"}'
```

### Example response

```json
{
  "carrier": "USPS",
  "date": "2026-10-13",
  "package_count": 12,
  "total_weight_oz": 264,
  "fee_usd": 0,
  "status": "scheduled"
}
```
