Package Tracking API for UPS, FedEx, USPS, DHL and More
Yes, there is one REST API that tracks UPS, FedEx, USPS and DHL packages.
In RocketShipIt you send the same JSON request to every carrier, with
"action": "track" and a tracking_number, and you get the same response
shape back: where the package is, every scan, the delivery date and who signed
for it. Only the carrier value changes.
{
"carrier": "UPS-REST",
"action": "track",
"params": {
"key": "your-key-from-authenticate-request",
"tracking_number": "1Z12345E0205271688",
"test": true
}
}
Swap "UPS-REST" for "FedEx-REST", "USPS-REST", "DHL-REST",
"Canada-REST" or any carrier in the table below,
and the request and the response stay the same. You use your own carrier
account credentials, and RocketShipIt talks to each carrier's current API for
you: UPS and FedEx REST with OAuth, and the USPS APIs that replaced Web Tools.
See Shipping Carrier APIs for how that works.
Sending a tracking request#
RocketShipIt is a single HTTPS endpoint that takes JSON and returns JSON. With
the cloud API you post to https://api.rocketship.it/v1 with
your RocketShipIt key in the x-api-key header. If you self-host,
post to your own server instead. The request body is the same either way.
cURL#
curl -X POST \
https://api.rocketship.it/v1 \
-H 'Content-Type: application/json' \
-H 'x-api-key: YOUR_RS_API_KEY' \
-d '{
"carrier": "UPS-REST",
"action": "track",
"params": {
"client_id": "YOUR_UPS_CLIENT_ID",
"client_secret": "YOUR_UPS_CLIENT_SECRET",
"tracking_number": "1Z12345E0205271688",
"test": true
}
}'
For UPS you can pass your UPS app's client_id and client_secret in the
tracking request itself, and RocketShipIt gets the OAuth token for you.
Alternatively, authenticate once and send the returned key (see
REST Authentication).
PHP#
<?php
$request = [
'carrier' => 'FedEx-REST',
'action' => 'track',
'params' => [
'key' => 'your-key-from-authenticate-request',
'tracking_number' => '123456789012',
'test' => true,
],
];
$ch = curl_init('https://api.rocketship.it/v1');
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Content-Type: application/json',
'x-api-key: YOUR_RS_API_KEY',
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($request));
$resp = json_decode(curl_exec($ch), true);
curl_close($ch);
foreach ($resp['data']['packages'] as $package) {
foreach ($package['activity'] as $scan) {
echo $scan['time'] . ' ' . $scan['description'] . ' '
. $scan['location']['city'] . PHP_EOL;
}
}
Python#
import requests
resp = requests.post(
"https://api.rocketship.it/v1",
headers={"x-api-key": "YOUR_RS_API_KEY"},
json={
"carrier": "USPS-REST",
"action": "track",
"params": {
"key": "your-key-from-authenticate-request",
"tracking_number": "9405509206486033133221",
},
},
timeout=30,
)
data = resp.json()["data"]
print("Delivered:", data["delivered_time"] or "not yet")
# USPS returns its scans at shipment level, in data["activity"]
for scan in data["activity"] or []:
print(scan["time"], scan["description"])
JavaScript (Node.js 18+)#
const resp = await fetch("https://api.rocketship.it/v1", {
method: "POST",
headers: {
"Content-Type": "application/json",
"x-api-key": "YOUR_RS_API_KEY",
},
body: JSON.stringify({
carrier: "DHL-REST",
action: "track",
params: {
key: "your-key-from-authenticate-request",
tracking_number: "YOUR_DHL_TRACKING_NUMBER",
},
}),
});
const { meta, data } = await resp.json();
if (data.errors?.length) console.error(data.errors);
console.log(data.estimated_delivery, data.delivered_time);
Request parameters#
| parameter | what it does |
|---|---|
tracking_number |
The number to track. For LTL carriers this is the PRO number. |
tracking_numbers |
An array of numbers. Only OnTrac-REST tracks several in one call. Veho, FlavorCloud and Amazon use the first one. Everyone else takes one tracking_number per request. |
shipment_id |
Used instead of tracking_number by USPS-REST, DHL-PL, Estes, OD-REST and RXO. |
reference_number |
UPS: track by a reference value you put on the label (see below). |
tracking_id_type |
FedEx REST: what kind of reference tracking_number holds, for example PURCHASE_ORDER. |
reference_type |
LTL and regional carriers: what kind of number you sent (PRO, BOL, PO and so on). |
ship_date_begin, ship_date_end |
Date window for reference tracking (UPS and FedEx). |
carrier_code |
FedEx: limit the search to one operating company, for example FDXE or FDXG. |
to_code, to_country |
Destination postal code and country, which narrows a FedEx reference search. |
test |
Send the request to the carrier's test environment. See Test Tracking Numbers. |
All parameters are listed on the Parameters page.
You don't need to ask for scan detail or proof of delivery: RocketShipIt always asks the carrier for the full scan history and, where the carrier supports it, signature and proof of delivery.
The tracking response#
Every carrier returns the same structure. This is an abridged FedEx response:
{
"meta": { "code": 200, "error_message": "" },
"data": {
"errors": null,
"shipment_id": "392754009705",
"origin": { "city": "Okeechobee", "state": "FL", "country": "US" },
"destination": { "city": "VANCOUVER", "state": "WA", "country": "US" },
"service": { "code": "FEDEX_2_DAY", "description": "FedEx 2Day" },
"pickup_date": "2020-05-12T18:39:00-04:00",
"estimated_delivery": "",
"delivered_time": "2020-05-14T15:54:00-07:00",
"weight": { "amount": 2, "unit": "LB" },
"reference_numbers": [{ "code": "SHIPPER_REFERENCE", "value": "500 RW" }],
"packages": [
{
"tracking_number": "392754009705",
"activity": [
{
"description": "Delivered",
"location": { "city": "VANCOUVER", "state": "WA", "postal_code": "98685", "country": "US" },
"time": "2020-05-14T15:54:00-07:00",
"status_code": "DL",
"status_description": "Delivered - Package delivered to recipient address - release authorized",
"signatory": "Signature not required"
},
{
"description": "On FedEx vehicle for delivery",
"location": { "city": "PORTLAND", "state": "OR", "postal_code": "97218", "country": "US" },
"time": "2020-05-14T08:14:00-07:00",
"status_code": "OD",
"status_description": "On FedEx vehicle for delivery"
}
]
}
],
"delivery_detail": { "location_type": "RESIDENCE", "location_desc": "Residence" }
}
}
The fields you will use most:
delivered_timeis set once the package is delivered and empty until then. It's the simplest "is it delivered?" check for every carrier.estimated_deliveryis the carrier's current delivery estimate, when it gives one.packages[].activity[]is the scan history, each scan with atime,location,descriptionand the carrier's ownstatus_code.activity[]at shipment level holds scans that belong to the whole shipment.USPS-RESTputs all of its scans here, and freight carriers use it too, so read both lists.delivery_detailhas who received it (received_by), where it was left and the number of delivery attempts.alternative_tracking_idslists other numbers the carrier knows the shipment by, when it reports them.
Every field is listed under Track. The carrier's raw reply is in the response's transaction log, as for every other action.
Status codes#
status_code and status_type are the carrier's own codes, passed
through, not a RocketShipIt vocabulary. UPS sends a status type (D
delivered, I in transit, X exception, P pickup, M manifest/label
created). FedEx sends event codes such as PU, IT, OD and DL. USPS
sends numeric event codes. Some carriers (Estes, Old Dominion, RXO, Veho,
FlavorCloud) are mapped into readable statuses such as IN_TRANSIT or
DELIVERED.
If you need one status across carriers, key off delivered_time for
delivered, and the newest activity's description for everything else. The
code tables are in each carrier's docs and in the legacy
UPS tracking status codes
page.
Which carriers can be tracked#
| carrier | carrier value |
notes |
|---|---|---|
| UPS | UPS-REST |
Tracking number or reference number. Includes UPS Mail Innovations (details). |
| FedEx | FedEx-REST |
Tracking number or reference (tracking_id_type). |
| FedEx Freight (LTL) | FedEx-Freight |
PRO number. A master PRO returns every handling unit. See FedEx Freight. |
| USPS | USPS-REST |
The USPS APIs that replaced Web Tools. USPS still uses Web Tools. |
| DHL Express | DHL-REST |
Also returns proof of delivery when DHL has it. |
| DHL eCommerce, DHL Global Mail, DHL Unified, DHL Parcel PL | DHL-eCommerce, DHL-GlobalMail, DHL-Unified, DHL-PL |
|
| Canada Post | Canada-REST, Canada |
PIN or DNC number. See Canada Post. |
| OnTrac | OnTrac-REST |
Several numbers in one request with tracking_numbers. |
| Stamps.com | Stamps-REST |
|
| Purolator, DPD, GLS, GSO, Dicom | Purolator, DPD, GLS, GSO, Dicom |
|
| Estes, Old Dominion, RXO, C.H. Robinson | Estes, OD-REST, RXO, CHRobinson |
LTL. Track by PRO, or by BOL/PO with reference_type. See Estes and Old Dominion. |
| Veho, FlavorCloud, Amazon Shipping | Veho, FlavorCloud, Amazon |
Royal Mail, DHL Germany, FedEx International Mail Service and USPS eVS don't
offer tracking through RocketShipIt; a track request to them returns
unsupported action.
Tracking by reference number#
Instead of a tracking number, you can track by a reference you printed on the label: an order, invoice, PO or RMA number (see Add Reference Values to Labels). Carriers only find these for shipments made on your own account.
UPS takes reference_number and a pickup date window. Without one, it
searches the last 14 days. UPS accepts a window of at most 90 days.
{
"carrier": "UPS-REST",
"action": "track",
"params": {
"key": "your-key-from-authenticate-request",
"reference_number": "371296",
"ship_date_begin": "20250701",
"ship_date_end": "20250801"
}
}
FedEx takes the reference in tracking_number and its type in
tracking_id_type. Narrow it with the destination and dates (YYYY-MM-DD).
Without dates it searches the last six months.
{
"carrier": "FedEx-REST",
"action": "track",
"params": {
"key": "your-key-from-authenticate-request",
"tracking_number": "318511",
"tracking_id_type": "PURCHASE_ORDER",
"carrier_code": "FDXE",
"to_country": "US",
"to_code": "01835",
"ship_date_begin": "2024-05-01",
"ship_date_end": "2024-05-24"
}
}
LTL carriers (Estes, Old Dominion, RXO) take tracking_number with a
reference_type such as BOL or PO. Without one they assume a PRO number,
except RXO, which assumes a bill of lading.
Polling vs. webhooks#
A tracking request is a poll: it returns the status at the moment you ask.
Most integrations poll open shipments a few times a day and stop once
delivered_time is set. To have UPS or FedEx push each new scan to your server
instead, see Tracking Webhooks.
Testing#
Send "test": true with one of the carriers' test tracking numbers to get a
canned response without a real shipment. The numbers and the status each one
returns are on the Test Tracking Numbers page.
Carrier notes#
UPS#
- Mail Innovations parcels have a UPS number and a USPS Package Identification Code. Store both at label time: scans on the USPS leg show up under the USPS number. See UPS Mail Innovations.
- UPS warnings come back in
data.errorswithtype: "Warning", next to whatever tracking data UPS did return. Checktypebefore treating an entry as a failure.
FedEx#
- FedEx occasionally reuses a tracking number. When it does, the response has
one entry in
packagesper shipment, each with its ownunique_idand its own scans. Use the scans to tell them apart. With the legacyFedExcarrier, you can send aunique_idastracking_numberto fetch one of them.
USPS#
USPS-RESTreturns its scans in the shipment-levelactivitylist, not underpackages.- USPS is retiring Web Tools. If you still use
"carrier": "USPS", see Carrier API REST Migration Deadlines.
API Examples#
Full request examples for every carrier are in the API Examples, for instance UPS, UPS by reference, FedEx, USPS, FedEx Freight and OnTrac.