Tracking packages by API: FedEx, UPS, DHL, USPS — and the AliExpress/Temu carriers from China
Order tracking is the integration nobody plans for until support tickets pile up. This is the practical map as of mid-2025: the four big carriers' official APIs with working C# (secrets read from environment variables, never hard-coded), the China cross-border carriers that actually move AliExpress and Temu parcels, the last-mile handoff gotcha that confuses everyone, and the aggregator shortcut when you need fifty carriers instead of five. A cheat-sheet table at the end collects every signup URL and public tracking-URL variant.
FedEx: OAuth2, then a single POST
Sign up at developer.fedex.com — test keys are free and issued immediately, production keys after a short review. The API is OAuth2 client-credentials: exchange client id + secret for a bearer token (valid about an hour), then call tracking:
using System.Net.Http.Headers;
using System.Net.Http.Json;
static readonly HttpClient http = new();
// Token endpoint. Secrets come from the environment - never from source code.
using var tokenReq = new HttpRequestMessage(HttpMethod.Post,
"https://apis.fedex.com/oauth/token")
{
Content = new FormUrlEncodedContent(new Dictionary<string, string> {
["grant_type"] = "client_credentials",
["client_id"] = Environment.GetEnvironmentVariable("FEDEX_API_KEY")!,
["client_secret"] = Environment.GetEnvironmentVariable("FEDEX_SECRET_KEY")!,
})
};
using var tok = await http.SendAsync(tokenReq);
var tokenJson = await tok.Content.ReadFromJsonAsync<JsonElement>();
var accessToken = tokenJson.GetProperty("access_token").GetString();
// Tracking: one POST, multiple numbers supported in the payload.
using var trackReq = new HttpRequestMessage(HttpMethod.Post,
"https://apis.fedex.com/track/v1/tracking-numbers")
{
Content = JsonContent.Create(new {
trackingInfo = new object[] {
new { trackingNumberInfo = new { trackingNumber = "774901234567" } }
}
})
};
trackReq.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
using var resp = await http.SendAsync(trackReq);
var feed = await resp.Content.ReadAsStringAsync();
// resp "completeTrackResults[0].trackResults[0].latestStatusDetail.description"
// carries the human status ("Delivered", "In transit", ...).
Public tracking URL (no auth, shareable):
https://www.fedex.com/fedextrack/?trknbr=774901234567. FedEx numbers are
typically 12 digits.
UPS: the new OAuth developer portal
The classic XML Track API is retired territory — sign up at
developer.ups.com (free, instant sandbox) and use the REST API: the same
client-credentials OAuth dance, then a GET per tracking number. UPS numbers are the
familiar 1Z... (18 characters):
// 1. Token: Basic auth header holds base64(clientId:clientSecret).
using var tok = new HttpRequestMessage(HttpMethod.Post,
"https://onlinetools.ups.com/security/v1/oauth/token")
{
Content = new FormUrlEncodedContent(
new Dictionary<string, string> { ["grant_type"] = "client_credentials" })
};
var user = Environment.GetEnvironmentVariable("UPS_CLIENT_ID")!;
var pass = Environment.GetEnvironmentVariable("UPS_CLIENT_SECRET")!;
tok.Headers.Authorization = new AuthenticationHeaderValue("Basic",
Convert.ToBase64String(System.Text.Encoding.ASCII.GetBytes($"{user}:{pass}")));
// ...send, read "access_token"...
// 2. Track: GET /api/track/v1/details/{inquiryNumber}
using var tr = new HttpRequestMessage(HttpMethod.Get,
"https://onlinetools.ups.com/api/track/v1/details/1Z999AA10123456784" +
"?locale=en_US&returnSignature=false");
tr.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
tr.Headers.Add("transId", Guid.NewGuid().ToString("N")); // your correlation id
tr.Headers.Add("transactionSrc", "maplecart-portal"); // who is calling
// Response: trackResponse.shipment[0].package[0].activity[0] = latest scan
// (city, code, description, timestamp) - walk activity[] for full history.
Public URL: https://www.ups.com/track?tracknum=1Z999AA10123456784.
DHL: the friendliest of the four
developer.dhl.com issues an API key (not OAuth) for the Express Tracking API — one header, one GET. This is the fastest big-carrier integration you will ever do. DHL Express waybills are 10 digits:
using var req = new HttpRequestMessage(HttpMethod.Get,
"https://api-eu.dhl.com/track/shipments" +
"?trackingNumber=1234567890&language=en");
req.Headers.Add("DHL-API-Key",
Environment.GetEnvironmentVariable("DHL_API_KEY")!);
// Response "status.status" ("delivered", "in-transit") plus events[] history.
// Regional hosts: api-eu.dhl.com (EU), api-us.dhl.com (US) - pick by account.
Public URL: https://www.dhl.com/en/express/tracking.html?AWB=1234567890
(DHL Parcel Germany has a separate portal and API — different product, check
developer.dhl.com's parcel section).
USPS: in mid-migration
The ancient Web Tools API (a USERID plus XML GET) is being replaced by the
modern OAuth-based APIs at developers.usps.com; 2025 is the transition
year with both alive. New integrations should target the new platform — but the Web Tools
track call is still the one you will find everywhere in the wild:
// Legacy Web Tools (still operational during migration): a GET that returns
// TrackResponse XML. USERID + password are per-account, free from USPS.
var url = "https://secure.shippingapis.com/ShippingAPI.dll?API=TrackV2&XML=" +
Uri.EscapeDataString(
"<TrackRequest USERID=\"ENV-USPS-USERID\">" +
"<TrackID ID=\"9400111899223812345678\"></TrackID></TrackRequest>");
// New platform equivalent: OAuth token then GET on api.usps.com - same shape
// as FedEx/UPS above. 22-digit "9400..." IMpb barcodes are the norm.
Public URL: https://tools.usps.com/go/TrackConfirmAction?tLabels=9400....
The China carriers: who actually moves AliExpress and Temu parcels
The big four are irrelevant for a $6 phone case from Shenzhen. Cross-border parcels ride China-origin consolidators, and 2020s e-commerce exposed the whole world to their naming quirks:
- Cainiao (菜鸟) — Alibaba's logistics network; the default engine
behind AliExpress "standard" shipping. Numbers often start with
LP(Cainiao's own format) or ride UPU-formatRB.../RR...CNcodes. - Yun Express (云途) —
YT-prefixed numbers; huge on AliExpress and Temu. - Yanwen (燕文) —
YE/UPB/LP-ish assortment; strong in EU lanes. - 4PX — another consolidator you will meet on Wish/AliExpress lanes.
- SunYou (速优) and CJ Packet — the cheap-and-slow tier.
None of these have public no-contract APIs as open as FedEx's; the official door is their merchant portal (Yun Express, 4PX and Yanwen issue API tokens to shipping customers with an account), and the public door is a web lookup — most usefully Cainiao's JSON endpoint, which knows about all the consolidators it carries:
// Cainiao global lookup - public JSON, no key, fine for low-volume human
// support tooling; do NOT build a production poller on an undocumented
// endpoint (it changes without notice, and there is no SLA):
GET https://global.cainiao.com/global/detail.json?mailNos=LP00123456789012&lang=en
// Web UI variant: https://global.cainiao.com/detail.htm?mailNoList=LP...
// Yun Express: https://www.yuntrack.com/Track/Detail?id=YT2512345678
// Yanwen: https://track.yanwen.com.cn/?trackNumber=YE2512345678
// 4PX: https://track.4px.com/ (search box; number variants vary)
The last-mile handoff gotcha
Every China-carrier integration meets the same confusion: the tracking history ends at
"arrived at destination country / handed to local carrier" and goes silent for days. The
parcel is not lost — it changed hands and usually changed tracking numbers
(the consolidator's number becomes a USPS 9400... or a local-carrier code for
the final leg). Cainiao's lookup follows the handoff internally; standalone carrier APIs
do not. If you poll only the origin carrier, your users will see a dead status exactly when
the parcel is closest to them.
The pragmatic answer: an aggregator
When the requirement is "any number from any store in one UI", do not integrate twelve APIs — integrate one aggregator that auto-detects the carrier and follows last-mile handoffs. 17TRACK (api.17track.net, free tier, key from the developer console) is the one with the deepest China-carrier coverage, which is why it is the default answer for AliExpress/Temu flows; AfterShip and TrackingMore are the commercial alternatives with richer webhooks:
using var req = new HttpRequestMessage(HttpMethod.Post,
"https://api.17track.net/track/v2.2/register");
req.Headers.Add("17token",
Environment.GetEnvironmentVariable("TRACK17_API_KEY")!);
req.Content = JsonContent.Create(new object[] {
new { number = "YT2512345678", carrier = 0 } // 0 = auto-detect
});
// register once, then /get returns merged events across carriers - including
// the China -> USPS handoff, which is exactly what raw carrier APIs miss.
Public URL: https://t.17track.net/en#nums=YT2512345678.
Cheat sheet: everything in one table
| Carrier | Signup | Auth | Public tracking URL |
|---|---|---|---|
| FedEx | developer.fedex.com | OAuth2 client-credentials | fedex.com/fedextrack/?trknbr={n} |
| UPS | developer.ups.com | OAuth2 client-credentials | ups.com/track?tracknum={n} |
| DHL Express | developer.dhl.com | API key header | dhl.com/en/express/tracking.html?AWB={n} |
| USPS | developers.usps.com | OAuth2 (new) / UserID (legacy) | tools.usps.com/go/TrackConfirmAction?tLabels={n} |
| Cainiao (AliExpress) | merchant portal / undocumented JSON | none for public lookup | global.cainiao.com/detail.htm?mailNoList={n} |
| Yun Express (Temu/AE) | YunTrack merchant account | account token | yuntrack.com/Track/Detail?id={n} |
| Yanwen | Yanwen merchant account | account token | track.yanwen.com.cn/?trackNumber={n} |
| 17TRACK (aggregator) | 17track.net developer console | API key | t.17track.net/en#nums={n} |
Design notes for your tracker service
Three lessons from running one against these APIs: cache aggressively —
packages do not move at 3am, so a 15–60 minute poll cadence per active tracking number is
plenty and keeps you far under every quota; normalize to one event model
(carrier status codes are a zoo — map them once to in-transit / out-for-delivery /
delivered / exception and store the raw payload for forensics); and
key everything off the tracking number, keep secrets in configuration —
every snippet above reads the environment, and nothing here exposes or hard-codes a real
credential. Number-format sniffing (12 digits → FedEx, 1Z → UPS, 10 digits →
DHL, 9400 → USPS, YT/LP → China consolidator →
17TRACK) will carry you further than you expect before you need the paid aggregator
tier.