whereparcel
Stuck on integration? Have a question? The developer is standing by.
API Reference

Shipment Tracking API Reference — REST Endpoints | WhereParcel

Real-time package & shipment tracking REST API. Single and bulk endpoints, auto carrier detection, code examples in cURL/Node/Python/PHP/Go. 7-day free trial.

EndpointPOST/v2/track

Track parcels (billable)

Overview

Track up to 5 parcels at once. Average response time 5 seconds per item. Supports 500+ carriers including USPS, UPS, FedEx, DHL, and more.

Some carriers are limited to 1 item per request:

The carriers listed below take substantially longer to look up, so a single synchronous request may contain at most 1 item across all of them combined — mixing two of them in one request is rejected as well. You can still batch them with other carriers: 1 item from this list + up to 4 items from any other carrier = 5 items total.

us.ups, us.usps, us.fedex, us.dhl, us.dhl.express, us.dhl.ecommerce, intl.ups, intl.usps, intl.fedex, intl.dhl, gb.royalmail, gb.royalmail.special, gb.evri, gb.ups, ca.ups, ca.fedex, de.ups, es.ups, ie.ups, au.post

This list may change. Exceeding the limit returns INVALID_REQUEST, and the error message names the exact carriers that triggered it. To track several items from these carriers, use /v2/webhooks/register (up to 100 items, asynchronous).

Events without a time: some carriers give only a date for certain events and no scan time (USPS does this for "In Transit to Next Facility" and "Pre-Shipment"). In that case timestamp is filled with noon on that date to stay valid ISO 8601, and the event carries timeUnknown: true. When the field is absent, the time came from the carrier and is real.

When to use this endpoint: Use /v2/track for on-demand, one-off lookups — for example, when a customer checks their order status on your website.

For continuous monitoring, use Webhooks instead. If you need to keep your database in sync with delivery status (e.g., updating order records, triggering notifications), register a webhook subscription via /v2/webhooks/register with recurring: true. The webhook approach is far more efficient — instead of polling repeatedly, you receive a push notification only when the status actually changes.

Rate Limits: Each API key has per-minute and per-month request quotas based on your plan. When exceeded, the API returns HTTP 429. See response headers X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset for current usage.

Plan Requests/min Requests/month
Starter 30 10,000
Pro 60 30,000
Business 200 300,000

Example Request

// npm install whereparcel
import { WhereParcel } from 'whereparcel';

const wp = new WhereParcel('wp_test_public_demo_d5waw8abfqor', 'sk_test_public_demo_mj7ya1taqmaqfkv6lpwe');

const response = await wp.trackBulk([
  {
    "carrier": "us.ups",
    "trackingNumber": "1Z999AA10123456784"
  },
  {
    "carrier": "us.ontrac",
    "trackingNumber": "D10017048734180"
  }
]);

console.log(response.summary);
for (const result of response.results) {
  if (result.status === 'success') {
    console.log(result.data.deliveryStatus);
  }
}
curl -X POST https://api.whereparcel.com/v2/track \
  -H "Authorization: Bearer wp_test_public_demo_d5waw8abfqor:sk_test_public_demo_mj7ya1taqmaqfkv6lpwe" \
  -H "Content-Type: application/json" \
  -d '{
    "trackingItems": [
      {
        "carrier": "us.ups",
        "trackingNumber": "1Z999AA10123456784"
      },
      {
        "carrier": "us.ontrac",
        "trackingNumber": "D10017048734180"
      }
    ]
  }'
const response = await fetch('https://api.whereparcel.com/v2/track', {
  method: 'POST',
  headers: {
    "Authorization": "Bearer wp_test_public_demo_d5waw8abfqor:sk_test_public_demo_mj7ya1taqmaqfkv6lpwe",
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
      "trackingItems": [
        {
          "carrier": "us.ups",
          "trackingNumber": "1Z999AA10123456784"
        },
        {
          "carrier": "us.ontrac",
          "trackingNumber": "D10017048734180"
        }
      ]
    })
});

const data = await response.json();
console.log(data);
<?php

$ch = curl_init();

curl_setopt($ch, CURLOPT_URL, 'https://api.whereparcel.com/v2/track');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST');

$headers = [
  'Authorization: Bearer wp_test_public_demo_d5waw8abfqor:sk_test_public_demo_mj7ya1taqmaqfkv6lpwe',
  'Content-Type: application/json'
];
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);

