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

# Migrating from ShipStation

> Move your shipping integration from ShipStation to Package Retriever in hours, not days.

# Migrating from ShipStation

ShipStation moved API access to their \$99/month Gold Plan. Package Retriever's API is free — no subscription, no per-label markup, no surcharge for using your own carrier accounts.

This guide maps every ShipStation API concept to its Package Retriever equivalent so you can migrate your existing integration.

## What you're getting

| Feature                     | ShipStation             | Package Retriever                  |
| --------------------------- | ----------------------- | ---------------------------------- |
| API access                  | \$99/month (Gold Plan)  | **Free**                           |
| BYOA (own carrier accounts) | +\$20/month surcharge   | **No surcharge**                   |
| Rate limits                 | Undocumented            | **Published in docs**              |
| Sandbox environment         | Locked behind Gold Plan | **Free, test key prefix**          |
| Webhook retry behavior      | Undocumented            | **5 attempts, published schedule** |
| Carbon emissions per label  | Not available           | **Included on every rate**         |
| Multi-carrier rate shopping | Yes                     | Yes                                |
| Batch label creation        | Yes (500 max)           | Yes (**5,000 max**)                |

## Authentication

ShipStation uses HTTP Basic Auth with an API Key and Secret. Package Retriever uses a single Bearer token.

```bash theme={null}
# ShipStation
curl -u "API_KEY:API_SECRET" https://ssapi.shipstation.com/orders

# Package Retriever
curl -H "Authorization: Bearer pr_live_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4" \
  https://api.packageretriever.com/v1/rates
```

One header, one key, no base64 encoding.

## Endpoint mapping

| ShipStation Endpoint        | Method | Package Retriever Equivalent                      | Status    |
| --------------------------- | ------ | ------------------------------------------------- | --------- |
| `/shipments/createlabel`    | POST   | `POST /v1/labels`                                 | Available |
| `/shipments/getrates`       | POST   | `POST /v1/rates`                                  | Available |
| `/shipments/voidlabel`      | POST   | `DELETE /v1/labels/{id}`                          | Available |
| `/shipments`                | GET    | `GET /v1/labels/{id}`                             | Available |
| `/carriers`                 | GET    | `GET /v1/carrier-accounts`                        | Available |
| `/carriers/listservices`    | GET    | Included in rate response                         | Available |
| `/webhooks/subscribe`       | POST   | Dashboard settings (single URL)                   | Available |
| `/orders`                   | GET    | Not applicable (use your marketplace integration) | —         |
| `/orders/createorder`       | POST   | Not applicable                                    | —         |
| `/shipments/createshipment` | POST   | `POST /v1/batches`                                | Available |
| `/accounts/listtags`        | GET    | Not applicable                                    | —         |

## Rate shopping

**ShipStation:**

```javascript theme={null}
const response = await fetch('https://ssapi.shipstation.com/shipments/getrates', {
  method: 'POST',
  headers: {
    'Authorization': 'Basic ' + btoa(`${API_KEY}:${API_SECRET}`),
    'Content-Type': 'application/json'
  },
  body: JSON.stringify({
    carrierCode: 'stamps_com',
    fromPostalCode: '94105',
    toPostalCode: '78701',
    toCountry: 'US',
    weight: { value: 16, units: 'ounces' },
    dimensions: { length: 9, width: 6, height: 2, units: 'inches' }
  })
});
```

**Package Retriever:**

```javascript theme={null}
import PackageRetriever from '@packageretriever/sdk';
const pr = new PackageRetriever('pr_live_YOUR_KEY');

const rates = await pr.rates.get({
  from_address: { name: 'Warehouse', street1: '417 Montgomery St', city: 'San Francisco', state: 'CA', zip: '94105', country: 'US' },
  to_address: { name: 'Customer', street1: '123 Main St', city: 'Austin', state: 'TX', zip: '78701', country: 'US' },
  parcel: { weight_oz: 16, length: 9, width: 6, height: 2 }
});

// rates.rates is sorted cheapest-first by default
// Every carrier returned in one response — USPS, UPS, FedEx, Sendle
console.log(rates.rates[0]);
// { carrier: 'USPS', service: 'Ground Advantage', rate_cents: 542, carbon_grams: 142, ... }
```

Key differences:

* All carriers returned in a single response (ShipStation requires separate calls per carrier)
* Sorted cheapest-first by default
* `carbon_grams` included on every rate
* Full address required (not just postal codes) — enables residential surcharge detection

## Label creation

**ShipStation:**

```javascript theme={null}
const label = await fetch('https://ssapi.shipstation.com/shipments/createlabel', {
  method: 'POST',
  headers: { 'Authorization': 'Basic ' + btoa(`${API_KEY}:${API_SECRET}`) },
  body: JSON.stringify({
    carrierCode: 'stamps_com',
    serviceCode: 'usps_ground_advantage',
    packageCode: 'package',
    weight: { value: 16, units: 'ounces' },
    shipFrom: { /* ... */ },
    shipTo: { /* ... */ },
    testLabel: false
  })
});
```

**Package Retriever:**

```javascript theme={null}
// Step 1: Get rates (already done above)
const rates = await pr.rates.get({ /* ... */ });

// Step 2: Purchase the cheapest rate
const label = await pr.labels.create({ rate_id: rates.rates[0].id });

console.log(label.tracking_number); // 9400111899223408065744
console.log(label.label_url);       // PDF download URL
console.log(label.rate_cents);      // 542
```

