Appearance
API reference · API keys · Analytics API guide
Downloads for a podcast over time
- Method:
GET - Path:
/v1/me/podcasts/{podcast_id}/analytics/downloads - Operation ID:
getPodcastDownloads - Tags: Analytics
A dense series of downloads per day, week or month, with unique listeners per day (never summed: weeks and months give the peak single day) and totals. Requires permission to see live analytics and use the analytics API (analytics.view). Every analytics response carries meta.range (the window used, after your plan's history limit), meta.freshness (how complete the figures are) and meta.methodology. A range ending before your plan's history is 400 with details[0].reason = "outside_retention".
Scope: analytics:read.
Make a request
Set these environment variables before running a sample. Each language reads the same values; no identifier needs replacing inside the code.
| Variable | Value to supply |
|---|---|
SHOWFM_API_KEY | Your API key. Keep it on your server. |
PODCAST_ID | A podcast UUID returned by List your podcasts. |
Samples run on your server: curl 7.76+, Node.js 22+ (TypeScript via your TypeScript runner), Python 3, or PHP 8 with the cURL extension. Never put your API key in browser code.
bash
: "${SHOWFM_API_KEY:?Set SHOWFM_API_KEY in your environment}"
: "${PODCAST_ID:?Set PODCAST_ID in your environment}"
curl --fail-with-body --silent --show-error --get \
"https://api.show.fm/v1/me/podcasts/${PODCAST_ID}/analytics/downloads" \
--header "Authorization: Bearer ${SHOWFM_API_KEY}" \
--data-urlencode "preset=30d" \
--data-urlencode "interval=day"ts
function env(name: string): string {
const value = process.env[name];
if (!value) throw new Error(`Set ${name} in your environment`);
return value;
}
async function main() {
const url = new URL(`https://api.show.fm/v1/me/podcasts/${encodeURIComponent(env("PODCAST_ID"))}/analytics/downloads`);
url.searchParams.set("preset", "30d");
url.searchParams.set("interval", "day");
const response = await fetch(url, {
headers: { Authorization: `Bearer ${env("SHOWFM_API_KEY")}` },
signal: AbortSignal.timeout(30_000)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
console.log(JSON.stringify(await response.json(), null, 2));
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});js
function env(name) {
const value = process.env[name];
if (!value) throw new Error(`Set ${name} in your environment`);
return value;
}
async function main() {
const url = new URL(`https://api.show.fm/v1/me/podcasts/${encodeURIComponent(env("PODCAST_ID"))}/analytics/downloads`);
url.searchParams.set("preset", "30d");
url.searchParams.set("interval", "day");
const response = await fetch(url, {
headers: { Authorization: `Bearer ${env("SHOWFM_API_KEY")}` },
signal: AbortSignal.timeout(30_000)
});
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${await response.text()}`);
}
console.log(JSON.stringify(await response.json(), null, 2));
}
main().catch(error => {
console.error(error);
process.exitCode = 1;
});python
import os
import json
from urllib.parse import quote, urlencode
from urllib.request import Request, urlopen
from urllib.error import HTTPError
url = f'https://api.show.fm/v1/me/podcasts/{quote(os.environ["PODCAST_ID"], safe="")}/analytics/downloads'
url += "?" + urlencode({
"preset": "30d",
"interval": "day",
})
request = Request(url, headers={
"Authorization": "Bearer " + os.environ["SHOWFM_API_KEY"]
})
try:
with urlopen(request, timeout=30) as response:
print(json.dumps(json.load(response), indent=2))
except HTTPError as error:
raise RuntimeError(f"HTTP {error.code}: {error.read().decode()}") from errorphp
<?php
function requiredEnv(string $name): string {
$value = getenv($name);
if ($value === false || $value === '') {
throw new RuntimeException("Set $name in your environment");
}
return $value;
}
$url = "https://api.show.fm" . "/v1/me/podcasts/" . rawurlencode(requiredEnv("PODCAST_ID")) . "/analytics/downloads";
$url .= '?' . http_build_query([
"preset" => "30d",
"interval" => "day",
], '', '&', PHP_QUERY_RFC3986);
$curl = curl_init($url);
curl_setopt_array($curl, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTPHEADER => ["Authorization: Bearer " . requiredEnv("SHOWFM_API_KEY")],
]);
$body = curl_exec($curl);
if ($body === false) {
throw new RuntimeException(curl_error($curl));
}
$status = curl_getinfo($curl, CURLINFO_HTTP_CODE);
if ($status < 200 || $status >= 300) {
throw new RuntimeException("HTTP $status: $body");
}
echo json_encode(json_decode($body, false, 512, JSON_THROW_ON_ERROR), JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR) . PHP_EOL;Base URL
https://api.show.fm
Authentication
apiKey: HTTP bearer
An API key from Settings → API keys.
showfm_live_in production.
Path parameters
podcast_id(required):string, format:uuidThe podcast id (UUID).
Query parameters
preset:string, possible values:"7d", "30d", "90d", "12m", "all"A named range ending today (UTC):
7d,30d,90d,12morall. Default30d. Do not combine withfromorto.from:string, format:dateFirst UTC day,
YYYY-MM-DD. Not after today.to:string, format:dateLast UTC day,
YYYY-MM-DD. Needsfrom. Default today.interval:string, possible values:"day", "week", "month", default:"day"day(default),week(ISO weeks, Monday start) ormonth, in UTC. A daily series is limited to 1,100 days.
Responses
Browse the documented response statuses, headers and body fields.
200 OK
Response headers
RateLimit-PolicyThe bucket that applied, e.g.
"account";q=60;w=60.stringRateLimitRequests left and seconds to reset, e.g.
"account";r=59;t=42.stringETagstring
Response body
Content type: application/json
no additional properties
data(required): PodcastDownloadsmeta(required): AnalyticsMeta
Example response
Illustrative data; your identifiers and figures will differ.
json
{
"data": {
"podcast_id": "123e4567-e89b-12d3-a456-426614174000",
"interval": "day",
"periods": [
{
"period_start": "2026-09-01",
"period_end": "2026-09-01",
"downloads": 120,
"unique_listeners": 95,
"partial": false
}
],
"totals": {
"downloads": 120,
"peak_daily_unique_listeners": 95
}
},
"meta": {
"range": {
"requested_from": "2026-09-01",
"requested_to": "2026-09-01",
"from": "2026-09-01",
"to": "2026-09-01",
"clamped": false,
"clamp_reason": null,
"retention_days": null,
"data_start": "2026-09-01"
},
"freshness": {
"source": "show.fm",
"verified_from": "2026-09-01",
"complete_through": "2026-09-01",
"revisable_days": 1,
"today_partial": false,
"last_updated_at": "2026-09-02T12:00:00Z"
},
"methodology": {
"source": "show.fm",
"guideline": "IAB Podcast Measurement Technical Guidelines v2.2 (aligned, not certified)",
"certified": false,
"download_definition": "One download per hashed IP address and user agent, per episode, per UTC day, after bot filtering and a minimum of 60 seconds of audio.",
"unique_listener_definition": "Distinct hashed IP address and user agent pairs in one UTC day. Never summed across days.",
"day_boundary": "UTC",
"docs_url": "https://show.fm/docs/analytics/"
}
}
}304 Not modified (matching If-None-Match).
If-None-Match). This response has no body.
400, 401, 403, 404, 500, 503
400invalid_request: a bad parameter, or a key sent in the URL.401unauthorized(no Bearer key) orinvalid_api_key.403insufficient_scope,forbidden(role or team-key setting) orplan_upgrade_required.404not_found: the key cannot reach this resource.500internal_error. Retry; quoteX-Request-Idif it persists.503service_unavailable. Retry shortly.
Response body
Content type: application/json
schema: Error, no additional properties
error(required):object, no additional propertiescode(required):string, possible values:"invalid_request", "not_found", "unavailable", "unauthorized", "invalid_api_key", "insufficient_scope", "forbidden", "plan_upgrade_required", "rate_limited", "service_unavailable", "internal_error", "not_implemented"message(required):stringdetails:arrayItems:
no additional properties
field(required):stringmessage(required):stringreason:stringA stable reason a client may branch on, for example
outside_retention.
docs_url:stringrequest_id:string
429 rate_limited. Wait for Retry-After seconds.
rate_limited. Wait for Retry-After seconds. Response headers
Retry-AfterintegerRateLimit-PolicyThe bucket that applied, e.g.
"account";q=60;w=60.stringRateLimitRequests left and seconds to reset, e.g.
"account";r=59;t=42.string
Response body
Content type: application/json
Uses the shared Error schema. View response fields.