Skip to main content

Documentation Index

Fetch the complete documentation index at: https://packageretriever.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

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

FeatureShipStationPackage Retriever
API access$99/month (Gold Plan)Free
BYOA (own carrier accounts)+$20/month surchargeNo surcharge
Rate limitsUndocumentedPublished in docs
Sandbox environmentLocked behind Gold PlanFree, test key prefix
Webhook retry behaviorUndocumented5 attempts, published schedule
Carbon emissions per labelNot availableIncluded on every rate
Multi-carrier rate shoppingYesYes
Batch label creationYes (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.
# 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 EndpointMethodPackage Retriever EquivalentStatus
/shipments/createlabelPOSTPOST /v1/labelsAvailable
/shipments/getratesPOSTPOST /v1/ratesAvailable
/shipments/voidlabelPOSTDELETE /v1/labels/{id}Available
/shipmentsGETGET /v1/labels/{id}Available
/carriersGETGET /v1/carrier-accountsAvailable
/carriers/listservicesGETIncluded in rate responseAvailable
/webhooks/subscribePOSTDashboard settings (single URL)Available
/ordersGETNot applicable (use your marketplace integration)
/orders/createorderPOSTNot applicable
/shipments/createshipmentPOSTPOST /v1/batchesAvailable
/accounts/listtagsGETNot applicable

Rate shopping

ShipStation:
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:
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:
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:
// 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:
// 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.
// 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.
// 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.
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:
{
  "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?