Every figure on StreamerVitals is a measurement, not a report from inside Twitch. This page documents exactly what we measure, how often, what we do when a measurement is missing, and the arithmetic behind every derived number the site publishes.
The thresholds quoted below are read from the same modules the pipeline runs on, so this page changes when the system changes. Twitch policy compliance was last reviewed on , and is re-reviewed at least every 90 days.
Data observed via the official Twitch API. We read the official Twitch Helix API with an application access token and nothing else. We do not scrape twitch.tv, we do not parse the site’s HTML, we do not use undocumented endpoints, and we do not consume anyone else’s tracker as a data source.
Four endpoints do essentially all of the work:
GET /helix/streams — the live directory, walked from the largest broadcast downwards. Every entry it returns already contains a viewer count, which makes each pass both a discovery crawl and a round of measurements.GET /helix/streams?user_id=… — targeted polling of channels we already track but that sit below the depth the directory walk reaches.GET /helix/users and GET /helix/games — display names, avatars, channel descriptions, category names and box art.GET /helix/channels/followers — the public follower total only. We never request, receive or store the identity of an individual follower.What this means in practice is that we are an outside observer with a stopwatch. Twitch’s own creator dashboard sees unique viewers, chat participation, subscriptions, revenue and traffic sources. We see none of that, and we never estimate it. Everything published here is derived from public concurrent-viewer counts sampled at recorded instants.
Sampling cadence is the single biggest driver of accuracy, so it is allocated deliberately rather than uniformly.
Two ingestion loops run continuously. A discovery pass walks the live directory every 5m, up to 15 pages of 100 streams — roughly the top 1,500 concurrent broadcasts. Every stream in that payload yields a free observation, so the largest channels are measured frequently at almost no quota cost. A second loop runs every minute and polls tracked channels that are live but sat below the directory depth, in priority order.
Priority is recomputed from audience size and from real demand on this site, so the channels people actually read about are the ones measured most finely:
priorityScore = round(log₁₀(1 + averageViewers30d) × 25)
+ round(log₁₀(1 + profileViews30d) × 15)Logarithms on both terms are the point: a 100,000-viewer channel needs finer sampling than a 100-viewer channel, but not a thousand times finer. The score then selects a polling interval:
| Tier | Priority score | Sampled every | Typically |
|---|---|---|---|
| High | 60 and above | 3m | Large audiences, or channels our own visitors look at often. |
| Normal | 30 to 59 | 5m | The bulk of actively tracked channels. |
| Low | 10 to 29 | 15m | Small channels, sampled coarsely so their numbers carry wider error bars. |
| Dormant | below 10 | 15m | Rarely live and rarely visited. Picked up by discovery when they next appear in the directory. |
Each scheduled poll carries ±10% jitter. Without it, every channel discovered in the same cycle would come due in the same second forever, which is a thundering herd aimed at both our database and Twitch’s rate limits.
When a channel we asked about is no longer returned by Twitch, the broadcast has ended somewhere between its last observation and that check. We record the midpoint of that interval, because it is the unbiased estimate — using the last sample would understate every stream by up to one sampling interval, and using the moment we noticed would overstate it by the same amount.
If nobody sees a stream for 25 minutes — a worker outage, a rate-limit storm — a reconciler closes it at its last observation instead. That is a weaker claim, so it is recorded as such and permanently reduces the broadcast’s data confidence. Follower totals are collected separately, on a 6-hour cadence, because they change slowly and burning quota on them would cost viewer-sample accuracy.
Time-weighted, computed in two stages, and never a mean of means.
Sample spacing is never perfectly regular. The scheduler shifts under load, requests fail, Twitch rate-limits us. A plain mean of sample values would silently over-weight whichever part of a broadcast happened to be sampled most densely — usually the beginning. So we integrate over time instead, using the trapezoidal rule between consecutive observations:
viewerSeconds = Σ ((vᵢ + vᵢ₊₁) / 2) × (tᵢ₊₁ − tᵢ)
observedSeconds = Σ (tᵢ₊₁ − tᵢ)
for every consecutive pair where
(tᵢ₊₁ − tᵢ) ≤ 15 minutes
averageViewers = viewerSeconds / observedSecondsThe gap rule is the important half. Any interval longer than 15m between consecutive samples is excluded from both the numerator and the denominator rather than interpolated across. Drawing a straight line between two points two hours apart would invent viewership we never measured. Those excluded intervals are counted against sampling coverage, surfaced on the stream page as sampling gaps, and shown as breaks in the chart.
Nothing is invented at the edges either. Time before the first sample and after the last is unobserved. Each edge is credited up to one sampling interval — a sample taken 90 seconds into a broadcast on a five-minute cadence is fairly taken as representative of that opening minute and a half — but no viewer value is extrapolated into it.
Across a window of days, average viewers is defined as total estimated hours watched divided by total hours streamed:
averageViewers = Σ hoursWatched / Σ hoursStreamedIt is deliberately not the mean of the daily averages, and the difference is not academic. Consider a channel that streams twelve hours to 100 viewers on Monday and one hour to 1,000 viewers on Tuesday. The mean of the two daily averages is 550. Our figure is (1,200 + 1,000) ÷ 13 ≈ 169.
169 is the honest answer to “how many people were watching this channel at a typical moment it was live?” 550 is an artefact of weighting a one-hour day exactly as heavily as a twelve-hour one. The same rule applies at every level: a channel’s 30-day average, a category’s average, and a ranking’s metric are all totals divided by totals.
Broadcasts shorter than 5m are excluded from most aggregates. They are overwhelmingly test broadcasts and accidental starts, and including them drags a channel’s averages toward noise.
Hours watched is an estimate, and the site labels it that way everywhere it appears. It is the area under the concurrency curve:
estimatedHoursWatched = averageConcurrentViewers × durationSeconds / 3600Two properties follow directly from that definition and are worth being explicit about. First, it is not a headcount: one person watching for two hours and 120 people watching for one minute each both produce two hours watched. Second, it multiplies an average measured over the observed portion of a broadcast by the broadcast’s full duration, which is the right estimate but means the figure inherits the uncertainty of its coverage. That is precisely what the confidence score exists to express.
Two published numbers that say how much weight a broadcast's statistics can bear.
The fraction of a broadcast covered by usable observations, from 0 to 1. Each edge is credited up to one sampling interval; anything beyond that is genuinely unobserved and is not credited.
leadCredit = min(max(firstSampleOffset, 0), expectedInterval)
tailCredit = min(max(lastSampleOffset, 0), expectedInterval)
coverage = clamp₀₁( (observedSeconds + leadCredit + tailCredit) / durationSeconds )A single 0–1 score combining coverage, how many samples exist at all, and whether we watched the broadcast end or merely inferred it:
confidence = coverage
× min(1, sampleCount / 4)
× (endedCleanly ? 1 : 0.9)It is multiplicative on purpose. Any single bad signal drags the score down, and the whole thing can be explained in one sentence: we are as confident as the worst part of the measurement allows. Fewer than 4 samples can never reach full confidence, however clean those samples look, and a broadcast closed by the reconciler rather than observed ending keeps a permanent 0.9× factor.
Confidence is shown on stream pages, and it is used as a gate: rankings exclude any channel whose data confidence in the window is below 50%, and the Trending score is multiplied by it directly.
Follower history begins at our first real observation of a channel’s follower total. There is no history before that point, we do not know what it was, and we do not back-fill it. A follower chart that starts in the middle of a channel’s life is telling the truth; one that stretches back to the channel’s creation would be fiction.
“Followers gained” is always the difference between two observations that both actually happened. If we have a reading on Monday and the next on Thursday, the gain is attributed to that span and the intervening days show no value rather than an invented one.
Warning: Follower collection can switch itself off
Follower totals are collected via the public total on GET /helix/channels/followers using an app access token, verified working on 2026-08-13. We never request or store the follower list itself. If Twitch ever declines the call, collection disables itself and no follower history is shown or invented.
Twitch documents the followers endpoint as requiring a user token with a specific scope, while also stating that a caller without that scope still receives the total — which is the only field we want. We therefore attempt it with an application token and treat a refusal as a first-class outcome: collection stops fleet-wide, an integrity alert is raised, and the interface says plainly that follower history is not being collected. Under no circumstances do we estimate, interpolate or back-fill a follower number we did not observe.
Workers fall over. Networks fail. Rate limits bite. When a measurement does not happen, the result is a hole in the record, and a hole is not a zero.
The same principle governs every surface on the site: a missing observation is rendered as missing, or the row is omitted entirely. Nothing is filled in to make a page look complete.
A percentage computed against a tiny baseline is noise wearing a suit.
“+1,400% average viewers” is technically true when a channel goes from two viewers to thirty, and it tells you nothing. Before any period-over-period delta is displayed, the previous period has to be substantial enough for a ratio to carry information. If it is not, the site shows no comparison at all rather than a spectacular number.
| Requirement on the previous period | Minimum |
|---|---|
| Tracked streams | 2 |
| Hours streamed | 2 |
| Average viewers | 3 |
| Follower base (for follower percentages) | 50 |
The decision is made once per window and applied to every metric on the page, so a profile never shows a confident-looking delta on one tile next to a blank on the tile beside it. Absolute values are still shown; it is only the percentage that is withheld.
We began existing at a particular moment, and we began tracking each channel at a particular moment after that. Every all-time figure on this site therefore means since we began tracking that channel, and every such figure is labelled with the date we started. It is not the streamer’s lifetime total on Twitch, and we never imply that it is.
Three consequences worth stating outright:
Window labels used across the site are 7 days, 30 days, 90 days, 1 year and All time — the last of which always carries its tracking-start qualifier.
Part of a channel's past can be reconstructed from Twitch's public VOD record. Its viewership cannot.
Twitch publishes no historical concurrent-viewer data. Not to us, not to anyone: there is no endpoint that answers “how many people were watching this channel last March”. Every site that shows you a viewer history built it the way we build ours — by polling the live API and keeping what it returned. Such a history begins on the day that site started watching, and there is no legitimate way to make it begin earlier.
One public record of the past does survive. GET /helix/videos lists a channel’s archived broadcasts, each carrying a start time, a duration and a title, and for some channels it reaches back years. We walk that list for the channels we track — deeply on a first pass, then only the newest pages — and import what it holds as broadcasts marked reconstructed rather than observed. How far back it goes is entirely the streamer’s choice: VODs expire, get deleted, or are never published at all, so the depth ranges from nothing to several thousand broadcasts. That range says something about a channel’s archiving habits and nothing about the channel.
A reconstructed broadcast is real history. It genuinely happened, on that date, for that long — Twitch’s own record says so. What is absent is the audience, because nobody was counting it at the time. That absence is permanent: it is not a gap we expect to fill, and it is not an estimate we are declining to publish. The number does not exist anywhere.
| Figure | Available for a reconstructed broadcast? |
|---|---|
| Start date and time | Yes — from Twitch’s VOD record. |
| Duration | Yes. |
| Title | Yes, as published. |
| Average, median, peak and minimum viewers | No. Viewership was not tracked at the time. |
| Estimated hours watched | No — it is computed from average viewers, which is absent. |
| Sampling coverage and data confidence | No. Nothing was sampled, so there is nothing to score. |
| Follower total on the day | No. Follower history starts at our first observation. |
A reconstruction must never be able to pass as an observation, so the separation is structural rather than a matter of care:
observed or vod_backfill. No query has to guess from the shape of the data.That third rule is worth the arithmetic, because getting it wrong would be invisible. Average viewers over any period is defined as:
averageViewers = Σ hoursWatched / Σ hoursStreamedA reconstructed broadcast contributes hours streamed and, necessarily, no hours watched. Let it into both sides of that division and the denominator grows while the numerator stands still. A channel with 100 observed hours and 20,000 estimated hours watched averages 200 viewers; recover 900 hours of its earlier history into the same calculation and the identical measurements would read 20. Every average, and everything ranked on one, would deflate in proportion to how much history we had recovered — worst for exactly the channels we know most about, and with nothing on the page to explain it.
For the same reason, a recovered archive does not count toward the content floor that decides whether a profile is offered to search engines. That floor counts broadcasts we observed, so importing a thousand VODs cannot promote a page that has nothing measured to show.
It is used wherever a start time and a duration are the whole of what the answer needs:
Reading a reconstructed broadcast
Where this site says a broadcast comes from Twitch’s VOD record, read it as: this stream happened, on this date, for this long, and its viewership was not tracked. It is not an estimate, a modelled figure or a number we are rounding — it is absent, and it is labelled as absent everywhere it appears.
Every board is a plain, published calculation over tracked broadcasts. There is no editorial thumb on the scale.
| Board | Periods | How it is computed |
|---|---|---|
| Average Viewers | 7 days, 30 days, 90 days | Total estimated hours watched divided by total hours streamed in the period, across broadcasts we tracked. Channels need at least 2 streams and 3 hours of tracked airtime to appear. |
| Peak Viewers | 7 days, 30 days, 90 days | The highest single viewer count we observed during the period. Because it is sampled rather than continuous, the true peak may be marginally higher. |
| Hours Watched | 7 days, 30 days, 90 days | Estimated hours watched: average concurrent viewers multiplied by broadcast duration, summed across tracked streams in the period. |
| Followers | 30 days | Current follower total as most recently observed. Ordering reflects our latest observation, not a live reading. |
| Follower Growth | 7 days, 30 days, 90 days | Followers gained between our first and last observation inside the period. Channels tracked for less than the full period are measured only over the time we actually observed. |
| Viewership Growth | 7 days, 30 days | Percentage change in average viewers versus the immediately preceding equal period. Channels whose previous period was too small for a percentage to be meaningful are excluded rather than shown with an inflated figure. |
| Hours Streamed | 7 days, 30 days, 90 days | Total tracked broadcast time in the period. |
| Trending | 7 days | A composite of recent viewership growth, hours-watched growth, follower growth and activity, measured against each channel’s own baseline and scaled by data confidence. Deliberately not a ranking by size. |
| Rising | 30 days | Small and mid-sized channels (10–2,000 average viewers) showing sustained growth over both 7 and 30 days, ranked by momentum against their own baseline. |
A channel must clear a floor in the window before it can appear on any board: at least 2 tracked streams, at least 3 hours of tracked airtime, and data confidence of at least 50%. A channel we barely measured cannot beat a channel we measured well, because it is not on the board at all.
Boards are computed by a scheduled job and read from a stored snapshot, never sorted at request time. Each board therefore shows the date of the snapshot it is displaying — if that date looks stale, it is stale, and the page says so instead of implying live ordering. Boards hold up to 500 channels, paginated 50 at a time, and rank movement is measured against the previous snapshot.
No opaque weighting, no machine-learned black box. Every score can be recomputed by hand from numbers already shown on the page.
Raw ratios are unusable for ranking: a channel going from 2 to 40 viewers produces a bigger percentage than any established channel can, forever. Growth is therefore measured in doublings and clamped, which is symmetric — halving scores −1, doubling scores +1:
growth = clamp( log₂(current / baseline), −2, +2 )How little a channel’s audience varies from stream to stream. 100 means every broadcast draws the same crowd; 0 means wild swings. Computed over the per-stream averages in the window, and withheld below 3 streams, where variance is just noise:
volatility = standardDeviation(streamAverages) / mean(streamAverages)
consistency = 100 × (1 − min(1, volatility))Recent performance against the channel’s own baseline — the last 7 days versus the 30 days before them. Flat is 50, doubling is about 88, halving is about 12. The hyperbolic tangent bounds the score without a hard cutoff, so a channel that grew tenfold still ranks above one that grew threefold without running away with the board:
momentum = 50 + 50 × tanh( log₂(recentAverageViewers / baselineAverageViewers) )Withheld when the baseline is below 5 average viewers, because at that size the ratio is measuring randomness.
A gentle, logarithmic reward for audience scale, used only by Trending. The floor guarantees a genuinely surging small channel keeps at least 35% of its growth signal — otherwise Trending would just be Top with extra steps:
prior = 0.35 + 0.65 × min(1, log₁₀(1 + averageViewers) / log₁₀(1 + 1,000))A composite measured against each channel’s own baseline, then scaled by how much we trust the underlying data and by the size prior:
raw = 0.45·viewerGrowth
+ 0.25·hoursWatchedGrowth
+ 0.20·followerGrowth
+ 0.10·activity
score = 100 × raw × dataConfidence × sizePriorWhere each component is mapped onto 0–1:
Minimum-activity gates are applied before any of that. Without at least 2 streams and 3 hours in the last 7 days, and a baseline of at least 10 average viewers, the score is zero regardless of how flattering the ratios look. One lucky stream cannot win a board.
Rising targets small and mid-sized channels growing steadily rather than spiking once. A channel qualifies when it has between 10 and 2,000 average viewers over 30 days, at least four tracked streams in that window, and growth of at least 15% over both the 7-day and the 30-day horizon. Requiring both horizons is what excludes the single viral broadcast.
How a channel does in one category relative to its own overall average — the “this game draws 1.4× your usual audience” figure. Withheld unless the category has at least 5 hours of tracked airtime:
index = categoryAverageViewers / overallAverageViewersA broadcast’s percentile is the share of the channel’s recent streams that drew fewer viewers, computed only when there are at least five comparable streams. A stream is flagged as a breakout when it drew at least 1.75× the channel’s average and sits at or above the 90th percentile of its own recent history — both bars, so a quiet channel’s ordinary night cannot qualify.
The weekday/hour heatmap is built from the last 90 days and is only shown once a channel has at least 6 tracked streams in that window. Hours are UTC, and a cell with no tracked airtime is empty rather than zero.
Four boards that sound similar and answer genuinely different questions.
| Board | Question it answers | Who appears |
|---|---|---|
| Top | Who is largest right now? | The biggest channels. A plain sort on a measured metric — no score, no weighting. |
| Fastest Growing | Who added the most, in absolute or relative terms? | Channels with the largest follower gain, or the largest percentage change in viewership against the immediately preceding equal period. |
| Trending | Who is unusually far above their own normal? | Channels of any size whose last 7 days beat their own baseline, scaled by data confidence and a mild size prior. Deliberately not a ranking by size. |
| Rising | Which smaller channels are growing consistently? | Channels between 10 and 2,000 average viewers that grew over both the 7-day and 30-day horizons. |
Category viewer and channel counts are sums over the broadcasts we observed, in the portion of the live directory we walked. They are not Twitch-wide totals, and they are systematically lower than Twitch’s own directory figures, because our crawl is bounded at roughly the top 1,500 concurrent streams. Treat them as a well-defined lower bound and a reliable indicator of direction, not as a platform census.
Within a channel’s profile, category share is a share of that channel’s tracked airtime, computed from the category segments recorded during its broadcasts. A category needs at least 5 hours of tracked airtime before it can be named a channel’s best-performing category.
A category only gets its own indexable page once it has at least 5 tracked streamers and 100 estimated hours watched over 30 days. Below that, anything we published would be a page about our own sampling rather than about the category.
We deliberately track far more channels than we ask search engines to list.
Tracking a channel and publishing it to search engines are two separate decisions. The database holds every channel we have ever seen go live; the sitemap holds a much smaller set. Emitting a million URLs that read “username — no history yet” would be thin content on an industrial scale, it would be useless to the person who clicked it, and it is the fastest available way to destroy a site’s standing. So a page has to earn its listing.
The decision runs in a fixed order:
Pages that are not offered for indexing are still fully readable, still linked, and are marked noindex, follow rather than noindex, nofollow — the page still points at categories, rankings and similar channels, and there is no reason to discard that.
The numeric thresholds behind the floor and the signals are operator-tunable rather than baked into a build, because they are starting values to be adjusted against real search data, not permanent truths.
| Status | Listed in search? | Meaning |
|---|---|---|
| discovered | No | This channel was just discovered. Tracking has started; there is no history yet. |
| tracking | No | This channel is being tracked. Charts and statistics will build as more data is collected. |
| qualified | Yes | This profile has enough tracked history to be listed in search engines. |
| historical | Yes | This channel is currently inactive. Its tracked history is preserved and remains listed. |
| noindex | No | This profile does not yet have enough distinct data to be listed in search engines. |
| removed | No | This channel is no longer available on Twitch. |
Where the minutes go, and why a comparison against another tracker will rarely tie exactly.
Nothing here is instantaneous, and the site would rather be a few minutes late and correct. A viewer count observed now travels through several scheduled stages before it reaches a profile page:
| Stage | Runs every | What it produces |
|---|---|---|
| Directory discovery | 5m | New channels, observations for the largest broadcasts, category totals. |
| Targeted sampling | 1m | Observations for tracked channels due under their priority tier. |
| Stream finalisation | 2m | Permanent per-broadcast aggregates: average, peak, median, hours watched, coverage, confidence. |
| Aggregation | 3m | Daily rows and the 7/30/90/365-day and all-time window statistics. |
| Profile refresh | 5m | Names, avatars and descriptions refreshed within the permitted window. |
| Follower sync | 6h per channel | Follower totals, subject to the caveat above. |
| Category rollups | 30m | Per-category observed viewership and channel counts. |
| Rankings | 1h | Board snapshots, each stamped with the date it was computed. |
On top of that, read models are cached briefly so that a popular profile does not re-run its analytical queries for every visitor. A stream that ended two minutes ago may therefore still be shown as live, and a broadcast that ended ten minutes ago may not yet have its final numbers.
If a figure on this site looks wrong rather than merely different, we would like to know. See contact.
Three retention classes, applied field by field.
Twitch’s Developer Agreement limits how long Twitch Materials — the text, images and metadata Twitch serves us — may be cached, and requires us to honour upstream deletions. It does not turn our own measurements into Twitch content. “At 14:05:00Z we observed 12,431 concurrent viewers on this broadcast” is a record of what our system measured, as is everything derived from it.
Every field we store is tagged with exactly one of three classes:
Raw samples are kept for 120 days, after which hourly rollups take over for 550 days; daily and per-broadcast aggregates are kept indefinitely. The full field-by-field map is below.
| Field | Source | Retention class | Max cache age | On upstream deletion |
|---|---|---|---|---|
| creator_account.external_account_id | GET /helix/users | First-party observation | No limit | retain |
| creator_account.login | GET /helix/users | Cached Twitch material | 24h | anonymize |
| creator_account.display_name | GET /helix/users | Cached Twitch material | 24h | anonymize |
| creator_account.profile_image_url | GET /helix/users | Cached Twitch material | 24h | purge |
| creator_account.offline_image_url | GET /helix/users | Cached Twitch material | 24h | purge |
| creator_account.description | GET /helix/users | Cached Twitch material | 24h | purge |
| creator_account.broadcaster_type | GET /helix/users | Cached Twitch material | 24h | purge |
| creator_account.platform_created_at | GET /helix/users | Cached Twitch material | No limit | purge |
| stream.external_stream_id | GET /helix/streams | First-party observation | No limit | retain |
| stream.started_at | GET /helix/streams | First-party observation | No limit | retain |
| stream.initial_title / stream.final_title / stream_segment.title | GET /helix/streams | Cached Twitch material | No limit | purge |
| stream.language | GET /helix/streams | Cached Twitch material | No limit | purge |
| stream_sample.viewer_count | GET /helix/streams (observed by us at a specific instant) | First-party observation | No limit | retain |
| stream_sample.sampled_at | internal clock | First-party observation | No limit | retain |
| category.external_category_id | GET /helix/games | First-party observation | No limit | retain |
| category.name | GET /helix/games | Cached Twitch material | 168h | purge |
| category.box_art_url | GET /helix/games | Cached Twitch material | 24h | purge |
| follower_sample.follower_count | GET /helix/channels/followers (total) | First-party observation | No limit | retain |
| stream.average_viewers / peak_viewers / estimated_hours_watched | derived from stream_sample | Derived analytics | No limit | retain |
| daily_creator_stat.* / creator_window_stat.* / ranking_snapshot.* | derived from first-party observations | Derived analytics | No limit | retain |
Not affiliated with Twitch
If a number here is wrong — not merely different from another source, but wrong — tell us and we will look at it. Where the error is ours we will correct it; where it is a limitation of sampling we will say so rather than quietly adjust a figure.
Streamers who want their cached Twitch material corrected or removed should read data removal, which sets out precisely what can be removed, what may be retained as our own observations, and what verification we need. For anything else, see contact, or read about StreamerVitals for what this site is and how it is funded.