Skip to content

API Endpoints

All endpoints are relative to the base URL http://localhost:8000. Versioned endpoints require authentication via session cookie unless noted otherwise.

Global middleware: All requests pass through rate limiting (100 req/60s), request ID injection, 30s timeout (120s for slow endpoints), GZIP compression, CORS, and security headers.


Health

GET /api/health

No auth required. Returns basic service status.

Response:

{
"status": "healthy",
"service": "echostats-api",
"version": "0.1.0"
}

GET /api/health/ready

No auth required. Checks MongoDB and Redis connectivity.

Response (ready):

{ "status": "ready" }

Response (not ready):

{
"status": "not_ready",
"reason": "database not connected, cache unreachable"
}

GET /api/health/update

No auth required. Checks for new releases on GitHub (cached for 1 hour).

Response:

{
"current_version": "0.1.0",
"latest_version": "0.2.0",
"update_available": true,
"release_url": "https://github.com/spotify-devs/echostats/releases/tag/v0.2.0",
"release_notes": "Bug fixes and performance improvements...",
"published_at": "2025-01-15T12:00:00Z"
}

Authentication

All auth endpoints are prefixed with /api/v1/auth.

GET /api/v1/auth/login

No auth required. Initiates Spotify OAuth flow.

  • Browser (Accept: text/html): Redirects (302) to Spotify authorization page.
  • API client: Returns the authorization URL as JSON.

Response (API client):

{
"url": "https://accounts.spotify.com/authorize?client_id=...",
"state": "abc123"
}

GET /api/v1/auth/callback

No auth required. Handles the Spotify OAuth callback.

ParameterTypeRequiredDescription
codestringyesAuthorization code from Spotify
statestringyesCSRF state token
errorstringnoError returned by Spotify

Success: Redirects to {frontend_url}/auth/callback?status=success and sets an httpOnly session cookie. Creates or updates the user document and stores encrypted Spotify tokens. Triggers a background sync.

Error: Returns 400 (bad request) or 502 (Spotify API error).

GET /api/v1/auth/status

No auth required. Returns current authentication state.

Response (authenticated):

{
"authenticated": true,
"user": {
"id": "682a3b...",
"spotify_id": "atul123",
"display_name": "Atul",
"image_url": "https://i.scdn.co/image/...",
"product": "premium"
},
"token_expires_at": "2025-02-01T12:00:00Z"
}

Response (not authenticated):

{ "authenticated": false }

POST /api/v1/auth/refresh

Refreshes the Spotify access token using the stored refresh token.

Response:

{ "status": "refreshed" }

Error: 401 if not authenticated or token refresh fails.

POST /api/v1/auth/logout

No auth required. Clears the session cookie.

Response:

{ "status": "logged_out" }

GET /api/v1/auth/dev-login

Response:

{
"status": "authenticated",
"user": {
"id": "682a3b...",
"spotify_id": "demo_user",
"display_name": "Demo User"
},
"token": "eyJhbGciOi..."
}

Analytics

All analytics endpoints are prefixed with /api/v1/analytics.

GET /api/v1/analytics/overview

Returns pre-computed analytics for the given period.

ParameterTypeDefaultDescription
periodstringall_timeOne of week, month, quarter, year, all_time

Response:

{
"period": "week",
"total_tracks_played": 319,
"total_ms_played": 64140000,
"total_hours": 17.8,
"unique_tracks": 279,
"unique_artists": 81,
"unique_albums": 45,
"unique_genres": 11,
"listening_streak_days": 5,
"top_artists": [
{ "name": "Radiohead", "play_count": 15, "spotify_id": "4Z8W4fKeB5YxbusRsdQVPb", "image_url": "https://...", "rank": 1 }
],
"top_tracks": [
{ "name": "Everything In Its Right Place", "play_count": 5, "spotify_id": "2kRFrWaLWifQkBFasAWgMo", "rank": 1 }
],
"top_albums": [
{ "name": "Kid A", "play_count": 12, "spotify_id": "6GjwtEZcfenmof6l18N7T7", "rank": 1 }
],
"top_genres": [
{ "name": "art rock", "play_count": 30, "rank": 1 }
],
"hourly_distribution": [
{ "hour": 0, "plays": 5 },
{ "hour": 14, "plays": 42 }
],
"daily_distribution": [
{ "day": "Monday", "plays": 48 }
],
"avg_audio_features": {
"danceability": 0.52,
"energy": 0.61,
"valence": 0.38,
"acousticness": 0.24,
"instrumentalness": 0.15,
"liveness": 0.12,
"speechiness": 0.05,
"tempo": 122.5
},
"computed_at": "2025-01-15T12:00:00Z"
}

