Authentication
StreamerSonglist provides an OAuth based authentication mechanism adhering to parts of the OAuth2.0 protocol
Tokens
| Type | Description |
|---|---|
| User access tokens | Authenticate 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 tokens | App access tokens are meant only for server-to-server API requests and should never be included in client code. |
| Refresh tokens | Long-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 tokens | Signed JWTs identifying the authenticated user. Issued when the openid scope is requested. Verify signatures against the JWKS endpoint at /.well-known/jwks.json. |
Clients
| Type | Description | Token Type | Flow(s) Supported |
|---|---|---|---|
| OAuth | You 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 streamer | User access token, App access token | Authorization code, client credentials |
| User | You 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 token | Client credentials |
| Streamer | You want to create an application that can access all data to a specific streamer | App access token | Client 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:
| Code | Permission |
|---|---|
read | Read |
write | Write |
Available Resources
| Resource | Label | Wildcard Scope |
|---|---|---|
streamer.action-log | Activity Log | streamer.action-log.* |
streamer.attribute | Attributes | streamer.attribute.* |
streamer.command | Commands | streamer.command.* |
streamer.integration | Integrations | streamer.integration.* |
streamer.overlay | Overlays | streamer.overlay.* |
streamer.play-history | Play History | streamer.play-history.* |
streamer.queue | Queue | streamer.queue.* |
streamer.setting | Settings | streamer.setting.* |
streamer.song | Songs | streamer.song.* |
user.preference | Preferences | user.preference.* |
user.song-request | Song Requests | user.song-request.* |
user.favorite | Favorite Songs | user.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.*matchesstreamer.song.readandstreamer.song.writestreamer.song.readmatches onlystreamer.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 Type | Description | Token Type |
|---|---|---|
| Authorization code | A user authenticates in the browser and your server exchanges the returned code for tokens. Supports PKCE. | User access token, refresh token, ID token |
| Refresh token | Exchange a refresh token (issued alongside an access token when offline_access was granted) for a fresh token. | User access token, refresh token |
| Client credentials | Server-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
| Parameter | Type | Description |
|---|---|---|
| client_id | string (required) | id generated from registered client |
| redirect_uri | URI (required) | callback uri specified when registering the client |
| response_type | string (required) | Only allowed value is code |
| scope | string (required) | space separated list of scopes. Include offline_access to receive a refresh token. Include openid to receive an ID token. |
| state | string (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_challenge | string (recommended) | PKCE challenge derived from a verifier your app generates. Required for public clients; strongly recommended for confidential clients. |
| code_challenge_method | string (recommended) | One of S256 or plain. S256 (SHA-256) is required whenever the user agent can compute it. |
| nonce | string (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
| Parameter | Type | Description |
|---|---|---|
| grant_type | string (required) | Must be authorization_code |
| code | string (required) | code value returned from the /oauth2/auth callback |
| redirect_uri | URI (required) | Must match the redirect_uri used in the authorize request |
| client_id | string (required) | id of the registered client. May be omitted here when credentials are sent via HTTP Basic auth (client_secret_basic). |
| client_secret | string (required) | Client secret. May be omitted here when sent via HTTP Basic auth. |
| code_verifier | string (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
| Parameter | Type | Description |
|---|---|---|
| grant_type | string (required) | Must be refresh_token |
| refresh_token | string (required) | The refresh token returned from a previous token response |
| client_id | string (required) | id of the registered client. May be omitted here when credentials are sent via HTTP Basic auth (client_secret_basic). |
| client_secret | string (required) | Client secret. May be omitted here when sent via HTTP Basic auth. |
| scope | string (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
| Parameter | Type | Description |
|---|---|---|
| grant_type | string (required) | Must be client_credentials |
| scope | string (required) | Space-separated list of scopes. Cannot include user-context scopes — client credentials tokens have no user subject. |
| client_id | string (required) | id of the registered client. May be omitted here when credentials are sent via HTTP Basic auth (client_secret_basic). |
| client_secret | string (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
| Parameter | Type | Description |
|---|---|---|
| token | string (required) | The access token or refresh token to revoke |
| client_id | string (required) | id of the registered client. May be omitted here when credentials are sent via HTTP Basic auth (client_secret_basic). |
| client_secret | string (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
| Feature | Streamer Access Token | OAuth Token |
|---|---|---|
| Setup | Create from settings page | Register an OAuth client |
| Scopes | Full access (no scopes) | Fine-grained scopes |
| Token format | Authorization: Streamer <tok> | Authorization: Bearer <tok> |
| Best for | Personal / single-streamer use | Third-party apps, multi-user apps |