PackageSave API

A RESTful API for managing shipping and logistics data.

Authentication

The PackageSave API uses JWT tokens for authentication. You can obtain your API token from your team's account settings page.

Using your API token

Include your token in one of two ways:

⚠️ Security Note

Keep your API token secure and never share it publicly. Anyone with this token can access your team's data.

Method 1: Authorization Header (Recommended)

Authorization: Bearer YOUR_JWT_TOKEN

Method 2: Query Parameter

GET /api/v1/current?access_token=YOUR_JWT_TOKEN

The Authorization header method is preferred for production use. The query parameter method is useful for testing in browsers.

GET /api/v1/current

Returns information about the current authenticated team.

Response

{
  "team_current": {
    "id": 1,
    "name": "Acme Corp",
    "address": {
      "id": 1,
      "name": "Headquarters",
      "line1": "123 Main St",
      "line2": null,
      "city": "Toronto",
      "state": "ON",
      "postal_code": "M5H 2N2",
      "country": "CA",
      "primary": true,
      "residential": false,
      "full_address": "123 Main St, Toronto, ON, M5H 2N2, CA"
    }
  }
}

Example Request

curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
     https://your-domain.com/api/v1/current

GET /api/v1/customers

Returns a paginated list of customers for the authenticated team.

Parameters

Parameter Type Description
page integer Page number (default: 1)
per_page integer Items per page (1-100, default: 20)
q string Search customers by name or email

Response

{
  "customers": [
    {
      "id": 1,
      "first_name": "John",
      "last_name": "Doe",
      "company_name": "Acme Inc",
      "email": "john@acme.com",
      "phone_number": "+1-555-0123",
      "name": "John Doe",
      "created_at": "2023-01-15T10:30:00Z",
      "updated_at": "2023-01-15T10:30:00Z"
    }
  ],
  "pagination": {
    "current_page": 1,
    "per_page": 20,
    "total_count": 45,
    "total_pages": 3
  }
}

Example Requests

Basic listing

curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
     "https://your-domain.com/api/v1/customers"

With pagination

curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
     "https://your-domain.com/api/v1/customers?page=2&per_page=10"

With search

curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
     "https://your-domain.com/api/v1/customers?q=john@acme.com"

POST /api/v1/orders

Creates a new order with customer and shipping information. Automatically enqueues a background job to fetch shipping rates.

Request Body

Parameter Type Required Description
customer object Yes Customer information
customer.first_name string Yes Customer first name
customer.last_name string Yes Customer last name
customer.email string No Customer email (used for lookup)
customer.phone_number string No Customer phone number
customer.company_name string No Customer company name
shipping_address object Yes Shipping address information
shipping_address.line1 string Yes Street address line 1
shipping_address.line2 string No Street address line 2 (apt, suite, etc.)
shipping_address.city string Yes City name
shipping_address.state string Yes State/Province code (e.g., CA, ON)
shipping_address.postal_code string Yes ZIP/Postal code
shipping_address.country string Yes Country code (US, CA)
shipping_address.residential boolean No Whether address is residential (default: false)
packages array Yes Array of package objects
packages[].weight decimal Yes Package weight in kilograms
packages[].length decimal Yes Package length in centimeters
packages[].width decimal Yes Package width in centimeters
packages[].height decimal Yes Package height in centimeters
packages[].value decimal Yes Declared value for insurance (in CAD)

Response

{
  "order": {
    "id": 123,
    "status": "pending",
    "tracking_number": null,
    "shipment_id_number": null,
    "rated_price": null,
    "currency": null,
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T10:30:00Z",
    "last_rated_at": null,
    "residential_delivery": false,
    "customer": {
      "id": 456,
      "first_name": "John",
      "last_name": "Doe",
      "company_name": "Acme Corp",
      "email": "john@example.com",
      "phone_number": "+1-555-0123",
      "name": "John Doe",
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-15T10:30:00Z"
    },
    "ship_to_address": {
      "id": 789,
      "name": "API Order Address",
      "line1": "123 Main St",
      "line2": "Suite 100",
      "city": "Toronto",
      "state": "ON",
      "postal_code": "M5V 3A8",
      "country": "CA",
      "primary": false,
      "residential": false,
      "full_address": "123 Main St, Suite 100, Toronto, ON, M5V 3A8, CA"
    },
    "packages": [
      {
        "id": 101,
        "weight": "2.5",
        "length": "30.0",
        "width": "20.0",
        "height": "15.0",
        "value": "100.0",
        "description": "Test description",
        "created_at": "2024-01-15T10:30:00Z",
        "updated_at": "2024-01-15T10:30:00Z"
      },
      {
        "id": 102,
        "weight": "1.2",
        "length": "25.0",
        "width": "15.0",
        "height": "10.0",
        "value": "50.0",
        "description": "Test description",
        "created_at": "2024-01-15T10:30:00Z",
        "updated_at": "2024-01-15T10:30:00Z"
      }
    ]
  }
}

Example Request

curl -X POST \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "customer": {
      "first_name": "John",
      "last_name": "Doe",
      "email": "john@example.com",
      "phone_number": "+1-555-0123",
      "company_name": "Acme Corp"
    },
    "shipping_address": {
      "line1": "123 Main St",
      "line2": "Suite 100",
      "city": "Toronto",
      "state": "ON",
      "postal_code": "M5V 3A8",
      "country": "CA",
      "residential": false
    },
    "packages": [
      {
        "weight": 2.5,
        "length": 30,
        "width": 20,
        "height": 15,
        "value": 100.00
      },
      {
        "weight": 1.2,
        "length": 25,
        "width": 15,
        "height": 10,
        "value": 50.00
      }
    ]
  }' \
  "https://your-domain.com/api/v1/orders"