Key differences:

* Two-step flow: get rates first, then purchase by `rate_id`
* Payment is via prepaid wallet (not per-transaction card charge)
* `wallet_balance_after_cents` returned on every purchase so you always know your balance

## Sandbox / testing

**ShipStation:** Requires Gold Plan (\$99/month) to access API at all. No separate test environment.

**Package Retriever:**

```javascript theme={null}
// Sandbox — use pr_test_ prefix. No billing, no real labels.
const pr = new PackageRetriever('pr_test_a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4');

// Every call works identically — returns realistic responses
const rates = await pr.rates.get({ /* ... */ });
const label = await pr.labels.create({ rate_id: rates.rates[0].id });
// label.tracking_number starts with 9999 (sandbox indicator)
// label.label_url points to a sample PDF
// Wallet is infinite in sandbox mode
```

No separate environment to configure. Same base URL, same endpoints. Just use a test key.

## BYOA (Bring Your Own Account)

**ShipStation:** \$20/month surcharge per carrier account.

**Package Retriever:** Free. Connect via dashboard, use in API automatically.

```javascript theme={null}
// Your BYOA rates appear alongside platform rates — no extra configuration
const rates = await pr.rates.get({ /* ... */ });

// BYOA rates are labeled with account_type: 'byoa'
rates.rates.forEach(rate => {
  console.log(`${rate.carrier} ${rate.service}: $${rate.rate_cents / 100} (${rate.account_type})`);
});
// USPS Ground Advantage: $5.42 (platform)
// UPS Ground: $7.18 (byoa)       ← your negotiated UPS rate
// FedEx Home Delivery: $8.91 (byoa)  ← your negotiated FedEx rate
```

## Webhooks

**ShipStation:** Multiple webhook types, complex subscription management.

**Package Retriever:** One event (`label.created`), one URL, simple HMAC verification.

```javascript theme={null}
// Verify webhook signature (Node.js)
import crypto from 'crypto';

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return signature === `sha256=${expected}`;
}

// In your webhook handler:
app.post('/webhooks/pr', (req, res) => {
  const isValid = verifySignature(
    JSON.stringify(req.body),
    req.headers['pr-signature'],
    process.env.PR_WEBHOOK_SECRET
  );
  if (!isValid) return res.status(401).send('Invalid signature');

  const { event, data } = req.body;
  // event = 'label.created'
  // data = full label object (tracking_number, label_url, carrier, etc.)

  res.status(200).send('OK');
});
```

Retry schedule (published — unlike ShipStation):

* Attempt 1: Immediately
* Attempt 2: 5 minutes
* Attempt 3: 30 minutes
* Attempt 4: 2 hours
* Attempt 5: 24 hours

## Batch label creation

**ShipStation:** Max 500 labels per batch.

**Package Retriever:** Max 5,000 labels per batch with parallel processing.

```javascript theme={null}
const batch = await pr.batches.create({
  items: orders.map(order => ({
    from_address: warehouse,
    to_address: order.address,
    parcel: order.parcel,
    rate_id: order.selectedRateId,
    carrier: order.carrier,
    service: order.service,
    rate_cents: order.rateCents
  }))
});

// Full batch cost deducted from wallet upfront
console.log(batch.total_cost_cents);           // 27100
console.log(batch.wallet_balance_after_cents); // 47300

// Start processing
await pr.batches.buy(batch.id);

// Poll for progress
const status = await pr.batches.get(batch.id);
console.log(status.estimated_completion_percentage); // 45
console.log(status.estimated_time_remaining_seconds); // 120
```

## Error handling

ShipStation returns carrier-native error strings. Package Retriever normalizes every error to a consistent format:

```json theme={null}
{
  "error": {
    "code": "LABEL.ADDRESS.UNDELIVERABLE",
    "message": "The destination address cannot receive this carrier service.",
    "suggestion": "Validate the address with POST /v1/addresses/validate before creating a label.",
    "docs_url": "https://docs.packageretriever.com/reference/errors#LABEL.ADDRESS.UNDELIVERABLE",
    "request_id": "req_8f3kd92ms"
  }
}
```

Every error includes:

* A dot-notation `code` you can catch programmatically (`error.code.startsWith('LABEL.')`)
* A `suggestion` telling you what to do next
* A `docs_url` linking directly to the error documentation
* A `request_id` for support lookups

## Migration checklist

* [ ] Create a Package Retriever account (free)
* [ ] Generate a sandbox API key (`pr_test_` prefix)
* [ ] Replace ShipStation rate calls with `POST /v1/rates`
* [ ] Replace label creation with `POST /v1/labels` (rate\_id from step above)
* [ ] Replace webhook subscriptions with single URL in dashboard settings
* [ ] Fund your wallet (prepaid, minimum \$5)
* [ ] Generate a live API key (`pr_live_` prefix)
* [ ] Swap test key for live key in production
* [ ] Cancel ShipStation subscription

Total migration time: **2-4 hours** for a typical integration.

## Questions?

* Docs: [docs.packageretriever.com](https://docs.packageretriever.com)
* Support: Available to all users regardless of plan (Intercom in dashboard)
* Status: [status.packageretriever.com](https://status.packageretriever.com)
