How Quilore counts a skip, not the field Spotify gives you for it.
Your export contains a field called skipped. For
seven and a half years of history it is wrong for everyone, and it looks like a fact
about you rather than a defect in the file.
A skip and a short play sound like the same idea. They are two separate rules in this site's code, checked against different fields, and a track can be one without being the other. Getting that distinction wrong is how a statistics tool tells you something false and confident: that you barely ever skip anything.
The field that goes quiet for seven years
Every record in the extended streaming history carries a boolean called
skipped. It would be reasonable to read that field and be done. We tried,
against a real 160,634-record export, and found that skipped reads
false for every single stream dated between 13 April 2015 and
16 October 2022, with a normal mix of true and false on either side of that
window. Filtering on the field as documented reports a 0.00% skip rate for those
seven and a half years. That does not read as a bug. It reads as a fact about a
listener who apparently never once pressed forward, which is exactly the kind of
wrong answer that never gets questioned.
What we derive instead
Every stream also carries reason_end, the platform's own account of why
the track stopped. Four of its values describe a listener leaving before the track
finished on its own terms, whatever the skipped flag claims:
reason_end | What actually happened |
|---|---|
fwdbtn | Skipped forward, mid-track |
backbtn | Skipped back, mid-track |
endplay | Playback stopped without finishing |
unknown | Spotify's own catch-all for an interrupted end |
A record showing a genuine skip looks like this. Note the flag and the reason disagreeing with each other, which is the whole problem in one line:
{
"ts": "2019-03-02T21:14:07Z",
"ms_played": 41200,
"master_metadata_track_name": "Overgrown",
"master_metadata_album_artist_name": "James Vincent McMorrow",
"reason_start": "trackdone",
"reason_end": "fwdbtn",
"skipped": false,
"conn_country": "GB"
}
That is a listener holding the forward button forty-one seconds in, on a stream dated
well inside the seven-and-a-half-year gap, with the field that is supposed to say so
set to false. The rule this site applies is an OR across both signals,
not a replacement of one by the other:
isSkipped = skipped === true || reason_end is one of the four
above. Either signal is enough. Neither has to agree with the other.
Checking that the fix does not invent skips
Adding a second signal on top of a broken one is only safe if it never fires where the
original field would have said no and been right. We checked, across the same
160,634 records: there is not one case of skipped === true paired with a
reason_end outside that four-value set. The derived rule is a strict
superset of the raw flag wherever the raw flag actually works. It adds coverage for the
seven and a half years the flag is silent, and it does not disagree with the flag for a
single stream in the years the flag is populated.
A skip and a short play are not the same rule
The site also has a separate threshold: a stream needs at least 30,000 milliseconds of
ms_played to count as a play at all, which is Spotify's own definition of a
stream and removes 9.1% of records in the same export, a meaningful share rather than a
rounding error. It is tempting to fold that into "skip" too, since a track abandoned
after four seconds is obviously a skip. It is also wrong, in both directions:
- A twelve-second interlude that plays to
reason_end: "trackdone"is short, but nobody skipped it. It is excluded from the play count and absent from the skip count, because it simply is not a skip. - Holding the forward button forty seconds into a six-minute track is a skip by every reasonable definition, and it clears the 30,000 ms threshold easily. It counts as a play and as a skip, at the same time.
Collapsing the two into one rule would either miss the second case or manufacture skips out of short intros and interludes that nobody actually abandoned. They stay two separate checks against two separate fields because that is what the export actually describes.
What this changes about your own numbers
If your listening history reaches back before October 2022, a skip rate computed the
obvious way, by reading skipped directly, is not conservative. It is
missing years. The gap is not evenly spread either: a heavy listener from 2016 will see
their derived skip rate move far more than someone whose account started last year,
because more of their history sits inside the window where the flag never worked.
About the data
The record counts, the date range and the field values above come from a single anonymised 160,634-record export, checked directly rather than estimated. Your own export will contain different numbers inside the same rule.
See your own skip rate.
Drop your Spotify export into Quilore. It runs in this tab and nothing leaves your device.