POST /api/v1/analytics/refresh

Recomputes analytics for the given period. If period is all_time, all periods are refreshed.

ParameterTypeDefaultDescription
periodstringall_timeOne of week, month, quarter, year, all_time

Response:

{
"status": "refreshed",
"periods": ["week", "month", "quarter", "year", "all_time"],
"computed_at": "2025-01-15T12:05:00Z"
}

GET /api/v1/analytics/trend

Returns listening trend data points over the given period.

ParameterTypeDefaultDescription
periodstringall_timeOne of week, month, quarter, year, all_time

Granularity rules:

  • week / month → daily points (2025-01-15)
  • quarter → weekly points (2025-W03)
  • year / all_time → monthly points (2025-01)

Response:

{
"period": "month",
"granularity": "%Y-%m-%d",
"points": [
{ "label": "2025-01-01", "plays": 42, "ms": 8400000, "hours": 2.3 },
{ "label": "2025-01-02", "plays": 38, "ms": 7600000, "hours": 2.1 }
]
}

GET /api/v1/analytics/rollup-status

Returns the status of daily rollup pre-aggregation.

Response:

{
"last_date": "2025-01-15",
"total_days": 90,
"complete": true,
"coverage": 100.0
}

POST /api/v1/analytics/rollup-build

Triggers a background job to rebuild daily rollups.

Response:

{
"status": "started",
"job_id": "682a3b..."
}

Returns "status": "already_running" if a rollup build is in progress.


History

All history endpoints are prefixed with /api/v1/history.

GET /api/v1/history

Returns paginated listening history with optional filters.

ParameterTypeDefaultDescription
pageint1Page number (min: 1)
limitint50Items per page (1–100)
artiststringFilter by artist name
trackstringFilter by track name
start_datestringISO 8601 start date
end_datestringISO 8601 end date

Response:

{
"items": [
{
"id": "682a3b...",
"user_id": "682a3a...",
"track": {
"spotify_id": "2kRFrWaLWifQkBFasAWgMo",
"name": "Everything In Its Right Place",
"artist_name": "Radiohead",
"album_name": "Kid A",
"album_image_url": "https://i.scdn.co/image/...",
"duration_ms": 250000
},
"played_at": "2025-01-15T14:32:00Z",
"ms_played": 245000,
"source": "api",
"context_type": "album",
"context_uri": "spotify:album:6GjwtEZcfenmof6l18N7T7"
}
],
"total": 1520,
"page": 1,
"limit": 50,
"pages": 31
}

POST /api/v1/history/import

Imports listening history from a Spotify privacy data export (JSON file).

Request: Multipart file upload.

FieldTypeDescription
fileUploadFileJSON file from Spotify extended streaming history export

Response:

{
"job_id": "682a3b...",
"status": "pending",
"items_processed": 0,
"items_total": 12450
}

The import runs as a background job. Use the Sync Jobs endpoints to track progress.


Tracks

All track endpoints are prefixed with /api/v1/tracks.

GET /api/v1/tracks/top

Returns the user’s top tracks by play count for the given period.

ParameterTypeDefaultDescription
periodstringall_timeOne of week, month, quarter, year, all_time
limitint50Number of tracks (1–100)

Response:

{
"items": [
{
"id": "682a3b...",
"spotify_id": "2kRFrWaLWifQkBFasAWgMo",
"name": "Everything In Its Right Place",
"artists": [{ "spotify_id": "4Z8W4fKeB5YxbusRsdQVPb", "name": "Radiohead" }],
"album": {
"spotify_id": "6GjwtEZcfenmof6l18N7T7",
"name": "Kid A",
"image_url": "https://i.scdn.co/image/..."
},
"duration_ms": 250000,
"popularity": 72,
"external_url": "https://open.spotify.com/track/2kRFrWaLWifQkBFasAWgMo",
"audio_features": {
"danceability": 0.45,
"energy": 0.63,
"valence": 0.22,
"tempo": 156.1
}
}
],
"period": "all_time",
"total": 279
}

GET /api/v1/tracks/{track_id}

No auth required. Returns a single track by Spotify ID.

ParameterTypeDescription
track_idstringSpotify track ID

Response:

