Authentication

StreamerSonglist provides an OAuth based authentication mechanism adhering to parts of the OAuth2.0 protocol

Tokens

TypeDescription
User access tokensAuthenticate users and allow your app to make requests on their behalf. If your application uses StreamerSonglist for login or makes requests in the context of an authenticated user, you need to generate a user access token.
App access tokensApp access tokens are meant only for server-to-server API requests and should never be included in client code.
Refresh tokensLong-lived tokens exchanged for a new access token when the original expires. Issued only when the offline_access scope is requested. Refresh tokens rotate on each exchange — the previous refresh token is invalidated.
ID tokensSigned JWTs identifying the authenticated user. Issued when the openid scope is requested. Verify signatures against the JWKS endpoint at /.well-known/jwks.json.

Clients

TypeDescriptionToken TypeFlow(s) Supported
OAuthYou need to use the authorization code flow to obtain access to a user's data based on scopes provided. Currently the app access token retrieved with the client credentials flow does not provide any additional non-public data for a user or streamerUser access token, App access tokenAuthorization code, client credentials
UserYou want to create an application based on your own access to data. This provides identical authorization to the token used when using the streamersonglist.com website.App access tokenClient credentials
StreamerYou want to create an application that can access all data to a specific streamerApp access tokenClient credentials

Scopes

Scopes use a 3-segment dot-separated format: category.resource.permission

For example, streamer.song.read grants Read access to the Songs resource in the streamer category.

Permissions

Each resource supports two permissions:

CodePermission
readRead
writeWrite

Available Resources

ResourceLabelWildcard Scope
streamer.action-logActivity Logstreamer.action-log.*
streamer.attributeAttributesstreamer.attribute.*
streamer.commandCommandsstreamer.command.*
streamer.integrationIntegrationsstreamer.integration.*
streamer.overlayOverlaysstreamer.overlay.*
streamer.play-historyPlay Historystreamer.play-history.*
streamer.queueQueuestreamer.queue.*
streamer.settingSettingsstreamer.setting.*
streamer.songSongsstreamer.song.*
user.preferencePreferencesuser.preference.*
user.song-requestSong Requestsuser.song-request.*
user.favoriteFavorite Songsuser.favorite.*

Wildcard Matching

You can request all permissions for a resource using the wildcard * as the permission segment.

For example, streamer.song.* grants both read and write access to songs.

Wildcard matching follows fosite's WildcardScopeStrategy. A * in any segment matches any value in that position:

  • streamer.song.* matches streamer.song.read and streamer.song.write
  • streamer.song.read matches only streamer.song.read

When requesting scopes for your application, use the wildcard form (resource.*) to request full access or individual permission names for fine-grained control.

Getting Tokens

The domain dedicated to authentication is https://id.staging.streamersonglist.com/oauth2

OpenID Connect discovery metadata is available at https://id.staging.streamersonglist.com/oauth2/.well-known/openid-configuration and the signing keys for ID tokens are published at https://id.staging.streamersonglist.com/oauth2/.well-known/jwks.json.

Supported authentication flows:

Flow TypeDescriptionToken Type
Authorization codeA user authenticates in the browser and your server exchanges the returned code for tokens. Supports PKCE.User access token, refresh token, ID token
Refresh tokenExchange a refresh token (issued alongside an access token when offline_access was granted) for a fresh token.User access token, refresh token
Client credentialsServer-to-server flow authenticating the client itself, not a user.App access token

Client authentication at the token endpoint uses either client_secret_post (credentials in the request body — the default for clients created in this dashboard) or client_secret_basic (credentials in an HTTP Basic auth header). Both are accepted.

Authorization Code Flow

GET /oauth2/auth

ParameterTypeDescription
client_idstring (required)id generated from registered client
redirect_uriURI (required)callback uri specified when registering the client
response_typestring (required)Only allowed value is code
scopestring (required)space separated list of scopes. Include offline_access to receive a refresh token. Include openid to receive an ID token.
statestring (recommended)Your unique token, generated by your application. An OAuth 2.0 opaque value used to defeat CSRF attacks. This value is echoed back in the response.
code_challengestring (recommended)PKCE challenge derived from a verifier your app generates. Required for public clients; strongly recommended for confidential clients.
code_challenge_methodstring (recommended)One of S256 or plain. S256 (SHA-256) is required whenever the user agent can compute it.
noncestring (optional)Random value generated by your app. When openid is requested, the same value is returned in the ID token's nonce claim for replay protection.
curl "https://id.streamersonglist.com/oauth2/auth\
    ?client_id=<your_registered_client_id>\
    &redirect_uri=<your_registered_redirect_uri>\
    &response_type=code\
    &state=bQxc3kvjuzTmwX4JE9rb7HvkvoUTG6\
    &code_challenge=<base64url(sha256(verifier))>\
    &code_challenge_method=S256\
    &scope=offline_access streamer.song.* streamer.queue.*"
