Skip to main content
This is the consent-establishing flow — a user signs in, approves scopes, and your app receives a token that acts as them. Run Discovery and onboarding first if you haven’t registered a client yet.
1

Generate PKCE values and state

Create a code_verifier, derive its S256 challenge, and generate an unguessable state.
Store codeVerifier and state server-side, bound to the user’s session — for example in a signed, httpOnly cookie with a short TTL. Never put the verifier in the browser or in a URL.
2

Redirect the user to the authorization endpoint

On success the user is redirected (302) to the Soundlink consent screen. This endpoint sends Cache-Control: no-store.
3

The user approves, and Soundlink returns to your callback

Compare state against the value you stored before doing anything else, and reject the request if it does not match. If the user declines, you receive ?error=access_denied&state=… instead — treat this as a normal outcome, not a failure.
4

Exchange the code for a token

The request is application/x-www-form-urlencoded. This grant authenticates with the PKCE verifier and sends no client_secret.
200 Response
redirect_uri must be identical to the one used in the previous step. Authorization codes are single-use. Store the refresh_token server-side.
5

Record the organization id

organization_id is a claim on the access token, so no extra request is needed to read it.This is the one value you must persist. Soundlink provides no endpoint that lists which organizations have authorized your client, so an organization whose id you have not stored is one you can no longer act for. Keep it in your own database, keyed to your own user or tenant, along with grant_id if you intend to support disconnecting.

Access token claims

string
The user’s id for authorization_code tokens; your client id for client_credentials tokens — those have no user.
Access tokens are signed with a symmetric key and no JWKS endpoint is published, so you cannot verify their signature. Decode claims only for values you need (such as organization_id) from a token you have just received over TLS, and never treat a decoded claim as proof of anything. Treat tokens as opaque credentials otherwise.

Refresh the access token

When the access token expires, trade the refresh token for a new pair.
200 Response
  • Every refresh rotates the token. Replace the stored refresh token with the new one. The old one stops working.
  • A refresh token is valid for 30 days. Each new one gets its own 30 days.
  • Never reuse a refresh token. Sending one that was already used revokes every refresh token for that grant, and the user must consent again. Two concurrent refreshes with the same token count as reuse, so refresh from one place.
  • You can narrow, not widen. Send scope to get an access token with fewer scopes. The new refresh token keeps the original scopes. Asking for more returns invalid_scope.
  • Send client_secret if your client has one. A wrong secret returns 401 invalid_client.
An expired, revoked, or reused refresh token returns 400 invalid_grant. Treat it as a disconnect and send the user through consent again.

Userinfo

GET /api/v1/oauth/userinfo requires a token from the authorization_code grant with the openid scope. A Client Credentials token is rejected with 403 access_denied regardless of its scopes, because it represents an application rather than a user.
cURL
200 Response
email is present only when the token carries the email scope. Responses send Cache-Control: no-store. Failures return { "error": "…" } with a WWW-Authenticate header: Send exactly one Authorization header — two are rejected rather than merged.

Next

Once the grant exists, mint unattended tokens with Client Credentials, or jump to Scopes, endpoints and errors to call campaign and metrics routes.