{
"id": "682a3b...",
"spotify_id": "2kRFrWaLWifQkBFasAWgMo",
"name": "Everything In Its Right Place",
"artists": [{ "spotify_id": "4Z8W4fKeB5YxbusRsdQVPb", "name": "Radiohead" }],
"album": {
"spotify_id": "6GjwtEZcfenmof6l18N7T7",
"name": "Kid A",
"image_url": "https://i.scdn.co/image/...",
"release_date": "2000-10-02"
},
"duration_ms": 250000,
"popularity": 72,
"explicit": false,
"external_url": "https://open.spotify.com/track/2kRFrWaLWifQkBFasAWgMo",
"audio_features": {
"danceability": 0.45,
"energy": 0.63,
"valence": 0.22,
"acousticness": 0.04,
"instrumentalness": 0.73,
"liveness": 0.11,
"speechiness": 0.04,
"tempo": 156.1,
"key": 4,
"mode": 1,
"time_signature": 4
}
}

Error: 404 if the track is not found.


Artists

All artist endpoints are prefixed with /api/v1/artists.

GET /api/v1/artists/top

Returns the user’s top artists by play count for the given period.

ParameterTypeDefaultDescription
periodstringall_timeOne of week, month, quarter, year, all_time
limitint50Number of artists (1–100)

Response:

{
"items": [
{
"id": "682a3b...",
"spotify_id": "4Z8W4fKeB5YxbusRsdQVPb",
"name": "Radiohead",
"image_url": "https://i.scdn.co/image/...",
"genres": ["art rock", "alternative rock", "experimental"],
"popularity": 78,
"followers": 8500000,
"external_url": "https://open.spotify.com/artist/4Z8W4fKeB5YxbusRsdQVPb"
}
],
"period": "all_time",
"total": 81
}

GET /api/v1/artists/{artist_id}

No auth required. Returns a single artist by Spotify ID.

ParameterTypeDescription
artist_idstringSpotify artist ID

Response:

{
"id": "682a3b...",
"spotify_id": "4Z8W4fKeB5YxbusRsdQVPb",
"name": "Radiohead",
"image_url": "https://i.scdn.co/image/...",
"genres": ["art rock", "alternative rock", "experimental"],
"popularity": 78,
"followers": 8500000,
"external_url": "https://open.spotify.com/artist/4Z8W4fKeB5YxbusRsdQVPb"
}

Error: 404 if the artist is not found.


Albums

All album endpoints are prefixed with /api/v1/albums.

GET /api/v1/albums/top

Returns the user’s top albums by play count for the given period.

ParameterTypeDefaultDescription
periodstringall_timeOne of week, month, quarter, year, all_time
limitint50Number of albums (1–100)

Response:

{
"items": [
{
"id": "682a3b...",
"spotify_id": "6GjwtEZcfenmof6l18N7T7",
"name": "Kid A",
"artists": ["Radiohead"],
"image_url": "https://i.scdn.co/image/...",
"release_date": "2000-10-02",
"total_tracks": 10,
"type": "album",
"external_url": "https://open.spotify.com/album/6GjwtEZcfenmof6l18N7T7"
}
],
"period": "all_time",
"total": 45
}

Genres

All genre endpoints are prefixed with /api/v1/genres.

GET /api/v1/genres/distribution

Returns genre distribution for the user’s listening history.

ParameterTypeDefaultDescription
periodstringall_timeOne of week, month, quarter, year, all_time

Response:

{
"genres": [
{ "name": "art rock", "play_count": 120, "percentage": 18.5 },
{ "name": "alternative rock", "play_count": 95, "percentage": 14.6 },
{ "name": "indie rock", "play_count": 72, "percentage": 11.1 }
],
"period": "all_time",
"total_genres": 11
}

Playlists

All playlist endpoints are prefixed with /api/v1/playlists.

GET /api/v1/playlists

Returns the user’s synced playlists with pagination.

ParameterTypeDefaultDescription
pageint1Page number (min: 1)
limitint50Items per page (1–100)

Response:

{
"items": [
{
"id": "682a3b...",
"user_id": "682a3a...",
"spotify_id": "37i9dQZF1DXcBWIGoYBM5M",
"name": "Today's Top Hits",
"description": "The hottest tracks right now.",
"image_url": "https://i.scdn.co/image/...",
"tracks_total": 50,
"owner": "Spotify",
"public": true,
"collaborative": false,
"external_url": "https://open.spotify.com/playlist/37i9dQZF1DXcBWIGoYBM5M"
}
],
"total": 24,
"page": 1,
"limit": 50
}

Player

All player endpoints are prefixed with /api/v1/player. These endpoints proxy directly to the Spotify Web API and require a valid Spotify access token.

GET /api/v1/player/current

Returns the currently playing track.

Response (playing):

