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"