$data = [
  'trackingItems' => [
    '0' => [
      'carrier' => "us.ups",
      'trackingNumber' => "1Z999AA10123456784"
    ],
    '1' => [
      'carrier' => "us.ontrac",
      'trackingNumber' => "D10017048734180"
    ]
  ]
];
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));

$response = curl_exec($ch);
curl_close($ch);

$result = json_decode($response, true);
print_r($result);
import requests
import json

url = 'https://api.whereparcel.com/v2/track'

headers = {
    'Authorization': 'Bearer wp_test_public_demo_d5waw8abfqor:sk_test_public_demo_mj7ya1taqmaqfkv6lpwe',
    'Content-Type': 'application/json'
}

data = {
    'trackingItems': {
        '0': {
            'carrier': "us.ups",
            'trackingNumber': "1Z999AA10123456784"
        },
        '1': {
            'carrier': "us.ontrac",
            'trackingNumber': "D10017048734180"
        }
    }
}

response = requests.post(url, headers=headers, json=data)

result = response.json()
print(result)
package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
)

func main() {
	url := "https://api.whereparcel.com/v2/track"

	payload := []byte(`{"trackingItems":[{"carrier":"us.ups","trackingNumber":"1Z999AA10123456784"},{"carrier":"us.ontrac","trackingNumber":"D10017048734180"}]}`)
	req, _ := http.NewRequest("POST", url, bytes.NewBuffer(payload))

	req.Header.Add("Authorization", "Bearer wp_test_public_demo_d5waw8abfqor:sk_test_public_demo_mj7ya1taqmaqfkv6lpwe")
	req.Header.Add("Content-Type", "application/json")

	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()

	body, _ := io.ReadAll(res.Body)
	fmt.Println(string(body))
}

TIP: Replace the test API key with your actual API key from the dashboard.

Request Body

Name Type Required Description
trackingItems array Required
trackingItems[].carrier string Required
Carrier code (e.g., 'kr.cj', 'us.fedex', 'de.gls')
trackingItems[].trackingNumber string Required
Tracking number
trackingItems[].clientId string Optional
Client tracking ID (returned as-is in the response)
trackingItems[].postalCode string Optional
Postal code (required/optional for some carriers, e.g., GLS Germany)
trackingItems[].phoneNumber string Optional
Phone number (required/optional for some carriers)

Response

Success Response (200)

200 OK

Successful tracking response

Response Body

{
  "success": true,
  "results": [
    {
      "carrier": "us.fedex",
      "trackingNumber": "231300687629630",
      "clientId": "order-123",
      "status": "success",
      "billable": true,
      "cached": false,
      "data": {
        "deliveryStatus": "delivered",
        "events": [
          {
            "timestamp": "2026-02-05T14:30:00-05:00",
            "status": "delivered",
            "location": "New York, NY",
            "description": "Delivered, left at front door"
          },
          {
            "timestamp": "2026-02-05T09:15:00-05:00",
            "status": "out_for_delivery",
            "location": "Brooklyn, NY",
            "description": "On FedEx vehicle for delivery"
          },
          {
            "timestamp": "2026-02-04T18:00:00-05:00",
            "status": "in_transit",
            "location": "Newark, NJ",
            "description": "At local FedEx facility"
          }
        ],
        "from": {
          "name": "J. Smith"
        },
        "to": {
          "name": "M. Johnson"
        },
        "lastUpdated": "2026-02-05T14:30:00-05:00"
      }
    },
    {
      "carrier": "us.usps",
      "trackingNumber": "9400111899223197428490",
      "status": "success",
      "billable": true,
      "cached": false,
      "data": {
        "deliveryStatus": "in_transit",
        "events": [
          {
            "timestamp": "2026-02-05T12:00:00-05:00",
            "status": "in_transit",
            "location": "Chicago, IL",
            "description": "In transit to next facility"
          }
        ],
        "lastUpdated": "2026-02-05T12:00:00-05:00"
      }
    }
  ]
}

Error Response (401)

401 Unauthorized

Authentication failed - API key missing or invalid

Response Body

{
  "success": false,
  "error": {
    "code": "UNAUTHORIZED",
    "message": "Invalid or missing API key"
  }
}

Error Response (429)

429 Too Many Requests

Rate limit exceeded

Response Body

{
  "success": false,
  "error": {
    "code": "RATE_LIMIT_EXCEEDED",
    "message": "Rate limit exceeded. Please try again later."
  }
}