[PR #720] fix: Use separate Client ID for Web API and switch CDN to spclient to avoid 429 rate-limiting #679

Open
opened 2026-02-28 14:33:51 +03:00 by kerem · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/jpochyla/psst/pull/720
Author: @grenaad
Created: 2/21/2026
Status: 🔄 Open

Base: mainHead: feat/separate-webapi-client-id


📝 Commits (4)

  • 95816fc fix: use separate Client ID for Web API calls to avoid 429 rate-limiting
  • e1fd568 fixed
  • 5662435 review update
  • ad11cfd review update

📊 Changes

10 files changed (+494 additions, -71 deletions)

View changed files

📝 psst-core/src/cdn.rs (+53 -23)
📝 psst-core/src/connection/mod.rs (+25 -0)
📝 psst-core/src/oauth.rs (+140 -16)
📝 psst-core/src/session/access_token.rs (+22 -1)
📝 psst-core/src/session/client_token.rs (+9 -2)
📝 psst-core/src/session/login5.rs (+6 -6)
📝 psst-gui/src/data/config.rs (+53 -0)
📝 psst-gui/src/main.rs (+25 -1)
📝 psst-gui/src/ui/preferences.rs (+124 -12)
📝 psst-gui/src/webapi/client.rs (+37 -10)

📄 Description

Disclosure

All code is generated with Opencode with Opus model. I tested this with my own client id, and auth is working. I have not used Rust in a few years, so I can't verify that the code quality is up to standard. I am happy to make any changes requested.

Summary

Spotify started returning 429 (Too Many Requests) errors for requests made to api.spotify.com using the official Spotify desktop client ID (65b708073fc0480ea92a077233ca87bd). This affects both the JSON Web API (search, library, playlists) and the storage-resolve endpoint (audio file URL resolution for playback).

This PR fixes both issues by:

  1. Adding a separate OAuth2 PKCE flow for Web API calls using a user-registered client ID
  2. Switching audio file resolution from api.spotify.com to Spotify's spclient infrastructure (same approach as librespot)

Inspired by ncspot commit 6769b7b.

Setup: Register a Spotify app at https://developer.spotify.com/dashboard with redirect URI http://127.0.0.1:8888/login, then enter the Client ID on the sign-in screen.

Web API authentication

  • Users enter their Spotify Developer Client ID in a text field on the sign-in screen, persisted in config.json
  • During login, a second OAuth2 PKCE flow runs after the Shannon session auth (two browser redirects)
  • WebApiToken (access token, refresh token, expiry) is stored in config.json alongside existing credentials
  • On startup, cached token is checked and refreshed transparently; on logout it is cleared
  • WebApi no longer depends on Login5 or SessionService — it manages its own token via set_webapi_credentials()
  • Web API OAuth failure is non-fatal: playback still works, only API calls (search, library, playlists) are affected

CDN audio resolution (spclient migration)

  • Added Transport::resolve_spclient() to resolve spclient access points via apresolve.spotify.com/?type=spclient
  • Cdn::resolve_audio_file_url() now uses https://{spclient_host}/storage-resolve/... instead of api.spotify.com
  • Response parsing changed from JSON to protobuf (StorageResolveResponse from librespot-protocol)
  • Added client-token header required by spclient endpoint
  • A single Arc<ClientTokenProvider> is shared between Login5 and Cdn to avoid redundant round-trips
  • spclient base URL is resolved once and cached for the Cdn instance lifetime

OAuth code consolidation

  • Deduplicated create_spotify_oauth_client/create_webapi_oauth_client into create_oauth_client(client_id, port)
  • Deduplicated generate_auth_url/generate_webapi_auth_url into a shared parameterized function
  • All Web API OAuth functions accept client_id: &str — no hardcoded constant
  • open and platform-dirs dependencies removed from psst-core; token persistence and browser-opening logic lives in psst-gui

Architecture

[Shannon session / Login5 / CDN audio keys]
  → Official client ID (65b708...)
  → Login5 tokens + shared Arc<ClientTokenProvider>

[CDN storage-resolve (audio file URLs)]
  → spclient host (e.g. gew1-spclient.spotify.com)
  → Login5 bearer token + client-token header
  → Protobuf StorageResolveResponse

[Web API (search, library, playlists)]
  → User-provided client ID (GUI input, persisted in config.json)
  → OAuth2 PKCE token (cached in config.json, auto-refreshed)

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/jpochyla/psst/pull/720 **Author:** [@grenaad](https://github.com/grenaad) **Created:** 2/21/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `feat/separate-webapi-client-id` --- ### 📝 Commits (4) - [`95816fc`](https://github.com/jpochyla/psst/commit/95816fc8f85b30c5baf45e37c9d324a35168c40c) fix: use separate Client ID for Web API calls to avoid 429 rate-limiting - [`e1fd568`](https://github.com/jpochyla/psst/commit/e1fd56843184aceeada543d7f54f3fd1032258fd) fixed - [`5662435`](https://github.com/jpochyla/psst/commit/56624352b3748160027429371975d68962eaf229) review update - [`ad11cfd`](https://github.com/jpochyla/psst/commit/ad11cfd9cfbdf57d1cb6e25c7b712be8728b6c23) review update ### 📊 Changes **10 files changed** (+494 additions, -71 deletions) <details> <summary>View changed files</summary> 📝 `psst-core/src/cdn.rs` (+53 -23) 📝 `psst-core/src/connection/mod.rs` (+25 -0) 📝 `psst-core/src/oauth.rs` (+140 -16) 📝 `psst-core/src/session/access_token.rs` (+22 -1) 📝 `psst-core/src/session/client_token.rs` (+9 -2) 📝 `psst-core/src/session/login5.rs` (+6 -6) 📝 `psst-gui/src/data/config.rs` (+53 -0) 📝 `psst-gui/src/main.rs` (+25 -1) 📝 `psst-gui/src/ui/preferences.rs` (+124 -12) 📝 `psst-gui/src/webapi/client.rs` (+37 -10) </details> ### 📄 Description ## Disclosure All code is generated with Opencode with Opus model. I tested this with my own client id, and auth is working. I have not used Rust in a few years, so I can't verify that the code quality is up to standard. I am happy to make any changes requested. ## Summary Spotify started returning 429 (Too Many Requests) errors for requests made to `api.spotify.com` using the official Spotify desktop client ID (`65b708073fc0480ea92a077233ca87bd`). This affects both the JSON Web API (search, library, playlists) and the `storage-resolve` endpoint (audio file URL resolution for playback). This PR fixes both issues by: 1. Adding a separate OAuth2 PKCE flow for Web API calls using a user-registered client ID 2. Switching audio file resolution from `api.spotify.com` to Spotify's `spclient` infrastructure (same approach as librespot) Inspired by [ncspot commit 6769b7b](https://github.com/hrkfdn/ncspot/commit/6769b7b). **Setup:** Register a Spotify app at https://developer.spotify.com/dashboard with redirect URI `http://127.0.0.1:8888/login`, then enter the Client ID on the sign-in screen. ## Web API authentication - Users enter their Spotify Developer Client ID in a text field on the sign-in screen, persisted in `config.json` - During login, a second OAuth2 PKCE flow runs after the Shannon session auth (two browser redirects) - `WebApiToken` (access token, refresh token, expiry) is stored in `config.json` alongside existing credentials - On startup, cached token is checked and refreshed transparently; on logout it is cleared - `WebApi` no longer depends on `Login5` or `SessionService` — it manages its own token via `set_webapi_credentials()` - Web API OAuth failure is non-fatal: playback still works, only API calls (search, library, playlists) are affected ## CDN audio resolution (spclient migration) - Added `Transport::resolve_spclient()` to resolve spclient access points via `apresolve.spotify.com/?type=spclient` - `Cdn::resolve_audio_file_url()` now uses `https://{spclient_host}/storage-resolve/...` instead of `api.spotify.com` - Response parsing changed from JSON to protobuf (`StorageResolveResponse` from `librespot-protocol`) - Added `client-token` header required by spclient endpoint - A single `Arc<ClientTokenProvider>` is shared between `Login5` and `Cdn` to avoid redundant round-trips - spclient base URL is resolved once and cached for the `Cdn` instance lifetime ## OAuth code consolidation - Deduplicated `create_spotify_oauth_client`/`create_webapi_oauth_client` into `create_oauth_client(client_id, port)` - Deduplicated `generate_auth_url`/`generate_webapi_auth_url` into a shared parameterized function - All Web API OAuth functions accept `client_id: &str` — no hardcoded constant - `open` and `platform-dirs` dependencies removed from `psst-core`; token persistence and browser-opening logic lives in `psst-gui` ## Architecture ``` [Shannon session / Login5 / CDN audio keys] → Official client ID (65b708...) → Login5 tokens + shared Arc<ClientTokenProvider> [CDN storage-resolve (audio file URLs)] → spclient host (e.g. gew1-spclient.spotify.com) → Login5 bearer token + client-token header → Protobuf StorageResolveResponse [Web API (search, library, playlists)] → User-provided client ID (GUI input, persisted in config.json) → OAuth2 PKCE token (cached in config.json, auto-refreshed) ``` --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/psst#679
No description provided.