/* your server generates the PKCE pair and stores the verifier in the user's session */
import { createHash, randomBytes } from 'node:crypto';

const base64Url = (buf: Buffer) =>
  buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');

const verifier = base64Url(randomBytes(32));
const challenge = base64Url(createHash('sha256').update(verifier).digest());

const params = new URLSearchParams({
  client_id: '<your_registered_client_id>',
  redirect_uri: '<your_registered_redirect_uri>',
  response_type: 'code',
  state: 'bQxc3kvjuzTmwX4JE9rb7HvkvoUTG6',
  code_challenge: challenge,
  code_challenge_method: 'S256',
  scope: 'offline_access streamer.song.* streamer.queue.*',
});

// Redirect the user's browser to this URL:
const authorizeUrl = `https://id.streamersonglist.com/oauth2/auth?${params.toString()}`;

Redirect the user agent to the URL above. StreamerSonglist responds with a 302 to your redirect_uri carrying code and state query parameters. Verify state matches the value you sent, then exchange the code for tokens.

/* your server */
app.get('auth/callback', (req, res) => {
  const code = req.query.code;
});

Exchange for Token

POST /oauth2/token with Content-Type: application/x-www-form-urlencoded

ParameterTypeDescription
grant_typestring (required)Must be authorization_code
codestring (required)code value returned from the /oauth2/auth callback
redirect_uriURI (required)Must match the redirect_uri used in the authorize request
client_idstring (required)id of the registered client. May be omitted here when credentials are sent via HTTP Basic auth (client_secret_basic).
client_secretstring (required)Client secret. May be omitted here when sent via HTTP Basic auth.
code_verifierstring (recommended)The PKCE verifier matching the code_challenge sent in the authorize request. Required if you included code_challenge.
curl -X POST "https://id.streamersonglist.com/oauth2/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=authorization_code" \
    -d "code=<code_from_callback_query_param>" \
    -d "redirect_uri=<your_registered_callback_uri>" \
    -d "client_id=<your_client_id>" \
    -d "client_secret=<your_client_secret>" \
    -d "code_verifier=<pkce_verifier_from_session>"
const response = await fetch('https://id.streamersonglist.com/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: '<code from previous authorize callback>',
    redirect_uri: `<your registered client's redirect uri>`,
    client_id: `<your registered client's id>`,
    client_secret: `<your registered client's secret>`,
    code_verifier: `<pkce verifier from session>`,
  }),
});

const data = await response.json();
const accessToken = data.access_token;
const refreshToken = data.refresh_token; // present when offline_access was granted
const idToken = data.id_token; // present when openid was granted

Example response when offline_access openid streamer.song.* streamer.queue.* was granted:

{
  "access_token": "eyJhbG...adQssw5c",
  "refresh_token": "eyJhbG...gdJpx3wCc",
  "id_token": "eyJhbG...signed.JWT",
  "expires_in": 3600,
  "scope": "offline_access openid streamer.song.* streamer.queue.*",
  "token_type": "bearer"
}

Refresh Token Flow

When your access token expires, exchange the refresh token issued alongside it (requires the original request to have included the offline_access scope) for a new access token.

POST /oauth2/token with Content-Type: application/x-www-form-urlencoded

ParameterTypeDescription
grant_typestring (required)Must be refresh_token
refresh_tokenstring (required)The refresh token returned from a previous token response
client_idstring (required)id of the registered client. May be omitted here when credentials are sent via HTTP Basic auth (client_secret_basic).
client_secretstring (required)Client secret. May be omitted here when sent via HTTP Basic auth.
scopestring (optional)Narrower subset of the originally granted scopes. Omit to receive all previously granted scopes.
curl -X POST "https://id.streamersonglist.com/oauth2/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=refresh_token" \
    -d "refresh_token=<your_refresh_token>" \
    -d "client_id=<your_client_id>" \
    -d "client_secret=<your_client_secret>"

