📊 Analytics Reports
Generate and download daily streaming analytics for Spotify, Apple Music and YouTube.
Resource Overview
Analytics Reports allow you to export full daily streaming statistics for a specific DSP as a downloadable ZIP file containing JSON data. Reports are generated asynchronously — you request the report, then poll for its status until it completes, and finally download the file via a pre-signed URL.
/analytics-reports
POST, GET
ZIP (JSON inside)
Owner-restricted
How It Works
Unlike standard JSON:API resources that return data synchronously, Analytics Reports use an asynchronous workflow because the datasets can be very large (10,000+ records per day). The process has three steps:
Request a report
Send a POST with the DSP ID and date. You receive a report ID and status pending.
Poll for status
Send a GET with the report ID. The status progresses: pending → processing → completed (or failed).
Download the file
When completed, the response includes a download_url — a pre-signed S3 URL valid for 12 hours. The file is a ZIP containing a single JSON file.
Supported DSPs
| DSP | dsp_id | Description |
|---|---|---|
| Spotify | 1 | Daily track stats including streams, skips, demographics, device/source breakdown, engagement metrics. |
| YouTube | 3 | Daily track stats including views, watch time, geography, and playback metrics. |
| Apple Music | 4 | Daily track stats including plays, listeners, and Shazam data. |
/api/v1/dsps. Only the DSPs listed above are supported for analytics reports.
Step 1: Create a Report
/analytics-reports
POST /api/v1/analytics-reports
Authorization: Bearer {token}
Content-Type: application/json
Accept: application/json
Request Body
| Field | Type | Required | Description |
|---|---|---|---|
| dsp_id | integer | Yes | The DSP to generate the report for. Must be 1 (Spotify), 3 (YouTube), or 4 (Apple). |
| date | string | Yes | The date to export in YYYY-MM-DD format. Only a single day can be requested per report. |
Example Request
POST /api/v1/analytics-reports
Authorization: Bearer {token}
Content-Type: application/json
{
"dsp_id": 1,
"date": "2026-03-28"
}
Example Response (201 Created)
{
"data": {
"id": 6,
"dsp_id": 1,
"dsp_name": "Spotify",
"date": "2026-03-28",
"status": "pending",
"total_records": null,
"file_size_bytes": null,
"error": null,
"download_url": null,
"started_at": null,
"completed_at": null,
"created_at": "2026-04-01T16:00:39+00:00"
}
}
Error: Unsupported DSP (422)
{
"error": "DSP not supported for analytics export",
"supported_dsp_ids": [1, 3, 4]
}
Error: Too many active reports (409)
{
"error": "You have reached the maximum of 20 active reports",
"active_count": 20
}
Step 2: Poll Report Status
/analytics-reports/{id}
GET /api/v1/analytics-reports/{id}
Authorization: Bearer {token}
Accept: application/json
Status Values
| Status | Description | Action |
|---|---|---|
| pending | Report is queued and waiting to be processed. | Poll again in 1 minute. |
| processing | Report is currently being generated. | Poll again in 1 minute. |
| completed | Report is ready. download_url is available. |
Download the file from download_url. |
| failed | Report generation failed. error field contains the reason. |
Check the error message and try again. |
Example Response (completed)
{
"data": {
"id": 6,
"dsp_id": 1,
"dsp_name": "Spotify",
"date": "2026-03-28",
"status": "completed",
"total_records": 9938,
"file_size_bytes": 3355443,
"error": null,
"download_url": "https://s3.amazonaws.com/...?X-Amz-Signature=...",
"started_at": "2026-04-01T16:00:41+00:00",
"completed_at": "2026-04-01T16:00:49+00:00",
"created_at": "2026-04-01T16:00:39+00:00"
}
}
Polling Recommendation
We recommend polling every 1 minute. Most reports complete within 5–20 seconds depending on the DSP and dataset size.
Step 3: Download the File
The download_url is a pre-signed S3 URL that does not require authentication. Simply perform a GET request to download the ZIP file.
# No authorization header needed — the URL is pre-signed
curl -o report.zip "https://s3.amazonaws.com/...?X-Amz-Signature=..."
Download URL Expiration
The download URL is valid for 12 hours. After expiration, poll the report again with GET /analytics-reports/{id} to obtain a fresh URL. The file on S3 is not deleted — only the signed URL expires.
File Format
The ZIP file contains a single JSON file named analytics-report-{date}.json. The JSON structure is:
{
"meta": {
"dsp_id": 1,
"dsp_name": "Spotify",
"date": "2026-03-28",
"total_records": 1250,
"generated_at": "2026-03-29T10:15:30+00:00",
"generated_by": 42
},
"data": [
{
"id": 100001,
"data_date": "2026-03-28T00:00:00.000000Z",
"dsp_name": "Spotify",
"track_id": 5012,
"track_name": "Summer Breeze",
"album_id": 1830,
"album_name": "Coastal Dreams",
"spotify_track_id": "4a7b9c2d1e0f3a5b8c6d7e9f",
"isrc": "USRC12345678",
"country_stats": {
"US": {
"streams_count": 142,
"completion_rate": 72.5,
"valid_streams_count": 142,
"track_length_seconds": 215,
"total_reproduction_time": 24.831,
"significant_streams_count": 103,
"avg_reproduction_percentage": 81.2
},
"GB": {
"streams_count": 38,
"completion_rate": 68.4,
"valid_streams_count": 38,
"track_length_seconds": 215,
"total_reproduction_time": 6.107,
"significant_streams_count": 26,
"avg_reproduction_percentage": 74.8
}
},
"demographics": {
"age_groups": {"18-22": 45, "23-27": 62, "28-34": 38, "35-44": 21, "45-59": 14},
"genders": {"female": 88, "male": 79, "": 13},
"products": {"duo-master": 12, "family-sub": 28, "": 140},
"access_levels": {"premium": 124, "free": 56},
"subscription_types": []
},
"aggregated_data": {
"total_streams": 180,
"total_skips": 47,
"total_saves": 0,
"total_shares": 0
},
"content_type_stats": {
"audio_streams": 172,
"video_streams": 8,
"total_streams": 180,
"audio_percentage": 96,
"video_percentage": 4
},
"geographic_stats": {
"regions": {"US-CA": 42, "US-NY": 31, "GB-LON": 22},
"geohashes": {"9q5c": 18, "dr5r": 14, "gcpu": 10},
"connection_countries": {"US": 142, "GB": 38},
"reporting_countries": {"US": 142, "GB": 38},
"total_geolocated_streams": 180
},
"device_platform_stats": {
"device_types": {"cell phone": 112, "computer": 48, "tablet": 20},
"operating_systems": {"iOS": 78, "Android": 54, "Windows": 32, "macOS": 16},
"shuffle_usage": {"enabled": 95, "disabled": 85},
"repeat_usage": {"enabled": 12, "disabled": 168}
},
"source_stats": {
"sources": {"others_playlist": 72, "collection": 45, "radio": 33, "search": 30},
"source_uris": {"spotify:playlist:37i9dQZF1DX...": 42}
},
"engagement_stats": {
"completion_rate": 73.89,
"lyrics_viewed": 8,
"canvas_viewed": 3,
"discovery_streams": 33,
"total_analyzed": 180
},
"timestamp_reliability": {
"online_streams": 165,
"offline_streams": 15,
"total_streams": 180,
"online_percentage": 92,
"offline_percentage": 8
},
"created_at": "2026-03-29T02:15:00.000000Z",
"updated_at": "2026-03-29T02:15:00.000000Z"
},
...
]
}
The data array contains all records for the requested date. Each record is the raw model data as stored in the database, with all nested JSON fields (demographics, geographic stats, etc.) fully expanded.
Spotify Record Fields
| Field | Type | Description |
|---|---|---|
| id | integer | Internal record ID |
| data_date | datetime | Date of the statistics |
| track_id | integer | Limbo track ID |
| track_name | string | Track title |
| album_id | integer | Limbo album ID |
| album_name | string | Album title |
| isrc | string | International Standard Recording Code |
| spotify_track_id | string | Spotify internal track identifier |
| country_stats | object | Per-country breakdown: streams, completion rate, reproduction time |
| demographics | object | Listener demographics: age groups, genders, products, access levels |
| aggregated_data | object | Totals: total_streams, total_skips, total_saves, total_shares |
| content_type_stats | object | Audio vs video stream breakdown |
| geographic_stats | object | Regions, geohashes, connection/reporting countries |
| device_platform_stats | object | Device types, OS, shuffle/repeat usage |
| source_stats | object | Playback sources and source URIs |
| engagement_stats | object | Completion rate, lyrics/canvas viewed, discovery streams |
| timestamp_reliability | object | Online vs offline stream counts |
Limits
- One day per report — each request generates a report for a single date.
- Up to 20 concurrent reports per user — you can request reports for all 3 DSPs in parallel.
- Download URL valid for 12 hours — poll again to get a fresh URL if expired.
- Owner-restricted — you can only see your own reports and your own tracks' data.
Complete Example
Here's a complete workflow requesting all 3 DSPs for the same day:
# 1. Request reports for all 3 DSPs
curl -X POST /api/v1/analytics-reports \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"dsp_id": 1, "date": "2026-03-28"}'
# → {"data": {"id": 6, "status": "pending", ...}}
curl -X POST /api/v1/analytics-reports \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"dsp_id": 4, "date": "2026-03-28"}'
# → {"data": {"id": 7, "status": "pending", ...}}
curl -X POST /api/v1/analytics-reports \
-H "Authorization: Bearer {token}" \
-H "Content-Type: application/json" \
-d '{"dsp_id": 3, "date": "2026-03-28"}'
# → {"data": {"id": 8, "status": "pending", ...}}
# 2. Poll until completed (every 1 minute)
curl /api/v1/analytics-reports/6 -H "Authorization: Bearer {token}"
# → {"data": {"status": "completed", "download_url": "https://...", ...}}
# 3. Download the ZIP (no auth needed)
curl -o spotify-2026-03-28.zip "{download_url}"
curl -o apple-2026-03-28.zip "{download_url}"
curl -o youtube-2026-03-28.zip "{download_url}"