Skip to content

API reference · API keys · Analytics API guide

Downloads per episode ​

  • Method: GET
  • Path: /v1/me/podcasts/{podcast_id}/analytics/episodes
  • Operation ID: listEpisodeAnalytics
  • Tags: Analytics

Every published episode, and any unpublished episode that has downloads, newest first: downloads in the range, all time and in the first seven days. 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.

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.

VariableValue to supply
SHOWFM_API_KEYYour API key. Keep it on your server.
PODCAST_IDA 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/episodes" \
  --header "Authorization: Bearer ${SHOWFM_API_KEY}"
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/episodes`);

  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/episodes`);

  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/episodes'
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 error
php
<?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/episodes";
$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: uuid

    The podcast id (UUID).

Query parameters ​

  • preset: string, possible values: "7d", "30d", "90d", "12m", "all"

    A named range ending today (UTC): 7d, 30d, 90d, 12m or all. Default 30d. Do not combine with from or to.

  • from: string, format: date

    First UTC day, YYYY-MM-DD. Not after today.

  • to: string, format: date

    Last UTC day, YYYY-MM-DD. Needs from. Default today.

  • limit: integer, default: 20, minimum: 1, maximum: 100

    Page size, 1 to 100. Default 20.

  • cursor: string

    The pagination.next_cursor value from the previous page.

Responses ​

Browse the documented response statuses, headers and body fields.

200 OK ​

Response headers ​

  • RateLimit-Policy

    The bucket that applied, e.g. "account";q=60;w=60.

    string

  • RateLimit

    Requests left and seconds to reset, e.g. "account";r=59;t=42.

    string

  • ETag

    string

Response body ​

Content type: application/json

no additional properties

304 Not modified (matching If-None-Match). ​

This response has no body.

400, 401, 403, 404, 500, 503 ​

  • 400 invalid_request: a bad parameter, or a key sent in the URL.
  • 401 unauthorized (no Bearer key) or invalid_api_key.
  • 403 insufficient_scope, forbidden (role or team-key setting) or plan_upgrade_required.
  • 404 not_found: the key cannot reach this resource.
  • 500 internal_error. Retry; quote X-Request-Id if it persists.
  • 503 service_unavailable. Retry shortly.

Response body ​

Content type: application/json

schema: Error, no additional properties

  • error (required): object, no additional properties
    • code (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): string

    • details: array

      Items:

      no additional properties

      • field (required): string

      • message (required): string

      • reason: string

        A stable reason a client may branch on, for example outside_retention.

    • docs_url: string

    • request_id: string

429 rate_limited. Wait for Retry-After seconds. ​

Response headers ​

  • Retry-After

    integer

  • RateLimit-Policy

    The bucket that applied, e.g. "account";q=60;w=60.

    string

  • RateLimit

    Requests 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.

Was this page helpful?