Each successful refresh rotates the refresh token: the previous refresh token is invalidated and the response contains a new one. Persist the new refresh_token on every exchange.

{
  "access_token": "eyJhbG...new-access",
  "refresh_token": "eyJhbG...new-refresh",
  "expires_in": 3600,
  "scope": "offline_access openid streamer.song.* streamer.queue.*",
  "token_type": "bearer"
}

Client Credentials Flow

POST /oauth2/token with Content-Type: application/x-www-form-urlencoded

ParameterTypeDescription
grant_typestring (required)Must be client_credentials
scopestring (required)Space-separated list of scopes. Cannot include user-context scopes — client credentials tokens have no user subject.
client_idstring (required)id of the registered client. May be omitted here when credentials are sent via HTTP Basic auth (client_secret_basic).
client_secretstring (required)Client secret. May be omitted here when sent via HTTP Basic auth.
curl -X POST "https://id.streamersonglist.com/oauth2/token" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials" \
    -d "scope=streamer.song.read streamer.queue.read" \
    -d "client_id=<your_client_id>" \
    -d "client_secret=<your_client_secret>"
const response = await fetch('https://id.streamersonglist.com/oauth2/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
  body: new URLSearchParams({
    grant_type: 'client_credentials',
    scope: 'streamer.song.read streamer.queue.read',
    client_id: `<your registered client's id>`,
    client_secret: `<your registered client's secret>`,
  }),
});

const data = await response.json();
const accessToken = data.access_token;

Client credentials tokens have no user subject and no refresh token — request a new token when the current one expires.

{
  "access_token": "eyJhbG...adQssw5c",
  "expires_in": 3600,
  "scope": "streamer.song.read streamer.queue.read",
  "token_type": "bearer"
}

UserInfo

When an access token was issued with the openid scope, you can fetch standard OpenID Connect claims about the authenticated user.

GET /userinfo

curl -H "Authorization: Bearer <your_access_token>" \
  "https://id.streamersonglist.com/userinfo"

The response includes sub (the subject identifier for the user) plus any claims corresponding to scopes granted to the token (for example, email when the email scope was granted).

Revoking Tokens

Invalidate an access token or refresh token before it expires — for example, when the user signs out or removes your app.

POST /oauth2/revoke with Content-Type: application/x-www-form-urlencoded

ParameterTypeDescription
tokenstring (required)The access token or refresh token to revoke
client_idstring (required)id of the registered client. May be omitted here when credentials are sent via HTTP Basic auth (client_secret_basic).
client_secretstring (required)Client secret. May be omitted here when sent via HTTP Basic auth.
curl -X POST "https://id.streamersonglist.com/oauth2/revoke" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "token=<access_or_refresh_token>" \
    -d "client_id=<your_client_id>" \
    -d "client_secret=<your_client_secret>"

Revoking a refresh token also invalidates the access tokens issued from it.

Streamer Access Tokens

Streamer access tokens are simple, database-backed tokens that grant full access to a specific streamer's data. Unlike OAuth tokens, they do not use scopes — a valid streamer token has full read and write access to that streamer's resources.

Streamer tokens are ideal when you are building a personal integration for your own channel and don't need the complexity of the OAuth flow.

You can create and manage streamer access tokens from your Settings > Access page on streamersonglist.com.

Authorization Header

Include the token in the Authorization header with the Streamer prefix:

Authorization: Streamer <token>

Examples

curl -H "Authorization: Streamer <your_token>" \
  "https://api.streamersonglist.com/v2/streamers/<streamer_id>/songs"
const response = await fetch('https://api.streamersonglist.com/v2/streamers/<streamer_id>/songs', {
  headers: {
    Authorization: 'Streamer <your_token>',
  },
});

const data = await response.json();

Differences from OAuth Tokens

FeatureStreamer Access TokenOAuth Token
SetupCreate from settings pageRegister an OAuth client
ScopesFull access (no scopes)Fine-grained scopes
Token formatAuthorization: Streamer <tok>Authorization: Bearer <tok>
Best forPersonal / single-streamer useThird-party apps, multi-user apps