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,
"line3": 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"
GET /api/v1/shipping_addresses
Returns sender addresses for the authenticated team. Use these addresses when creating orders via the API.
Parameters
| Parameter | Type | Description |
|---|---|---|
type |
string | "sender" (default) or "all" |
primary |
boolean | "true" to only return primary address |
Response
{
"shipping_addresses": [
{
"id": 1,
"name": "Warehouse",
"line1": "123 Main St",
"line2": null,
"line3": 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/shipping_addresses"
POST /api/v1/shipping_addresses
Creates a new sender address for the team.
Request Body
| Parameter | Type | Required | Description |
|---|---|---|---|
shipping_address.name |
string | Yes | Address name (e.g., "Warehouse") |
shipping_address.line1 |
string | Yes | Street address |
shipping_address.city |
string | Yes | City |
shipping_address.state |
string | Yes | State/Province code |
shipping_address.postal_code |
string | Yes | ZIP/Postal code |
shipping_address.country |
string | Yes | Country code (CA, US) |
shipping_address.primary |
boolean | No | Set as primary address |
Example Request
curl -X POST \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"shipping_address": {
"name": "Warehouse",
"line1": "123 Main St",
"city": "Toronto",
"state": "ON",
"postal_code": "M5H 2N2",
"country": "CA",
"primary": true
}
}' \
"https://your-domain.com/api/v1/shipping_addresses"
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.line3 | string | No | Street address line 3 (additional info) |
| 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) |
| ship_from_address_id | integer | No | ID of sender address (from GET /api/v1/shipping_addresses). Falls back to team's primary address if not specified. |
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",
"line3": null,
"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",
"line3": null,
"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",
"line3": null,
"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"
GET /api/v1/orders/:id/label
Downloads the shipping label for a purchased order. Returns PDF by default (recommended for printing), or PNG/base64 for programmatic access.
Query Parameters
| Parameter | Type | Description |
|---|---|---|
label_format |
string | "pdf" (default), "png", or "base64" |
package_index |
integer | 0-based index for PNG format with multi-package orders (default: 0) |
Response Formats
PDF (default)
Returns a 4x6 inch PDF with all labels (one page per package). Best for thermal printers.
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
"https://your-domain.com/api/v1/orders/123/label" \
-o label.pdf
PNG
Returns a single label as PNG image. Use package_index for multi-package orders.
curl -H "Authorization: Bearer YOUR_JWT_TOKEN" \
"https://your-domain.com/api/v1/orders/123/label?label_format=png" \
-o label.png
Base64 JSON
Returns raw base64 data for each package. Useful for direct thermal printer integration.
{
"labels": [
{
"package_index": 0,
"tracking_number": "1Z999AA1234567890",
"image_format": "gif",
"image_data": "R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7..."
}
]
}
Tip: Check label_available first
The order response includes label_available: true/false and label_count fields. Check these before calling the label endpoint to avoid 404 errors while the shipment is still processing.
Error Responses
// No label available yet
{ "error": "No label available. Order may still be processing." }
// Invalid package index
{ "error": "Package index 2 not found. Order has 1 package(s)." }
// Invalid label_format
{ "error": "Invalid label_format. Use 'pdf', 'png', or 'base64'." }
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"