{
"is_playing": true,
"track_name": "Everything In Its Right Place",
"artist_name": "Radiohead",
"album_name": "Kid A",
"album_image": "https://i.scdn.co/image/...",
"track_id": "2kRFrWaLWifQkBFasAWgMo",
"duration_ms": 250000,
"progress_ms": 120000,
"device": "MacBook Pro",
"shuffle": false,
"repeat": "off"
}

Response (not playing):

{ "is_playing": false }

GET /api/v1/player/devices

Returns available Spotify Connect devices.

Response:

{
"devices": [
{
"id": "abc123",
"name": "MacBook Pro",
"type": "Computer",
"is_active": true,
"volume_percent": 65
}
]
}

GET /api/v1/player/queue

Returns the current playback queue (up to 20 items).

Response:

{
"currently_playing": {
"id": "2kRFrWaLWifQkBFasAWgMo",
"name": "Everything In Its Right Place",
"artist": "Radiohead",
"album": "Kid A",
"image": "https://i.scdn.co/image/...",
"duration_ms": 250000
},
"queue": [
{
"id": "6LgJvl0Xdtc73RJ1mZoUnj",
"name": "Kid A",
"artist": "Radiohead",
"album": "Kid A",
"image": "https://i.scdn.co/image/...",
"duration_ms": 280000
}
]
}

POST /api/v1/player/play

Start or resume playback.

Request body (all fields optional):

{
"context_uri": "spotify:album:6GjwtEZcfenmof6l18N7T7",
"uris": ["spotify:track:2kRFrWaLWifQkBFasAWgMo"],
"device_id": "abc123"
}

Response:

{ "status": "playing" }

POST /api/v1/player/pause

Pause playback.

Response:

{ "status": "paused" }

POST /api/v1/player/next

Skip to next track.

Response:

{ "status": "skipped" }

POST /api/v1/player/previous

Skip to previous track.

Response:

{ "status": "skipped_back" }

Browse

All browse endpoints are prefixed with /api/v1/browse. These proxy to the Spotify Web API.

GET /api/v1/browse/new-releases

Returns new album releases from Spotify.

ParameterTypeDefaultDescription
limitint20Number of results

Response:

{
"items": [
{
"id": "6GjwtEZcfenmof6l18N7T7",
"name": "Kid A Mnesia",
"artists": "Radiohead",
"image": "https://i.scdn.co/image/...",
"release_date": "2021-11-05",
"total_tracks": 34,
"type": "compilation"
}
]
}

GET /api/v1/browse/featured-playlists

Returns Spotify’s featured playlists.

ParameterTypeDefaultDescription
limitint20Number of results

Response:

{
"message": "Good afternoon",
"items": [
{
"id": "37i9dQZF1DXcBWIGoYBM5M",
"name": "Today's Top Hits",
"description": "The hottest tracks right now.",
"image": "https://i.scdn.co/image/...",
"tracks_total": 50,
"owner": "Spotify"
}
]
}

GET /api/v1/browse/categories

Returns Spotify browse categories.

ParameterTypeDefaultDescription
limitint50Number of results

Response:

{
"items": [
{
"id": "toplists",
"name": "Top Lists",
"image": "https://t.scdn.co/media/..."
}
]
}

Library

All library endpoints are prefixed with /api/v1/library. These proxy to the Spotify Web API.

GET /api/v1/library/followed-artists

Returns artists the user follows on Spotify.

ParameterTypeDefaultDescription
limitint50Number of artists (1–50)

Response:

{
"items": [
{
"id": "4Z8W4fKeB5YxbusRsdQVPb",
"name": "Radiohead",
"image": "https://i.scdn.co/image/...",
"followers": 8500000,
"genres": ["art rock", "alternative rock"]
}
],
"total": 142
}

GET /api/v1/library/saved-albums

Returns albums saved to the user’s Spotify library.

ParameterTypeDefaultDescription
limitint50Number of albums (1–50)
offsetint0Pagination offset

Response:

{
"items": [
{
"id": "6GjwtEZcfenmof6l18N7T7",
"name": "Kid A",
"artists": "Radiohead",
"image": "https://i.scdn.co/image/...",
"release_date": "2000-10-02",
"total_tracks": 10,
"added_at": "2024-06-15T10:30:00Z"
}
],
"total": 85,
"limit": 50,
"offset": 0
}

Recommendations

GET /api/v1/recommendations

Returns track recommendations based on the user’s listening analytics.

ParameterTypeDefaultDescription
limitint20Number of recommendations (1–50)
seed_typestringmixedOne of artists, tracks, genres, mixed

