https://api.recoupable.dev.
Private calls use x-api-key: $RECOUP_API_KEY (or the platform’s existing bearer authentication). Customer credentials stay with the agent/server. Public connection entry and callback use browser-bound OAuth state, not the customer API key.
Customer and site
GET /api/accounts/id: verify the current credential.POST /api/agents/signupwith{ "email": "customer@example.com" }: request email verification.POST /api/agents/verifywith{ "email": "customer@example.com", "code": "123456" }: receiveapi_key; never echo it.GET /api/artists: inspect the caller’s roster and choose the artist’s account ID.GET /api/sites: inspect existing site records.POST /api/siteswith{ "name": "Release experience", "artistId": "ARTIST_ACCOUNT_UUID", "releaseUrl": "https://open.spotify.com/track/TRACK_ID" }: register a site. AddorganizationIdonly for a selected authorized workspace. Read the returnedsite.id. Omit owner/account ID fields; ownership comes from authentication.
Configure
GET /api/sites/{siteId}/fan-connection returns:
config contains site_id, return_url, marketing_text, enabled and revision. Update with PUT /api/sites/{siteId}/fan-connection:
connectUrl; always use that returned URL.
- 400: invalid input or missing artist attribution.
- 401/403: reconnect the customer or select an authorized workspace.
- 402: no eligible paid subscription for the site’s owning account.
- 404: missing site or API not deployed; inspect the actual response.
- 409: another configuration update won; reload and reconcile before retry.
- 503: provider, billing or storage unavailable; surface the dependency.
POST /api/subscriptions/sessions accepts {plan: "starter" | "pro", successUrl, cancelUrl?} and returns the customer checkout URL. Let the customer complete payment; then retry activation. Fans do not buy this feature.
Public fan journey
GET /api/sites/public/{siteId}/spotify displays the site’s agreement. Its own form POST binds acceptance to that browser and configuration revision, then redirects to Spotify PKCE authorization. Do not handcraft this POST or bypass the page. Spotify returns to Recoup’s /api/sites/spotify/callback; Recoup then returns to the configured site URL with recoup_spotify=connected, cancelled or failed.
Only a successful provider exchange with user-read-email and user-read-private creates records. Actual granted scopes and the exact accepted marketing text are retained separately from the successful connection. Email may be absent. No tokens or profile details are passed to the public site.
Read fans
GET /api/sites/{siteId}/fans?offset=0&limit=50 (private):
id, site_id, spotify_id, display_name, email, first/last connection timestamps and nested site_fan_connections with site_fan_permissions and site_fan_marketing_consents. Paginate up to 100 fans per page. Records are private to authorized workspace members. Read access remains available after paid eligibility expires. Repeated connection updates the existing fan for that site and adds a connection history entry. The site’s owner and artist supply attribution. These records are separate from email-only signups and other artist audience data.
Recoup operator setup
Operators must apply20260928010000_site_fan_connections.sql, configure SITES_SPOTIFY_CLIENT_ID, register an HTTPS callback ending /api/sites/spotify/callback, set SITES_FAN_SPOTIFY_REDIRECT_URI. This is Recoup service configuration, not work to impose on each artist. The existing browser playback callback remains separate.
Activity reporting
GET /api/sites/{siteId}/activity returns 30-day counts for visits, starts, completions, replays and shares to authorized workspace members. POST /api/sites/public/{siteId}/activity accepts {id: EVENT_UUID, visitId: VISIT_UUID, event: "visit" | "start" | "complete" | "replay" | "share"}. Reuse the event UUID when retrying. Use a site-scoped visit ID. Never send emails, profile data or arbitrary page contents to this endpoint. These are browser-reported interactions, not verified Spotify listening or a named fan activity history.
Recoup-hosted generated sites use the trusted host bridge: window.recoup.track(event) reports approved events; window.recoup.join() reveals the trusted signup controls. External sites should implement the equivalent fixed-event transport and keep account credentials on the server.
Playback for Premium and Free listeners
The hosted player reads the connected Spotify profile’sproduct field. Premium listeners use the Spotify Web Playback SDK. Free (free or open) listeners use the site’s saved, verified recording through the same play/pause, seek and volume controls. If the profile cannot be read, its subscription remains unknown; available artist audio keeps playback usable. The player also handles a Spotify SDK account rejection by switching to the recording.
GET /api/sites/public/{siteId} includes playbackAudioUrl, a fresh signed audio URL valid for one hour, or null when no matching verified recording is available. It is resolved from the published site’s workspace-owned context request and matching Spotify track; private research and analysis are excluded. Fetch a fresh public response when reloading rather than storing this URL permanently. Publish only audio you have permission to serve to fans.
File playback is not a Spotify stream. Spotify playback subscription, Recoup’s paid fan-connection entitlement, and fan capture are separate concerns. A missing audio file must show an honest unavailable state while keeping the game playable.