GET /api/v1/orders/:id

Retrieves details for a specific order including customer, shipping address, and packages.

URL Parameters

Parameter Type Description
id integer Order ID

Response

{
  "order": {
    "id": 123,
    "status": "purchased",
    "tracking_number": "1Z999AA1234567890",
    "shipment_id_number": "SHP-2024-0123",
    "rated_price": "27.50",
    "currency": "CAD",
    "created_at": "2024-01-15T10:30:00Z",
    "updated_at": "2024-01-15T11:45:00Z",
    "last_rated_at": "2024-01-15T10:31:00Z",
    "residential_delivery": false,
    "customer": {
      "id": 456,
      "first_name": "John",
      "last_name": "Doe",
      "company_name": "Acme Corp",
      "email": "john@example.com",
      "phone_number": "+1-555-0123",
      "name": "John Doe",
      "created_at": "2024-01-15T10:30:00Z",
      "updated_at": "2024-01-15T10:30:00Z"
    },
    "ship_to_address": {
      "id": 789,
      "name": "API Order Address",
      "line1": "123 Main St",
      "line2": "Suite 100",
      "city": "Toronto",
      "state": "ON",
      "postal_code": "M5V 3A8",
      "country": "CA",
      "primary": false,
      "residential": false,
      "full_address": "123 Main St, Suite 100, Toronto, ON, M5V 3A8, CA"
    },
    "packages": [
      {
        "id": 101,
        "weight": "2.5",
        "length": "30.0",
        "width": "20.0",
        "height": "15.0",
        "value": "100.0",
        "description": "Test description",
        "created_at": "2024-01-15T10:30:00Z",
        "updated_at": "2024-01-15T10:30:00Z"
      },
      {
        "id": 102,
        "weight": "1.2",
        "length": "25.0",
        "width": "15.0",
        "height": "10.0",
        "value": "50.0",
        "description": "Test description",
        "created_at": "2024-01-15T10:30:00Z",
        "updated_at": "2024-01-15T10:30:00Z"
      }
    ]
  }
}

Response Fields

Field Type Description
status string Order status: pending, processing, created, purchased, completed, cancelled, failed
tracking_number string UPS tracking number (available after purchase)
shipment_id_number string Internal shipment identifier
rated_price decimal Final price after markup (available after rate selection)
currency string Currency code (CAD, USD)
last_rated_at datetime Timestamp of last rate fetch
residential_delivery boolean Whether delivery is to a residential address

Example Request

curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
     "https://your-domain.com/api/v1/orders/123"

GET /api/v1/orders/:id/shipping_rates

Retrieves available shipping rates for an order. Returns "pending" status if rates are still being calculated.

Response (Ready)

{
  "status": "ready",
  "shipping_rates": [
    {
      "id": 789,
      "service_code": "03",
      "service_name": "Ground",
      "base_price": "25.00",
      "marked_up_price": "27.50",
      "currency": "CAD",
      "transit_days": 3
    }
  ]
}

Response (Pending)

{
  "status": "pending",
  "message": "Shipping rates are being calculated"
}

Example Request

curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
     "https://your-domain.com/api/v1/orders/123/shipping_rates"

POST /api/v1/orders/:id/refresh_shipping_rates

Invalidates existing shipping rates and re-enqueues the rate fetching job. Use this when order details have changed.

Response

{
  "status": "success",
  "message": "Shipping rates refresh initiated"
}

Example Request

curl -X POST \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  "https://your-domain.com/api/v1/orders/123/refresh_shipping_rates"

POST /api/v1/orders/:id/purchase

Purchases a shipping label for the order using the selected shipping rate. Enqueues a background job to create the shipment.

Request Body

Parameter Type Required Description
shipping_rate_id integer Yes ID of the selected shipping rate

Response

{
  "status": "success",
  "message": "Order purchase initiated",
  "order": {
    "id": 123,
    "status": "processing",
    "selected_rate": {
      "id": 789,
      "service_name": "Ground"
    }
  }
}

Example Request

curl -X POST \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"shipping_rate_id": 789}' \
  "https://your-domain.com/api/v1/orders/123/purchase"

POST /api/v1/orders/:id/void_shipment

Voids a purchased shipment and cancels the order. The shipment must be in "purchased" status and have a shipment ID. This will refund the shipping charges and update the order status to "cancelled".

URL Parameters

Parameter Type Description
id integer Order ID

Requirements

  • Order must be in "purchased" status
  • Order must have a shipment ID number
  • Shipment must not have been delivered

Response (Success)

{
  "status": "success",
  "message": "Shipment has been successfully voided",
  "order": {
    "id": 123,
    "status": "cancelled",
    "tracking_number": null,
    "shipment_id_number": "SHP-2024-0123",
    "voided_at": "2024-01-15T12:00:00Z",
    "customer": {
      "id": 456,
      "first_name": "John",
      "last_name": "Doe",
      "email": "john@example.com"
    }
  }
}

Response (Error)

{
  "error": "Order cannot be voided - it is not in purchased status"
}

What Happens When Voiding

  • The shipment is cancelled with UPS
  • Tracking numbers are removed from packages
  • Shipping labels are deleted
  • A negative balance transaction is created to refund the order value
  • Order status is updated to "cancelled"
  • The voided_at timestamp is set

Example Request

curl -X POST \
  -H "Authorization: Bearer YOUR_JWT_TOKEN" \
  "https://your-domain.com/api/v1/orders/123/void_shipment"