Seeds are derived from the user’s analytics snapshot (top artists, tracks, and genres).

Response:

{
"items": [
{
"id": "3EYHN9PZiFaz5tEspCPPDr",
"name": "Paranoid Android",
"artists": ["Radiohead"],
"artist": "Radiohead",
"album": "OK Computer",
"album_image": "https://i.scdn.co/image/...",
"duration_ms": 383000,
"popularity": 75,
"audio_features": {
"danceability": 0.38,
"energy": 0.72,
"valence": 0.19
},
"external_url": "https://open.spotify.com/track/3EYHN9PZiFaz5tEspCPPDr"
}
],
"total": 20,
"seed_info": {
"type": "mixed",
"top_artist": "Radiohead",
"top_genre": "art rock"
}
}

Returns {"items": [], "seed_info": {}} if no analytics snapshot is available.


Sync Jobs

All sync job endpoints are prefixed with /api/v1/sync-jobs.

GET /api/v1/sync-jobs

Returns paginated list of sync jobs.

ParameterTypeDefaultDescription
pageint1Page number (min: 1)
limitint20Items per page (1–100)
statusstringFilter: pending, running, completed, failed
job_typestringFilter: periodic, rollup_build, import, initial

Response:

{
"items": [
{
"id": "682a3b...",
"job_type": "periodic",
"status": "completed",
"items_processed": 50,
"items_total": 50,
"error_message": null,
"started_at": "2025-01-15T14:00:00Z",
"completed_at": "2025-01-15T14:00:12Z",
"created_at": "2025-01-15T14:00:00Z",
"steps": [
{
"action": "recently_played",
"status": "completed",
"detail": "Fetched 50 recent tracks",
"items": 50,
"error": null
},
{
"action": "audio_features",
"status": "completed",
"detail": "Updated audio features for 12 tracks",
"items": 12,
"error": null
}
]
}
],
"total": 145,
"page": 1,
"pages": 8
}

GET /api/v1/sync-jobs/stats

Returns aggregate sync job statistics.

Response:

{
"total_jobs": 145,
"completed": 140,
"failed": 3,
"running": 1,
"total_items_synced": 15200,
"last_sync_at": "2025-01-15T14:00:12Z"
}

POST /api/v1/sync-jobs/trigger

Manually triggers a data sync. Runs: recently played → audio features → playlists → top items → analytics refresh.

Response:

{
"status": "started",
"job_id": "682a3b...",
"message": "Sync job started"
}

Returns "status": "already_running" if a sync is in progress.


API Logs

All API log endpoints are prefixed with /api/v1/api-logs. These track outbound calls to the Spotify API.

GET /api/v1/api-logs

Returns paginated API call logs.

ParameterTypeDefaultDescription
pageint1Page number (min: 1)
limitint50Items per page (1–200)
servicestringFilter by service (e.g. spotify)
status_minintMinimum HTTP status code
status_maxintMaximum HTTP status code
methodstringHTTP method (GET, POST, etc.)
endpoint_containsstringFilter by endpoint substring

Response:

{
"items": [
{
"id": "682a3b...",
"service": "spotify",
"method": "GET",
"endpoint": "/v1/me/player/recently-played",
"status_code": 200,
"latency_ms": 142.5,
"error": null,
"timestamp": "2025-01-15T14:00:01Z"
}
],
"total": 3200,
"page": 1,
"limit": 50,
"pages": 64
}

GET /api/v1/api-logs/stats

Returns aggregate API call statistics.

Response:

{
"total_calls": 3200,
"error_count": 15,
"rate_limit_count": 2,
"success_rate": 99.5,
"avg_latency_ms": 185.3,
"status_distribution": {
"2xx": 3180,
"4xx": 12,
"5xx": 8
},
"top_endpoints": [
{ "endpoint": "GET /v1/me/player/recently-played", "count": 1450 },
{ "endpoint": "GET /v1/audio-features", "count": 820 }
]
}

Error Responses

All endpoints use consistent error shapes.

Validation Error (422)

{
"detail": [
{
"loc": ["query", "limit"],
"msg": "ensure this value is less than or equal to 100",
"type": "value_error"
}
]
}

Authentication Error (401)

{ "detail": "Not authenticated" }

Not Found (404)

{ "detail": "Track not found" }

Service Unavailable (503)

Returned when MongoDB or Redis is unreachable. Includes a Retry-After header.

{ "detail": "Service temporarily unavailable" }

Internal Server Error (500)

{ "detail": "Internal server error" }