# Comparing lastscanned vs. direct carrier calls, AfterShip, EasyPost, Ship24 & 17TRACK

URL: https://docs.lastscanned.com/comparison

When building package tracking into a SaaS product, customer support workflow, order-status portal, or AI agent, engineering teams typically evaluate two options:

1. **Calling carrier websites or official carrier APIs directly** from their own backend (Cloudflare Workers, Vercel Functions, AWS Lambda, Fly.io).
2. **Buying a multi-carrier tracking aggregator** (AfterShip, EasyPost, Ship24, or 17TRACK).

This page breaks down the concrete technical obstacles of calling 31 carriers directly from serverless functions, followed by a head-to-head architecture and pricing comparison against legacy tracking aggregators.

---

## 1. Why calling carrier endpoints directly from serverless functions fails (`403`/`428` WAFs & portal gates)

If you try to `fetch()` carrier tracking endpoints directly from a serverless or edge runtime (Cloudflare Workers, Vercel Functions, AWS Lambda, Fly.io, or a Node.js container on a cloud provider), requests fail immediately across both undocumented web endpoints and official carrier developer portals:

| Carrier | What happens when your serverless function calls the carrier directly | How `GET /v1/track/{number}` solves it |
|---|---|---|
| **[UPS](/carriers/ups)** (`1Z…`) | **Undocumented web API (`webapis.ups.com/track/api/Track/GetStatus`)**: Protected by Akamai Bot Manager (`403 Forbidden` on datacenter IPs and Node/Undici TLS fingerprints) and requires a paired `GetCSRFToken` handshake (`X-XSRF-TOKEN-ST` header + session cookies).<br />**Official UPS Developer Portal**: Requires OAuth 2.0 client credentials tied to a 6-character UPS shipper account number. | One stateless `GET` request with `Authorization: Bearer lsk_live_...` returns normalized UTC + local timestamps, `signedBy`, `service`, and scan events—no UPS account required. |
| **[FedEx](/carriers/fedex)** (12/15/20/22 digits) | **Undocumented web API & `api.fedex.com/track/v2/shipments`**: Akamai Bot Manager sensor checks reject cloud/datacenter requests with `403`, while the official FedEx Developer Portal requires OAuth 2.0 token refresh loops, project validation tests, and production key approval. | Auto-detects 12-, 15-, 20-, and `96…` 22-digit FedEx barcodes in Pass 1 and returns the full scan history in one synchronous JSON response. |
| **[USPS](/carriers/usps)** (`92…`–`95…`, `420…`, `…US`) | **Public tracker (`tools.usps.com`)**: Akamai Bot Manager blocks serverless datacenter IPs with `403` or blank challenge pages.<br />**Official USPS v3 API**: Requires OAuth 2.0 + Customer Onboarding Portal (COPRA) enrollment (Mailer ID / CRID), and still returns `"Pre-Shipment: Picked Up by Shipping Partner, USPS Awaiting Item"` (`"USPS Currently Awaiting Package"`) for 3–7 days on `92…` consolidator labels. | Tracks all `92…`–`95…` and `…US` S10 numbers with zero USPS enrollment—and lets you pass [`?carrier=osm`](/carriers/osm) or [`?carrier=pitneybowes`](/carriers/pitneybowes) on `92…` consolidator barcodes to retrieve live middle-mile sort scans before USPS acceptance. |
| **[DHL](/carriers/dhl)** (10-digit Express, `GM…`, `JD…`, `0034043…`) | **Fragmented portals & rate limits**: DHL splits tracking across `express`, `ecommerce`, and `parcel-de` divisions, requires a DHL Developer Portal API key (`DHL-API-Key`) with strict default daily quotas, and returns deeply nested division-specific schemas. | Validates 10-digit Mod-7 (`dhlExpress`) and S10 `…DE` check digits, routes across Express, eCommerce, and Paket automatically, and normalizes every scan into one schema. |
| **[OnTrac / LaserShip](/carriers/ontrac)** (`C…`/`D…`, `1LS…`, `LX…`) | **Datacenter IP blocks (`t.lasership.com/OrderEvents`)**: Blocks automated datacenter requests and requires separate legacy LaserShip vs. OnTrac format handling. | Auto-detects both OnTrac (`C`/`D` + 14 digits) and LaserShip (`1LS…`, `LX…`) formats and returns normalized UTC and local scan timestamps. |
| **[Veho](/carriers/veho)** (`VH…`) | **Firebase App Check gate (`graph.shipveho.com/graphql`)**: Rejects requests with `401`/`403` unless accompanied by a live `x-firebase-appcheck` token minted via reCAPTCHA Enterprise (`6Lf5dJ4pAAAAAM5213981b76-48e0-a6a8-575b0e5d1`). Official Veho Packages API (`api.shipveho.com`) restricts tracking details unless your merchant API key created the order. | Tracks any `VH…` parcel (and shipper references via `?carrier=veho`) with one API key—no Veho shipper contract or App Check token required. |
| **[Canada Post](/carriers/canadapost), [Purolator](/carriers/purolator) & [Intelcom / Dragonfly](/carriers/intelcom)** | **WAF challenges & shipper-only portals**: Purolator sits behind AWS WAF challenges and SOAP/REST contract keys; Canada Post requires Developer Program credentials; Intelcom / Dragonfly (`INTLCM…`) has no self-serve public developer portal. | Validates 16-digit GS1 Mod-10 (`gs1`) and S10 `…CA` numbers, supports Purolator `3L+9D` and `?carrier=purolator` 12-digit PINs, and auto-detects `INTLCM…` Dragonfly parcels across Canada and Australia. |
| **US & AU Regional Couriers** ([UniUni](/carriers/uniuni), [SpeedX](/carriers/speedx), [GOFO](/carriers/gofo), [Better Trucks](/carriers/bettertrucks), [AxleHire / Jitsu](/carriers/axlehire), [CDL](/carriers/cdl), [Spee-Dee](/carriers/speedee), [Cirro](/carriers/cirro), [TForce](/carriers/tforce), [GLS US](/carriers/glsus), [Sendle](/carriers/sendle)) | **Proprietary payloads & proof-of-work gates**: SpeedX encodes tracking payloads in base64 proto-JSON; Sendle's 2026 platform requires solving a cryptographic SHA-256 proof-of-work challenge (`salt:nonce` zero-bit check) on every lookup; TForce only renders year-less HTML tables; UniUni and GOFO offer no public recipient API keys. | Decodes every payload, solves Sendle's proof-of-work challenge, resolves regional timezones and check digits, and returns a single `TrackingResult` JSON shape. |
| **Cross-Border Consolidators** ([Yanwen](/carriers/yanwen), [4PX](/carriers/4px), [Cainiao](/carriers/cainiao), [YunExpress](/carriers/yunexpress), [CNE Express](/carriers/cne), [SunYou](/carriers/sunyou), [UBI Smart Parcel](/carriers/ubi), [Asendia](/carriers/asendia)) | **Signature headers, IP bans & missing handoff numbers**: CNE requires custom MD5 request signatures and blocks shared IPs (`ReturnValue: -5555`); SunYou wraps responses in JSONP with mislabeled `+08:00` destination offsets; UBI mixes duplicate partner scans; merchant APIs require shipper accounts that created the waybill. | Auto-detects all 8 cross-border formats, cleans up timezone offsets, and extracts the **destination carrier and final-mile tracking number** into `message` (e.g. `"Handed to USPS as 9261..."` or `"Handed to Australia Post as 33X..."`). |

