The downloads of episodes, counted by the rules of the IAB Tech Lab from the requests that a podcast host logs.
The rules are those of the Podcast Measurement Technical Guidelines, version 2.3 of September 2026. They count the episodes of video too, when a feed delivers them as files.
A request is a map of one line of the log of a server:
{:time #inst "2026-09-26T08:15:02Z"
:ip "203.0.113.5" ; or a hash of it
:user-agent "Overcast/3.0 (+http://overcast.fm/; iOS podcast app)"
:method :get
:url "https://cdn.example.com/episode-54.mp3"
:status 206
:range "bytes=0-" ; the Range header
:bytes 1048576 ; of the body that was sent
:referer nil}
The requests of a listener for a file in 24 hours count as one download when they hold enough of the file to play for one minute, and a listener is an IP address and a user agent. The requests that can't be part of a download are left out, such as HEAD requests and the requests of bots.
The rules need data from outside podcast-clj: the open list of user agents of the OPAWG, which names the apps and the bots, and lists of the addresses of data centers, VPNs and known bots from other sources.
The downloads of episodes, counted by the rules of the IAB Tech Lab from
the requests that a podcast host logs.
The rules are those of the Podcast Measurement Technical Guidelines,
version 2.3 of September 2026. They count the episodes of video too,
when a feed delivers them as files.
A request is a map of one line of the log of a server:
{:time #inst "2026-09-26T08:15:02Z"
:ip "203.0.113.5" ; or a hash of it
:user-agent "Overcast/3.0 (+http://overcast.fm/; iOS podcast app)"
:method :get
:url "https://cdn.example.com/episode-54.mp3"
:status 206
:range "bytes=0-" ; the Range header
:bytes 1048576 ; of the body that was sent
:referer nil}
The requests of a listener for a file in 24 hours count as one download
when they hold enough of the file to play for one minute, and a
listener is an IP address and a user agent. The requests that can't be
part of a download are left out, such as HEAD requests and the requests
of bots.
The rules need data from outside podcast-clj: the open list of user
agents of the OPAWG, which names the apps and the bots, and lists of the
addresses of data centers, VPNs and known bots from other sources.(downloads requests)(downloads requests
{:keys [threshold file-fn window]
:or {file-fn (comp file-url :url) window :day}
:as opts})The downloads that requests make by the rules of the IAB with opts,
and why the other requests don't count, as a map of :downloads and
:dropped.
The requests of one listener for one file in one window of 24 hours make one download. They must hold at least the bytes of the threshold of the file. A download is the first of its requests, with the :listener that it counts for and the agent that its user agent names. The listener is a pair of the part of the address that counts and the user agent. The downloads come in the order of their first requests.
The :dropped map counts the requests that the filters leave out, by
reason. Under :short, it counts the requests of listeners who fetched
less than the threshold. The opts are these:
A window is a day in UTC. With :rolling, a window is the 24 hours from the first request of a listener for a file. With :extending, it's the 24 hours from the last request, so each request extends it. These are the three windows of the guidelines. For a file with no threshold, any request that passes the filters counts.
The requests of a listener hold the bytes that their ranges ask for, cut to the :bytes that the server sent. A byte counts once, whatever the number of requests that ask for it.
The downloads that `requests` make by the rules of the IAB with `opts`, and why the other requests don't count, as a map of :downloads and :dropped. The requests of one listener for one file in one window of 24 hours make one download. They must hold at least the bytes of the threshold of the file. A download is the first of its requests, with the :listener that it counts for and the agent that its user agent names. The listener is a pair of the part of the address that counts and the user agent. The downloads come in the order of their first requests. The :dropped map counts the requests that the filters leave out, by reason. Under :short, it counts the requests of listeners who fetched less than the threshold. The `opts` are these: - :user-agents, as user-agents gives them, to name the apps, the devices and the bots - :exclude-pred, a predicate of a request, for the addresses to leave out - :threshold, a function of a file that gives the bytes that a download needs, or a map of files to bytes - :file-fn, a function of a request that gives the file that it asks for, by default its URL without the query - :window, which is :day by default, :rolling or :extending A window is a day in UTC. With :rolling, a window is the 24 hours from the first request of a listener for a file. With :extending, it's the 24 hours from the last request, so each request extends it. These are the three windows of the guidelines. For a file with no threshold, any request that passes the filters counts. The requests of a listener hold the bytes that their ranges ask for, cut to the :bytes that the server sent. A byte counts once, whatever the number of requests that ask for it.
(drop-reason request)(drop-reason {:keys [method status range user-agent] :as request}
{:keys [exclude-pred] :as opts})Why the rules of the IAB leave out request by opts, as a keyword, or
nil when it can be part of a download. These are the filters of the
first step of the guidelines, in the order that they apply:
opts takes.
It's for the addresses of servers, data centers, VPNs and known bots,
from the lists of the host.Why the rules of the IAB leave out `request` by `opts`, as a keyword, or nil when it can be part of a download. These are the filters of the first step of the guidelines, in the order that they apply: - :malformed, a request with no instant as :time, or no :url or :ip, or with a field of another type than the type of that field - :method, a request that isn't GET, since HEAD moves no content - :status, an answer that isn't 200 or 206, or 000, which Akamai logs for a 206 that ended early. A request without a status passes, since a prefix service sees no answers. - :two-bytes, ranges of two bytes or fewer, which players ask for to learn whether a server sends ranges - :no-user-agent, a request with no user agent - :watch, a request of Apple Podcasts on a watch, which repeats the download of the phone - :bot, a request whose :agent-type is bot or bot-library. When the list doesn't know the agent, it's a user agent that says bot, crawler or spider. - :excluded, a request that the :exclude-pred of `opts` takes. It's for the addresses of servers, data centers, VPNs and known bots, from the lists of the host.
(identify user-agents user-agent)(identify user-agents user-agent referer)The agent that the header user-agent names by the list user-agents,
with the keys that OP3 gives it, and the referrer of the header
referer.
The result is a map of :agent-type, :agent-name, :device-type and :device-name. With the referer of a request from a browser, it also has :referrer-type and :referrer-name.
The agent type is the type of the file of the first entry that matches: bot, app, library or browser. A library that bots use is bot-library. A user agent that matches no entry is unknown, and its name is the header. The device comes from the devices of the list, except for a bot. The referrer comes from the referrers of the list, or else it's the domain of the referer.
The agent that the header `user-agent` names by the list `user-agents`, with the keys that OP3 gives it, and the referrer of the header `referer`. The result is a map of :agent-type, :agent-name, :device-type and :device-name. With the referer of a request from a browser, it also has :referrer-type and :referrer-name. The agent type is the type of the file of the first entry that matches: bot, app, library or browser. A library that bots use is bot-library. A user agent that matches no entry is unknown, and its name is the header. The device comes from the devices of the list, except for a bot. The referrer comes from the referrers of the list, or else it's the domain of the referer.
(listener-ip ip)The part of the IP address ip that tells listeners apart, as the IAB
counts them.
That's an IPv4 address whole, and the first 64 bits of an IPv6 address, as a prefix such as 2001:db8:1:2::/64. An IPv4 address inside IPv6, as a server writes a client of IPv4, is that IPv4 address.
Other text, such as a hash of an address, stays as it is. So a host that keeps a hash in place of the address hashes this part.
The part of the IP address `ip` that tells listeners apart, as the IAB counts them. That's an IPv4 address whole, and the first 64 bits of an IPv6 address, as a prefix such as 2001:db8:1:2::/64. An IPv4 address inside IPv6, as a server writes a client of IPv4, is that IPv4 address. Other text, such as a hash of an address, stays as it is. So a host that keeps a hash in place of the address hashes this part.
(threshold {:keys [size header duration] :as file})The bytes of file that a download needs by the rules of the IAB: the
header, and one minute of the content. It's nil when the size isn't
known.
The file is a map of its :size and the size of its :header in bytes,
and its :duration in seconds. The header of an MP3 file is its ID3 tag.
An episode of one minute or less needs the whole file, and so does a
file with no duration.
The bytes of `file` that a download needs by the rules of the IAB: the header, and one minute of the content. It's nil when the size isn't known. The `file` is a map of its :size and the size of its :header in bytes, and its :duration in seconds. The header of an MP3 file is its ID3 tag. An episode of one minute or less needs the whole file, and so does a file with no duration.
(user-agents files)The open list of user agents of the OPAWG from files, ready to
identify agents by.
The files map :bots, :apps, :libraries, :browsers, :devices and
:referrers to the JSON of the file of that name, as text or read. The
files are in src/ or build/ of https://github.com/opawg/user-agents-v2.
Each entry of a file has a name, a pattern and at times a category. The pattern is a regular expression of JavaScript. A pattern that doesn't compile on the platform is left out, and the report of the list says so. The list changes often, so a host keeps a copy of one commit.
The open list of user agents of the OPAWG from `files`, ready to identify agents by. The `files` map :bots, :apps, :libraries, :browsers, :devices and :referrers to the JSON of the file of that name, as text or read. The files are in src/ or build/ of https://github.com/opawg/user-agents-v2. Each entry of a file has a name, a pattern and at times a category. The pattern is a regular expression of JavaScript. A pattern that doesn't compile on the platform is left out, and the report of the list says so. The list changes often, so a host keeps a copy of one commit.
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |