# DHL Tracking API & MCP

URL: https://docs.lastscanned.com/carriers/dhl

Query live **DHL** (`dhl`) package status and scan history across **DHL Express**, **DHL eCommerce**, **DHL Paket DE**, and **Deutsche Post** in a single synchronous `GET` request or MCP tool call. `lastscanned` validates 10-digit Mod-7 DHL Express waybills, `JD`/`JJD` piece IDs, `GM` eCommerce numbers, `0034043…` Paket SSCCs, and UPU S10 `…DE` numbers—returning a normalized `TrackingResult` JSON payload with zero DHL Developer Portal keys.

## Overview & Quick Facts

| Property | Value |
|---|---|
| **Carrier slug (`?carrier=`)** | `dhl` |
| **Brand & service aliases** | DHL Express, DHL eCommerce Solutions, DHL Paket (Parcel DE), Deutsche Post International |
| **Category** | Global express, cross-border e-commerce & European parcel carrier (Detection precedence **#3 of 31**) |
| **Single-package REST endpoint** | `GET https://api.lastscanned.com/v1/track/{trackingNumber}` |
| **Batch REST endpoint** | `GET https://api.lastscanned.com/v1/track?numbers=...` (up to 35 numbers per carrier in a 100-number call) |
| **MCP server (`https://mcp.lastscanned.com`)** | `track_package`, `track_packages`, `list_carriers` (`claude mcp add --transport http lastscanned https://mcp.lastscanned.com`) |
| **Authentication & pricing** | `Authorization: Bearer lsk_live_...` · **1 free keyless lookup/day per IP** · **Free tier** (5 unique numbers/day, $0) · **Pro** ($4.99/mo for 1,000 unique numbers, unlimited re-polls, $0.015/extra) · **HTTP 402 Stripe MPP** ($0.50 for 25-number `lsk_pack_` key or $0.02/number in USDC on Tempo) |

## Why developers use `lastscanned` for DHL instead of direct requests

- **Avoid Akamai HTTP `428` / `403` cryptographic challenges**: Scripted calls from Cloudflare Workers, Vercel, or AWS Lambda to `www.dhl.com/utapi` are blocked with Akamai `428 Precondition Required` / `403` bot challenges, while the official `api-eu.dhl.com/track/shipments` Unified API enforces strict per-app daily rate limits and API key approval. Calling `GET https://api.lastscanned.com/v1/track/:number` works in one `fetch()`.
- **Four DHL divisions unified in one schema**: Whether a customer gives you a 10-digit DHL Express waybill, a `GM…` DHL eCommerce US number, a 20-digit `0034043…` German DHL Paket SSCC, or a Deutsche Post `RR…DE` parcel, `lastscanned` normalizes all four into the same `TrackingResult` shape.

## Tracking Number Formats & Auto-Detection (`matches` vs `mayMatch`)

DHL is evaluated **third (#3)** in `lastscanned`'s two-pass detector (ahead of [FedEx](/carriers/fedex) so 20-digit `0034043…` DHL Paket SSCC barcodes are claimed before FedEx's 20-digit rule):

| Pass | Rule / Regex | Check Digit | Examples |
|---|---|---|---|
| **Pass 1 (`matches`) — Express waybill** | `/^\d{10}$/` (10 digits) | **Yes — `dhlExpress(n)` (Mod-7)** | `1234567892`, `5680684190` |
| **Pass 1 (`matches`) — Express Piece ID** | `/^JJ?D\d{10,20}$/i` (`JD` or `JJD` + 10–20 digits) | — | `JD014600006281920311` |
| **Pass 1 (`matches`) — DHL eCommerce** | `/^GM\d{16,20}$/i` (`GM` + 16–20 digits) | — | `GM2951173829104827` |
| **Pass 1 (`matches`) — DHL Paket SSCC** | `/^0034043\d{13}$/` (20 digits starting `0034043`) | — | `00340434292135100186` |
| **Pass 1 (`matches`) — Deutsche Post S10** | `/^[A-Z]{2}\d{9}DE$/i` (2 letters + 9 digits + `DE`) | **Yes — `s10(n)` (Mod-11)** | `RR123456785DE`, `CY283910485DE` |
| **Pass 2 (`mayMatch`)** | *None* | — | — |

### `dhlExpress` Mod-7 check-digit algorithm

Because 10-digit strings frequently collide with US phone numbers or order IDs, `lastscanned` checks that the first 9 digits modulo 7 equal the 10th digit:

```ts
/** DHL Express 10-digit waybill: the first 9 digits mod 7 give the last. */
export function dhlExpress(n: string): boolean {
  return Number(n.slice(0, 9)) % 7 === Number(n[9]);
}
```

### Disambiguating DHL vs FedEx & USPS (`?carrier=dhl`)

- **12- or 20-digit DHL Paket numbers outside `0034043…`**: Standard 12- or 20-digit German DHL Paket numbers that do not start with `0034043` match FedEx's numeric patterns in Pass 1. Pass `?carrier=dhl` to route them to DHL.
- **DHL eCommerce US labels starting `420` + ZIP + `92/93…`**: These barcodes are inducted into USPS for final delivery and auto-detect as `usps`. Pass `?carrier=dhl` if you specifically want DHL eCommerce's scan view.

## Synchronous `GET` Request & Normalized JSON Response

### `curl`

```bash
curl -s "https://api.lastscanned.com/v1/track/5680684190" \
  -H "Authorization: Bearer $LASTSCANNED_API_KEY"
```

### JavaScript / TypeScript (`fetch`)

```ts
const res = await fetch("https://api.lastscanned.com/v1/track/5680684190", {
  headers: { Authorization: `Bearer ${process.env.LASTSCANNED_API_KEY}` },
});
const pkg = await res.json();
console.log(pkg.status, pkg.service, pkg.deliveredAt);
```

### MCP `track_package` tool call

```json
{
  "tool": "track_package",
  "arguments": {
    "tracking_number": "5680684190"
  }
}
```

### Normalized `TrackingResult` JSON response

```json
{
  "carrier": "dhl",
  "trackingNumber": "5680684190",
  "status": "delivered",
  "statusDescription": "Delivered - Signed for by: M. MILLER",
  "service": "EXPRESS WORLDWIDE",
  "deliveredAt": "2026-10-16T19:27:00Z",
  "deliveredAtLocal": "2026-10-16T14:27:00",
  "signedBy": "M. MILLER",
  "destination": {
    "city": "KANSAS CITY",
    "state": "MO",
    "country": "US"
  },
  "estimatedDelivery": null,
  "estimatedDeliveryAt": null,
  "message": null,
  "events": [
    {
      "timestamp": "2026-10-16T19:27:00Z",
      "localTime": "2026-10-16T14:27:00",
      "status": "delivered",
      "description": "Delivered",
      "location": "KANSAS CITY, MO, USA",
      "code": "OK"
    },
    {
      "timestamp": "2026-10-16T13:45:00Z",
      "localTime": "2026-10-16T08:45:00",
      "status": "out_for_delivery",
      "description": "With delivery courier",
      "location": "KANSAS CITY, MO, USA",
      "code": "WC"
    },
    {
      "timestamp": "2026-10-16T09:12:00Z",
      "localTime": "2026-10-16T04:12:00",
      "status": "in_transit",
      "description": "Arrived at DHL Delivery Facility KANSAS CITY - USA",
      "location": "KANSAS CITY, MO, USA",
      "code": "AF"
    },
    {
      "timestamp": "2026-10-15T21:30:00Z",
      "localTime": "2026-10-15T23:30:00",
      "status": "in_transit",
      "description": "Processed at LEIPZIG - GERMANY",
      "location": "LEIPZIG, GERMANY",
      "code": "PL"
    }
  ]
}
```

> **Raw status & scan codes**: `statusDescription` and `events[].description` combine DHL's `description` and `remark` fields (deduplicating repeated remarks on DHL Paket DE), while `events[].code` preserves DHL's native scan code (such as `"WC"` for With Courier / Out for Delivery, `"OK"` for Delivered, or `"AF"` for Arrived at Facility).

## Carrier-Specific Status & Integration Notes

- **Timestamps across DHL Express vs eCommerce / Paket**: DHL Express includes an explicit UTC offset on every scan (`2026-10-16T14:27:00-05:00`), so both `timestamp` (UTC) and `localTime` are populated. DHL eCommerce and DHL Paket DE report local scan times without a timezone offset, so `localTime` is populated and `timestamp` is `null`.
- **Next steps & exception guidance**: When a shipment requires customs duties or delivery rescheduling, DHL's `nextSteps` instruction is surfaced in the top-level `message` field.

## Related Resources

- **Live browser lookup tool**: [Track a DHL package live on lastscanned.com/lookup/dhl](https://lastscanned.com/lookup/dhl)
- **Carrier hub**: [All 31 supported carriers](/carriers)
- **Regex & check digits**: [Tracking number formats & auto-detection](/formats)
- **AI support workflows**: [WISMO AI agents & synchronous tracking](/wismo)
