How Quilore counts a skip, not the field Spotify gives you for it.
Your export contains a field called skipped. In the
exports we have checked, it is wrong for years of history, 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 that starts in July 2021. Across its first
2,833 streams, everything before 18 October 2022,
skipped reads false every single time. From that date on it
behaves normally. Reading the reason each track ended finds 806 skips
in the stretch where the flag found none.
Our export cannot see further back than July 2021. An independent analysis of an older export puts the start of the silent stretch at 13 April 2015, and its end matches ours to within two days, which is why we describe it as seven and a half years. Filtering on the field as documented reports no skips at all for that period. 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 of that kind looks like this. The fields and their values are the ones the export uses; the track and the timestamp are made up, so nothing here is anyone's real listening. Note the flag and the reason disagreeing, which is the whole problem in one line:
{
"ts": "2022-03-02T21:14:07Z",
"ms_played": 41200,
"master_metadata_track_name": "Example Track",
"master_metadata_album_artist_name": "Example Artist",
"reason_start": "trackdone",
"reason_end": "fwdbtn",
"skipped": false,
"conn_country": "GB"
}
That is a listener pressing forward forty-one seconds in, on a stream dated inside the
silent stretch, 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
stretch where the flag is silent, and it does not disagree with the flag for a single
stream once 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 counts above come from one real export, 160,634 records from 24 July 2021 to 18 August 2026, belonging to one of the two people who build Quilore and used with their agreement. They were checked directly rather than estimated. The 13 April 2015 start date is from an independent analysis of an older export, and the example record is made up. 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.