What Is an Apdex Score? (And What a Good One Actually Is)
Apdex compresses thousands of request latencies into one 0-to-1 number. The math, how to pick T, what counts as good, and how TracePath computes it.
Apdex (Application Performance Index) is a score between 0 and 1 that summarises how many of your requests were fast enough. Every request is sorted into one of three buckets against a target time T — satisfied (at or under T), tolerating (between T and 4T), frustrated (slower than that, or failed) — and the score is (satisfied + tolerating / 2) / total. A score of 1.0 means every request landed inside T; by the original specification's rating bands, 0.94 and above is excellent and anything under 0.70 means a visible share of your users are sitting there waiting.
Key takeaways
- Apdex is a volume-weighted measure of user experience. Percentiles tell you how slow the tail is; Apdex tells you how many people are in it.
- The whole score hinges on T, which is a promise about what your users will accept — not a number you derive from your current latency.
- Raising T is the easiest metric fraud in observability. Freeze T when you set it, and record why you chose it.
- Apdex flattens the tail: a 4.1-second request and a 45-second request both count as exactly one frustrated request. Keep P95 and P99 next to it.
- In TracePath, Apdex is computed per route template, errors count as frustrated, and the Apdex Drop alert fires when the score falls below a threshold you set.
The formula, in full
Apdex(T) = (satisfied + tolerating / 2) / total
satisfied response time <= T
tolerating T < response time <= 4T
frustrated response time > 4T, or the request failedThe half-weight on the tolerating bucket is the only opinion baked into the maths: a request that was merely annoying counts as half a happy user. It is a convention from the Apdex specification published by the Apdex Alliance in the mid-2000s, not something anybody measured. It is also why the score degrades gently — you do not get a cliff edge at T, you get a slope between T and 4T.
A worked example
Take 200 requests to POST /api/checkout over fifteen minutes, with T set to 500 ms:
| Bucket | Condition | Count | Weight | Contribution |
|---|---|---|---|---|
| Satisfied | ≤ 500 ms | 150 | 1.0 | 150 |
| Tolerating | 500 ms – 2 s | 34 | 0.5 | 17 |
| Frustrated | > 2 s | 12 | 0 | 0 |
| Frustrated | 5xx response | 4 | 0 | 0 |
| Total | 200 | 167 |
167 / 200 = 0.835 — "fair", and close enough to the bottom of that band that somebody should look at it.
Now change nothing about the application and move T to one second. Twenty-two of the tolerating requests were already under a second, so they become satisfied. The frustrated boundary moves out to four seconds, so five of the slow twelve become tolerating:
(172 + 17 / 2) / 200 = 0.90
Same users, same waiting, a score that moved from "fair" to the edge of "excellent". That is the single most important thing to understand about Apdex before you put it on a dashboard.
What counts as a good Apdex score
The rating bands from the specification are still the common convention:
| Score | Rating | What it feels like |
|---|---|---|
| 0.94 – 1.00 | Excellent | Effectively nobody waits |
| 0.85 – 0.93 | Good | Occasional slow request, no complaints |
| 0.70 – 0.84 | Fair | A steady trickle of people noticing |
| 0.50 – 0.69 | Poor | Support tickets |
| 0.00 – 0.49 | Unacceptable | The endpoint is functionally broken |
Two caveats. First, a score is only meaningful next to the T it was computed with, so "we run at 0.95" says nothing on its own. Second, an Apdex number is most useful compared against itself over time — the shape of the line after a deploy tells you more than the absolute value.
How to choose T
T is the answer to "how long is this allowed to take before a real person notices?" Derive it from the interaction, not from a histogram:
- Interactive requests a user is watching — 300 ms to 500 ms. This covers most API calls behind a UI.
- Page loads and first meaningful render — around 1 second, though at this point you are usually better served by Core Web Vitals.
- Deliberately heavy work — report generation, exports, bulk imports. Seconds, sometimes tens of seconds, and it should be an explicit, written-down number.
- Health checks and internal probes — 50 ms to 100 ms, if you score them at all.
Three rules that keep the number honest:
- Never derive T from your own percentiles. Setting T to your current P90 guarantees an Apdex around 0.9 forever, regardless of what you ship.
- Freeze it. Changing T retroactively changes history. If you must change it, mark the date on the chart and say so.
- Write down the reason. "500 ms because the search box feels laggy above that" survives a team change; "500 ms" alone does not.
Apdex versus percentiles
Percentiles answer "how slow is the slow tail" but say nothing about how much traffic sits in it. A P95 of 1.2 s reads badly even when 99.9% of requests are fast and the 95th percentile lands on a cron-triggered endpoint nobody is waiting for. Apdex weighs volume, which is exactly what you want when deciding what to fix first.
The inverse failure is just as real. Apdex cannot tell you the difference between a tail that tops out at 4.1 seconds and one that tops out at 45 seconds — both are frustrated requests, both score zero. Once the score tells you an endpoint is bad, the percentiles and the trace waterfall tell you how bad and why.
Use them as a pair: Apdex to rank what to work on, percentiles and traces to work on it.
Where Apdex lies to you
Worth knowing before you make it a quarterly target:
- It is gameable, and the gaming is invisible. See the worked example. A dashboard showing Apdex without T is not showing you anything.
- It compresses the tail to nothing. Catastrophic outliers and mild ones score identically.
- It is noisy at low volume. On an endpoint with 12 requests an hour, two slow ones swing the score by 0.08. Always set a minimum sample size before you alert on it.
- It treats every request as one user. A page that makes 40 API calls generates 40 samples; a background job generates one. The score drifts toward whatever is chattiest.
- Failures are all-or-nothing. A request that 500s counts as frustrated no matter how fast it failed — which is usually right, but it means an error spike and a latency spike look the same in the score.
How TracePath computes Apdex
TracePath derives endpoint performance from server spans arriving over OTLP, grouped by HTTP method plus route template (GET /api/users/:id), so one slow reporting route never drags down the endpoints next to it. A few specifics worth knowing:
The bands are fixed, not a per-project setting. On the endpoints list and in alerting, a request at or under 750 ms is satisfied, up to 1500 ms is tolerating, and anything slower — or any 5xx — is frustrated. That is a 750 ms target with the tolerating band ending at 2T rather than the specification's 4T, which makes the score stricter than a textbook Apdex at the same T. The per-endpoint drill-down reports its Apdex tile against a tighter anchor still: 500 ms satisfied, up to 2 s tolerating. If the two numbers differ for the same endpoint, that is why.
4xx responses are not frustrated. Only 5xx counts as a failure, so a fast 404 or a rejected form submission scores as satisfied. That is deliberate — a validation error is a working application.
Streaming endpoints are excluded. SSE, WebSocket and long-poll routes are flagged at ingest and dropped from latency-based scoring entirely. A 30-second connection lifetime is not a 30-second wait, and scoring it as one would bury every real problem underneath it.
The tuning knob is per endpoint, not per project. On any endpoint's page you can set an expected-slow offset in milliseconds, with a required note explaining why. The offset shifts that endpoint's satisfied, tolerating and frustrated boundaries — and the P99 component of its impact score — so /api/export can be judged on its own terms without softening /api/checkout. Raw P50/P95/P99 are never offset-adjusted, so the latency you read is still the latency your users got.
The endpoints list ranks by Impact, not by Apdex. Impact is the worst of five components: an inverted Apdex, server error rate, P99 latency, client error rate, and a volume-weighted error floor. Taking the max rather than a blend means one badly degraded signal surfaces an endpoint immediately instead of being averaged away by four healthy ones — and it is why an endpoint with a perfectly respectable Apdex can still sit at the top of the list. More on how the ranking works.
An alert recipe that will not wake you up for nothing
TracePath's Apdex Drop rule evaluates the score over a lookback window and fires when it falls below your threshold. The defaults are a threshold of 0.85, a 15-minute window, and a 50-request minimum; a score under 0.50 is escalated as critical rather than a warning.
A two-rule setup that works well in practice:
| Warning rule | Paging rule | |
|---|---|---|
| Threshold | 0.90 | 0.75 |
| Lookback | 15 min | 15 min |
| Min requests | 100 | 100 |
| Destination | Team channel | On-call escalation |
The minimum request count is doing most of the work here. Set it high enough that a quiet hour cannot trip the rule on four slow requests — as a rough starting point, use the traffic you would expect in roughly a third of the lookback window. Pair it with a monitor on the same route so you can tell "slow" from "gone"; synthetic monitors catch the second case, and the alerting docs cover every rule type and its defaults.
Getting endpoint data in
Apdex needs server spans, which means standard OpenTelemetry instrumentation and no proprietary agent. Point your exporter at the ingest host with your project token:
OTEL_EXPORTER_OTLP_ENDPOINT=https://ingest.tracepath.dev/api/otel
OTEL_EXPORTER_OTLP_HEADERS=Authorization=Bearer <project_token>
OTEL_SERVICE_NAME=checkout-apiEndpoints, percentiles, impact ranking and Apdex all come from that one stream — there is nothing extra to enable. The OpenTelemetry setup guide has per-language instructions, and the free plan includes 5 GB of ingest a month with 7-day retention. Full limits are on the pricing page.
FAQ
Is a lower or higher Apdex better? Higher. The score runs from 0 to 1, where 1 means every request finished inside your target time T and nothing failed.
What is a good Apdex score? 0.94 and above is excellent, 0.85 to 0.93 is good, and below 0.70 means a meaningful share of users are waiting. The rating only means something alongside the T it was computed with — a 0.95 at T = 5 s is a worse application than a 0.85 at T = 300 ms.
How is Apdex different from an SLO? An SLO is a target you commit to, usually expressed as a percentage of requests inside a latency threshold over a long window, with an error budget attached. Apdex is a measurement, and its half-weighted middle bucket makes it a softer one. Apdex is good for ranking and dashboards; a straight "percentage of requests under 300 ms" is easier to reason about as a contract.
Does Apdex include errors? In TracePath, 5xx responses count as frustrated regardless of how fast they returned. 4xx responses do not — a validation error or a 404 is scored on its latency like any other request.
Can I set my own Apdex threshold in TracePath? The satisfied and tolerating bands are fixed, but you can set a per-endpoint expected-slow offset that shifts those boundaries for a single route, with a note explaining why. The alert threshold itself — the score at which an Apdex Drop rule fires — is fully configurable per rule.
Why does my endpoint show a good Apdex but a bad impact score? Impact takes the worst of five signals, only one of which is Apdex. An endpoint that is consistently fast but returns 12% server errors, or whose P99 sits at nine seconds, will rank badly on impact while its Apdex still looks healthy.