### Side-by-side code comparison: Direct carrier integrations vs. `lastscanned`

Instead of maintaining 31 regex matchers, 4 check-digit validators, per-carrier OAuth token caches, CSRF handshakes, and 31 response parsers in your serverless function—only to hit `403 Forbidden` from a cloud IP—your backend makes a single `fetch()` call:

```ts
// One synchronous GET call from any Cloudflare Worker, Vercel Function, or AWS Lambda
const res = await fetch(
  `https://api.lastscanned.com/v1/track/${encodeURIComponent(trackingNumber)}`,
  {
    headers: {
      Authorization: `Bearer ${process.env.LASTSCANNED_API_KEY}`,
    },
  },
);

const pkg = await res.json();
// pkg.carrier           -> "ups" | "usps" | "fedex" | "speedx" | "uniuni" | ... (31 carriers)
// pkg.status            -> "label_created" | "in_transit" | "out_for_delivery" | "delivered" | "exception" | "returned" | "unknown"
// pkg.estimatedDelivery -> "2026-10-28" (or null)
// pkg.message           -> "Handed to UniUni as UUS5BM1269395824723" (on cross-border handoffs)
// pkg.events            -> Chronological scan array with UTC `timestamp` and local `localTime`
```

---

## 2. Head-to-head comparison: `lastscanned` vs. AfterShip, EasyPost, Ship24 & 17TRACK

Most multi-carrier tracking APIs were designed around **asynchronous webhook queues** for enterprise post-purchase email suites (`POST` a tracking number to register a tracker, store a tracker ID in your database, and wait for an async webhook to fire).

`lastscanned` is built for **synchronous, stateless `GET` lookups** and **AI agent tool calls (MCP + HTTP 402 Stripe MPP)** with predictable unique-number pricing.

| Capability / Metric | `lastscanned` | AfterShip | EasyPost | Ship24 | 17TRACK |
|---|---|---|---|---|---|
| **Primary request model** | **Synchronous `GET /v1/track/{number}`** (live JSON in one HTTP call; zero registration) | Async `POST` tracker registration + webhook queue | `POST` tracker creation tied to carrier/label accounts | Async `POST` tracker webhook (sync per-call locked to `$99/mo` tier) | Async `POST` register number first + webhook / subsequent poll |
| **Database / webhook state required** | **None (stateless)** | Must store tracker IDs & run webhook endpoints | Must store `trk_...` IDs & webhook handlers | Must register trackers (on standard plan) | Must register numbers against prepaid quota |
| **Entry API tier** | **$0/mo Free** (5 numbers/day + **1 keyless lookup/day per IP**) | **$59/mo Premium** (API access excluded on Essentials) | Pay-as-you-go (`$0.01–$0.03`/tracker) or `$20/mo` BYOCA | **$0 Free** (10/mo), then **$39/mo** (per-shipment) or **$99/mo** (per-call) | 200 one-time free, then **$119/yr** prepaid (`5,000/yr`) |
| **Cost for 1,000 unique packages / month** | **$4.99 / month** (Pro) | **$59.00 / month** (Premium) | **$20.00 / month** (`$0.02`/tracker) | **$39.00 / month** (or `$99/mo` per-call) | **$19.83 / month eq.** (`$119/yr` annual lock-in) |
| **Overage rate above 1,000 packages** | **$0.015** per extra unique tracking number | **$0.08** per shipment | **$0.01–$0.03** per tracker | **$0.039–$0.045** per shipment | Prepaid annual tier upgrade required |
| **Cost of re-polling the same package 20x in a month** | **$0 (unlimited re-polls included on Pro)** | Included after async registration | Included on registered tracker | Included on per-shipment tier; **20x charges on per-call tier** | Included after registration |
| **Failed / carrier-error lookup billing** | **Always $0** (only `200` or `404` count) | Quota consumed on registration | Charged on tracker creation | Quota consumed on tracker creation | Quota consumed on registration |
| **Carrier credentials required** | **Zero** across all 31 carriers | Zero (for standard public carriers) | Often requires BYO carrier credentials for full data | Zero | Zero |
| **US, CA & AU regional couriers (`uniuni`, `speedx`, `gofo`, `veho`, `intelcom`, `bettertrucks`, `cdl`, `axlehire`, `sendle`)** | **All 13 built-in** | Partial (enterprise post-purchase focus) | Thin on China/Temu/Shein US regional last-mile | Partial aggregator coverage | Strong on China export, mixed on US regional |
| **`92…` `"USPS Currently Awaiting Package"` middle-mile consolidator scans** | **Built-in via `?carrier=osm` & `?carrier=pitneybowes`** | Requires separate courier slug / tracker registration | Returns USPS pre-shipment status until USPS scan | Depends on courier auto-detection | Depends on courier auto-detection |
| **Cross-border last-mile handoff number extraction** | **Top-level `message` (`"Handed to <carrier> as <number>"`) across all 8 cross-border carriers** | Nested in courier-specific fields | Limited cross-border consolidator support | Available in aggregator payload | Available after async registration |
| **Native remote MCP server** | **`https://mcp.lastscanned.com`** (Streamable HTTP + `/lsk_live_...` URL-path auth) | None native | None native | Docs example only (requires paid subscription quota) | Community stdio wrappers only |
| **Keyless AI agent micropayments (`HTTP 402`)** | **Built-in Stripe MPP** (`$0.50` / 25-number card pack or `$0.02` USDC on Tempo) | None | None | None | None |

