How this works
| Source | Company | Last run | Components |
|---|---|---|---|
| EasyPost | EasyPost | Collecting | 4 tracked |
| Shippo | Shippo | Collecting | 3 tracked |
| ShipStation | Auctane | Collecting | 5 tracked |
| ShipEngine | Auctane | Collecting | 4 tracked |
Collected every 5 minutes. 304 incidents and 190 maintenance records in the archive; 140 of the incidents affect a tracked carrier's API and 25 are platform-only. Machine-readable health.
What we monitor
Shipzil tracks the API status of USPS, FedEx and UPS as reported by four shipping platforms: EasyPost, Shippo, ShipStation and ShipEngine. Each platform independently monitors carrier API availability for its own customers, and we aggregate those reports into one view.
Four platforms, three companies
ShipStation and ShipEngine are both Auctane products and publish the same incidents, often within minutes of each other. Counting them as two independent opinions would overstate how much corroboration there is, so for the "sources reporting a problem" figure they count once. That leaves 3 independent sources: EasyPost, Shippo, Auctane.
Why cross-reference?
No single source gives the full picture. If FedEx shows degraded on one platform and operational elsewhere, it is more likely an integration problem at that platform. If two independent companies report the same carrier as impaired, it is probably the carrier.
How uptime is calculated
For each carrier we calculate reported uptime separately on each platform. Where the platform publishes component state changes, downtime starts when the affected component leaves operational and ends when it returns. When those transitions are unavailable, the incident's open and close times are the fallback. The headline number is the worst-performing platform, not an average.
Averaging would be misleading in two ways: it mixes integrations that are not comparable (ShipStation and ShipEngine reach USPS through Stamps.com, while EasyPost and Shippo connect directly), and it improves every carrier's number each time another platform is added. The worst platform answers a question you can act on — how bad does this get on the integration you might be using. Per-platform figures are on each carrier page.
What we exclude, and why
- Standing advisories. Incidents left unresolved for more than 48 hours are usually regional or seasonal notices, not API outages — one ran for 63 days. Counting them as continuous downtime would make every number meaningless. They are listed separately.
- Account-surface incidents. Login, dashboard, billing and API-key problems are real outages if you use a platform's interface, but a broken login page does not mean a carrier's API is failing. They appear under platform incidents instead.
- Incidents the source stopped reporting. Statuspage sometimes drops an incident from its feed without ever marking it resolved. Since we never delete records and the source never says "resolved", those would sit here as ongoing forever — one showed as an open FedEx label problem for 72 days after the platform had stopped reporting it. We now detect that and list them separately as no longer reported, ending at the last update the source published rather than a resolution time we cannot vouch for.
- Scheduled maintenance. Expected downtime, listed but not counted.
- Other carriers. DHL, Canada Post, La Poste and the rest are out of scope.
Measuring the window, not the paperwork
An incident's open and close times are not the same as the time something was broken. Platforms keep incidents open after service returns while backlogs clear — one ran seven and a half hours on paper while the affected components were actually down for three and a quarter.
So where a platform publishes component state changes alongside its updates, we measure each window from those: the moment a component left operational to the moment it came back. That is the platform's own record of what broke and for how long, which also means the severity comes from the state it set rather than from an impact field someone filled in afterwards. Carrier pages show how many of each platform's incidents were measured this way versus inferred from the incident's open and close times.
We keep every update timeline we see, because the platforms do not. Their APIs return only the most recent incidents, and the description of an incident is replaced by "resolved" once it closes — so anything we do not capture at the time is gone.
When a platform under-reports an outage
Severity is a field the platforms fill in themselves, and they do not fill it in consistently. One platform recorded a seven-hour total system failure — its own updates said "impacting all operations" and it failed over to a backup data centre — as no impact, leaving every component marked operational throughout. Taken at face value, that outage would have counted as zero downtime.
So where a platform reports no impact on something that affected carriers and reads like a failure, we raise it to minor and mark it with an asterisk. Minor rather than a guess at how bad it was: the duration already carries the weight, and inventing a severity we cannot support would be its own kind of dishonesty. Announcements — holiday deadlines, volume warnings, service changes — are left alone. 19 of 140 incidents are currently marked this way.
This matters for comparing platforms. The share of incidents a platform files as "no impact" ranges from under a tenth to a third depending on the platform, so a platform that documents its problems more candidly would otherwise look worse than one that does not.
Attributing platform-wide failures
When a platform's own API breaks, every carrier on it is affected — but the incident often tags no carrier at all, which would make those outages invisible. Where a platform tags one of its own platform-wide components, or describes a failure of a shipping surface every carrier shares (rating, labels, tracking, core API), we attribute it to all three carriers. Incidents attributed this way are labelled inferred; 46 of 140 currently are.
A note on USPS via Stamps.com
ShipStation and ShipEngine route USPS through Stamps.com, so their status reflects that integration rather than the USPS API directly. EasyPost and Shippo connect to USPS directly.
Measured rate checks
Every 5 minutes Shipzil sends the same domestic US rate request to each platform's production API. The measured time covers the full platform rating request, including whatever carrier work that platform performs. Carrier reachability records how many carriers connected to the monitoring account returned at least one bookable rate.
These checks use one account per platform, one fixed lane, one parcel and a scheduled Cloudflare Worker. They show what happened to that request, not provider-wide performance. The accounts also have different carriers connected, and ShipStation v1 is intentionally checked against USPS only to stay well below its request limit, so latency is not a leaderboard.
The platform's own reported component status and Shipzil's measured rate check are kept separate. A platform can report operational while a carrier fails to return a rate; the disagreement is evidence, not a reason to overwrite either source.
A failed check is the platform's, not ours. Each rate request is given 15 seconds. For scale, the slowest platform measured here is ShipEngine: median 4429ms, 95th percentile 9721ms. The budget is about 1.5× that. A check recorded as failed is one where the platform did not answer inside that window, or answered without a usable rate. Where the fault is ours instead — the collector not running — it is counted separately as a gap and never charged to the platform.
Measured beats reported. Every headline number here is what a real request actually did from an ordinary account. Platforms also publish their own performance figures, and those measure a different operation. Right now Shippo publishes 314ms for its API, while a full multi-carrier rating request through it measures 1925ms here. Neither is dishonest — they are not the same request. But a vendor's own number is not a substitute for what a merchant experiences, and this site does not present it as one.
Coverage is the share of scheduled 5-minute checks for which the collector observed a result. Check success is the share of those observed checks that completed with a bookable rate. Missing schedule slots are unknown — they are never counted as provider failures or successes. Latency percentiles include successful checks only, while failures and missing slots are reported separately.
Getting told, rather than looking. There is an Atom feed of carrier-affecting changes — incidents opened and resolved, and failures measured here that the platform did not report. Slack, Teams and Zapier all consume it directly. The bar is deliberately high: only carrier-affecting incidents, and a measured failure needs two consecutive unanswered checks before it appears. A feed that cries wolf gets muted, and a muted feed is worse than none.
The raw 24-hour samples and summaries are available from the versioned JSON telemetry API. The v1 contract is additive: existing fields will not be renamed or removed within v1.
Two more v1 endpoints back the chart above.
Latency returns a
series per platform for the 1h, 6h and 24h windows, as
[unix_seconds, milliseconds] pairs covering successful
checks only, alongside failedAt — the instants of checks a
platform did not answer. That separation is the point: a break in the line with no failure
marked against it is a check we never ran, not a platform fault.
Rollups returns
daily totals per platform — observed, successful and failed checks with p50 and p95 — for
every day since collection began. Nothing is dropped by age. They are kept because the raw
samples are not: the per-check window is 24 hours, so once a day's checks age out, the
rollup is what still answers for it.
windowSeconds appears twice and means two different things.
At the top level it is the window the API is asked to cover, a fixed 86400. Inside each
platform's summary it is the span actually observed, which is
shorter until history fills. Coverage is computed against the schedule, not against either
figure. mature turns true once the observed span reaches 90%
of the requested window, so it can be true while the observed span is still under 24 hours.
Response shape
{
version: 1,
generatedAt: string, // ISO 8601
cadenceSeconds: 300, // scheduled gap between checks
windowSeconds: 86400, // requested window (see note above)
scope: {
request, fromPostalCode, toPostalCode,
parcel, vantage, note
},
platforms: { // keyed by slug, NOT an array
easypost: {
summary: {
platform, cadenceSeconds,
windowStart, windowEnd, // ISO 8601
windowSeconds, // observed span
mature, // boolean
expectedSamples, observedSamples,
successfulSamples, failedSamples, missingSamples,
coveragePct, // observed / expected
checkSuccessPct, // successful / observed
latestOutcome, // "success" | "failure"
latestAt, lastError?,
latency: { latest, median, p95? },
carriers: { latest, baseline? },
services: { latest }
},
samples: [ // one per observed check, oldest first
{ // on success:
timestamp, // Unix SECONDS, not ms
outcome: "success",
latencyMs, services, carriers, baseline?
},
{ // on failure:
timestamp,
outcome: "failure",
error // short, credential-free reason
} // no latencyMs/services/carriers
]
},
shippo: { ... }, shipstation: { ... }, shipengine: { ... }
}
}
Missing checks are absent from samples entirely rather than
present with a null outcome, so samples.length equals
observedSamples, never
expectedSamples. p95 is
omitted below 20 successful samples. A failed check carries no metrics at all, so its
sample has an error string and no timing fields — a
failure is never recorded as a zero or as a slow success.
Update frequency
Status and rate checks are collected every 5 minutes. Pages rebuild when something changes and at least once a day, and the dashboard refreshes current status, uptime and measured rate summaries from the collector on load. Incident history is kept permanently — once recorded, an incident stays even after the source platform's own history rolls over.
Feature requests & contact
Want a carrier added? Something wrong? sam@shipzil.com