Reading organization data
Resource endpoints sit under/v1/, not /api/v1/ — that prefix belongs to the OAuth endpoints in Authorization Code + PKCE and Client Credentials. They take a bearer token the same way, and unlike userinfo they accept a Client Credentials token, which is what makes unattended access possible.
Every response nests its payload under
data, alongside a meta.requestId worth logging — quote it when asking Soundlink about a specific request.List campaigns
GET /v1/campaigns · scope campaigns:read
200 Response
integer
default:"1"
Minimum
1.integer
default:"10"
Between
1 and 100. Values outside the range are rejected with 400.string
default:"createdAt"
createdAt or status.string
default:"desc"
asc or desc.One campaign
GET /v1/campaigns/{campaignId} · scope campaigns:read
cURL
strategyType. A campaign the token’s organization does not own answers 404 — the same as one that does not exist, so ownership is not enumerable.
status values
status values
creating · active · paused · stopped · completed · failed · endedgeneration values
generation values
1, 2 or 3. Writes are supported only for generation 3.Campaign metrics
GET /v1/campaigns/{campaignId}/metrics/overview · scope metrics:read
Totals for a date range, with no per-day rows.
cURL
200 Response
string
default:"campaign start"
YYYY-MM-DD, inclusive.string
default:"today"
YYYY-MM-DD, inclusive. Must be greater than or equal to startDate, or you get 400.cpl, cpf and streams_per_listener are derived server-side — media spend per listener, per follower, and streams per listener, each 0 when listeners or followers are 0. Handle impressions, ad_clicks and link_clicks as nullable: a platform that did not report them sends null, which is not the same as zero.
Scopes and the endpoints they unlock
Request the minimum you need — the consent screen shows each scope to the user, and a shorter list converts better.
scope values are space-separated, must be unique, and must fall within your client’s registered allowedScopes.
scopes_supported in the discovery document is the authoritative list — it currently returns openid, email, campaigns:read, campaigns:write, metrics:read and videos:write.No token — client authentication only
Any scope
openid
email
Adds the email claim to the userinfo response. Unlocks no endpoint of its own.
campaigns:read
campaigns:write
Wallet-funded campaigns only. Same endpoints and rules as the API key scope of the same name — see Creating campaigns and Managing campaigns.
metrics:read
videos:write
Same endpoints and rate limit as the API key scope of the same name — see Importing videos.
API reference
Request parameters, response schemas and error codes for every endpoint above.
The reference documents these endpoints with
Authorization: Bearer sk_... authentication (the
x-api-key header also still works but is deprecated, retiring 2026-08-17). The same endpoints
accept an OAuth bearer token too — send Authorization: Bearer <access_token> in place of the
API key, and the scopes above apply.Errors and rate limits
All OAuth errors return{ "error": "<code>" }.
Resource errors have a different shape
The/v1/ resource endpoints wrap the error in an object and add a request id, rather than returning the flat { "error": "<code>" } the OAuth endpoints use. Branch on the nested error.code:
401
400 (bad query), 401, 403, 404, 429 and 500 are all possible. A 404 carries campaign_not_found — returned both for a campaign that does not exist and for one your organization does not own.
Authorize error behaviour
Two distinct behaviours, which affect how you handle failures:- Redirected errors
- Direct errors
When
client_id and redirect_uri are both valid, validation failures redirect to your callback with ?error=<code>&state=<state>. Handle these in your callback route.Rate limits
/authorize and /token are rate limited per client; exceeding the limit returns 429 with temporarily_unavailable and a Retry-After header. Respect it with backoff. Caching Client Credentials tokens for their full hour (see Client Credentials) keeps normal usage far below any limit.
Security checklist
Always send and verify state
Always send and verify state
Generate an unguessable
state per authorization request, bind it to the user’s session, and reject any callback whose state does not match. This is your CSRF defence on the redirect.Keep the PKCE verifier server-side
Keep the PKCE verifier server-side
The
code_verifier must never reach the browser, a URL, or client-side storage. Hold it in a signed httpOnly cookie or server session with a short TTL, and consume it once.Store the client secret in a secrets manager
Store the client secret in a secrets manager
Never commit it, never ship it in a client bundle, never expose it through an API response. Load it from the environment at runtime and rotate by requesting a new client. Exchange codes and request tokens only from your backend.
Register exact redirect URIs
Register exact redirect URIs
Use the fewest possible, all HTTPS, each a complete URL. Never build a redirect target from user input or an open redirector, and do not rely on prefix matching — none is performed.
Redact tokens and secrets from logs
Redact tokens and secrets from logs
Treat
code, code_verifier, client_secret, access_token and full Authorization headers as secrets. Log the token’s exp and organization_id if you need traceability, not the token. Watch for accidental capture in request-body logs, error reporters and APM traces — a 400 from the token endpoint often carries the whole form body.Do not trust unverified token claims
Do not trust unverified token claims
No JWKS is published, so token signatures cannot be verified by clients. Decode claims only to read values from a token you just received over TLS, and never as an authorization decision in your own system.