---

## 3. Deep dive on the four architectural differences

### A. Synchronous `GET` vs. asynchronous `POST` + webhook registration

In interactive workflows—such as **"Where Is My Order?" (WISMO) AI support agents**, live chat widgets, helpdesk macros, and on-demand order-lookup pages—a customer asks about a package *right now*.

With legacy webhook platforms (AfterShip, 17TRACK, Ship24's standard plan), looking up an unregistered tracking number requires a multi-step dance:
1. `POST` the tracking number to create a tracker in their database.
2. Receive an initial `PENDING` status with an empty `events: []` array while their background queue schedules a job.
3. Wait seconds or minutes for an inbound webhook before your application can answer the customer's question.

With `lastscanned`, there is no tracker registration step and no webhook receiver to host. Call `GET https://api.lastscanned.com/v1/track/{trackingNumber}` (or pass up to 100 numbers to `GET /v1/track?numbers=...`) and receive the live normalized status and full scan timeline in the same HTTP response.

### B. Unique-number monthly billing (`$4.99/mo` Pro) vs. per-shipment or per-call fees

Tracking APIs typically force an unpleasant billing trade-off:
- **Per-shipment enterprise plans** (AfterShip at `$59/mo`, Ship24 at `$39/mo`) charge steep monthly minimums bundled with branded tracking pages, email marketing flows, and returns portals you may not need.
- **Per-call APIs** charge every single time you poll a tracking number—so polling an active shipment 15 times over 5 days costs 15 API credits.

`lastscanned` combines synchronous `GET` simplicity with **unique-number monthly billing**:
- **Free tier (`$0/mo`)**: 5 unique tracking numbers per day (plus **1 unauthenticated keyless lookup per day per IP** so anyone can test `GET /v1/track/:number` immediately).
- **Pro tier (`$4.99/mo`)**: Includes **1,000 unique tracking numbers per monthly billing period**, then **$0.015** per additional unique tracking number.
- **Unlimited re-polls**: Once your account looks up a tracking number during a billing period, every subsequent `GET /v1/track/:number` or MCP `track_package` call for that same tracking number is **free for the rest of the billing period**.
- **Failure protection**: Only lookups where the carrier returns a tracking result (`200`) or confirms `404 not_found` count toward your daily allowance, Pro unique-number count, or MPP credit pack. Upstream timeouts or `502 carrier_error` responses never cost a cent.

### C. Native remote MCP server & keyless `HTTP 402` Stripe MPP for AI agents

`lastscanned` is designed from the ground up for both human developers and autonomous AI agents:

- **Zero-install remote MCP server (`https://mcp.lastscanned.com`)**: Connect Claude Code, Cursor, Windsurf, or custom agents over Streamable HTTP in one command—no local wrapper scripts required:
  ```bash
  claude mcp add --transport http lastscanned https://mcp.lastscanned.com \
    --header "Authorization: Bearer lsk_live_YOUR_API_KEY"
  ```
  For MCP clients that cannot send custom HTTP headers (such as ChatGPT Developer Mode connectors), pass the API key directly in the URL path: `https://mcp.lastscanned.com/lsk_live_YOUR_API_KEY`.
- **Keyless pay-per-request via Stripe Machine Payments Protocol (`HTTP 402`)**: When an autonomous agent calls `GET /v1/track/{trackingNumber}` without an API key after using the IP's 1 free daily lookup (or calls the batch endpoint), `lastscanned` returns an `HTTP 402 Payment Required` response with a standard `WWW-Authenticate: Payment` challenge ([mpp.dev](https://mpp.dev), discovery spec at [`https://api.lastscanned.com/openapi.json`](https://api.lastscanned.com/openapi.json)):
  - **Card rail (`stripe/charge`)**: Pay **$0.50** via a Stripe Shared Payment Token for a **25-tracking-number credit pack**. Unused credits are returned in the `x-lastscanned-key` response header (`lsk_pack_<32hex>`) and `credits.key` JSON field so the agent can reuse them as a standard `Bearer lsk_pack_...` token on future requests or MCP calls.
  - **Stablecoin rail (`tempo/charge`)**: Pay **$0.02** per tracking number in USDC on Tempo.

### D. Solving the `"USPS Currently Awaiting Package"` black hole & cross-border handoffs across 31 carriers

Two of the largest sources of `"Where Is My Order?"` support tickets happen in the handoff gap between carriers:

1. **Domestic `92…` USPS consolidator parcels ([OSM Worldwide](/carriers/osm) & [Pitney Bowes](/carriers/pitneybowes))**:
   Millions of US e-commerce parcels ship via OSM Worldwide or Pitney Bowes bearing a 22-digit `92…` (or 30-digit `420` + ZIP + `92…`) USPS IMpb barcode. While the consolidator moves the parcel across its regional hubs for 3–7 days, querying USPS only returns `"Pre-Shipment: Picked Up by Shipping Partner, USPS Awaiting Item"` (`"USPS Currently Awaiting Package"`). With `lastscanned`, passing **`?carrier=osm`** or **`?carrier=pitneybowes`** on that exact same `92…` number queries the consolidator directly and returns every intermediate hub arrival/departure scan plus the consolidator's `estimatedDelivery` date.
2. **Cross-border last-mile handoff extraction ([Yanwen](/carriers/yanwen), [4PX](/carriers/4px), [Cainiao](/carriers/cainiao), [YunExpress](/carriers/yunexpress), [CNE Express](/carriers/cne), [SunYou](/carriers/sunyou), [UBI Smart Parcel](/carriers/ubi), [Asendia](/carriers/asendia))**:
   When an international parcel clears customs and is handed to a local courier ([USPS](/carriers/usps), [UniUni](/carriers/uniuni), [SpeedX](/carriers/speedx), [GOFO](/carriers/gofo), [OnTrac](/carriers/ontrac), [Canada Post](/carriers/canadapost), [Intelcom / Dragonfly](/carriers/intelcom), Australia Post, or Royal Mail), `lastscanned` extracts both the destination carrier and the secondary tracking number into the top-level `message` field (for example, `"Handed to UniUni as UUS5BM1269395824723"`), allowing your app or AI agent to chain a second lookup on the local doorstep barcode automatically.

---

## Related guides & references

- **[Supported carriers (all 31)](/carriers)**: Per-carrier API & MCP guides, format tables, and live lookup links.
- **[Tracking number formats & auto-detection](/formats)**: Exact `matches` and `mayMatch` regular expressions, check-digit algorithms (`ups1Z`, `s10`, `dhlExpress`, `gs1`), and collision rules.
- **[WISMO AI agents & synchronous tracking](/wismo)**: Status lifecycle handling, cross-border handoff resolution, and copy-paste TypeScript / MCP patterns.
- **[MCP server (`https://mcp.lastscanned.com`)](/mcp)**: Streamable HTTP setup for Claude Code, Cursor, Windsurf, and ChatGPT.
- **[Plans, quotas & Stripe MPP (`HTTP 402`)](/plans)**: Detailed breakdown of Free, Pro (`$4.99/mo`), and machine-payable requests.
- **[Live web package lookup (`lastscanned.com/lookup`)](https://lastscanned.com/lookup)**: Test any tracking number across all 31 carriers in your browser.
