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_time is set once the package is delivered and empty until then. It's the simplest "is it delivered?" check for every carrier.
  • estimated_delivery is the carrier's current delivery estimate, when it gives one.
  • packages[].activity[] is the scan history, each scan with a time, location, description and the carrier's own status_code.
  • activity[] at shipment level holds scans that belong to the whole shipment. USPS-REST puts all of its scans here, and freight carriers use it too, so read both lists.
  • delivery_detail has who received it (received_by), where it was left and the number of delivery attempts.
  • alternative_tracking_ids lists 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.errors with type: "Warning", next to whatever tracking data UPS did return. Check type before treating an entry as a failure.

FedEx#

  • FedEx occasionally reuses a tracking number. When it does, the response has one entry in packages per shipment, each with its own unique_id and its own scans. Use the scans to tell them apart. With the legacy FedEx carrier, you can send a unique_id as tracking_number to fetch one of them.

USPS#

  • USPS-REST returns its scans in the shipment-level activity list, not under packages.
  • 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.