> ## Documentation Index
> Fetch the complete documentation index at: https://www.getsoundlink.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth: Client Credentials

> Mint unattended access tokens for an organization that already granted consent, and revoke a grant when the integration ends.

Once a grant exists, request tokens for that organization without involving the user. Client Credentials never creates a grant — run [Authorization Code + PKCE](/docs/oauth-authorization-code) first for each new organization.

<Warning>
  **There are no refresh tokens.** A Client Credentials token can be re-requested at any time, so design ongoing integrations around this grant rather than Authorization Code.
</Warning>

## Get a token

Your `client_secret` goes in the request body (`client_secret_post`); HTTP Basic is not supported.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
  curl -X POST https://api.getsoundlink.com/api/v1/oauth/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials" \
    -d "client_id=YOUR_CLIENT_ID" \
    -d "client_secret=YOUR_CLIENT_SECRET" \
    -d "organization_id=ORGANIZATION_UUID" \
    -d "scope=openid email"
  ```

  ```ts TypeScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const res = await fetch("https://api.getsoundlink.com/api/v1/oauth/token", {
    method: "POST",
    headers: { "Content-Type": "application/x-www-form-urlencoded" },
    body: new URLSearchParams({
      grant_type: "client_credentials",
      client_id: process.env.SOUNDLINK_CLIENT_ID!,
      client_secret: process.env.SOUNDLINK_CLIENT_SECRET!,
      organization_id: organizationId,
      scope: "openid email",
    }),
  });
  ```
</CodeGroup>

```json 200 Response theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.PAYLOAD.SIGNATURE",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid email"
}
```

<ParamField body="scope" type="string">
  Optional. Omit to receive the grant's full set. If supplied it must be a subset of the granted scopes, or you get `invalid_scope`.
</ParamField>

**Renewal** is simply another request. Cache the token for its hour rather than minting one per API call — the token endpoint is rate limited per client.

<AccordionGroup>
  <Accordion title="unauthorized_client — what it means here">
    Your client is authenticated but does not have `client_credentials` in its allowed grant types. Ask Soundlink to add it.
  </Accordion>

  <Accordion title="invalid_grant — what it means here">
    One of: no grant exists for this client and organization; the grant was revoked; or `THIRD_PARTY_INTEGRATIONS_ENABLED` is off for that organization. Run the consent flow for the organization first — Client Credentials never creates a grant.
  </Accordion>
</AccordionGroup>

***

## Revoking a grant

Disconnect an organization by revoking the grant. Authenticate with your client credentials and identify the grant by `grant_id` (available as a token claim).

```bash cURL theme={"theme":{"light":"github-light","dark":"github-dark"}}
curl -X POST https://api.getsoundlink.com/api/v1/oauth/grants/revoke \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "client_id=YOUR_CLIENT_ID" \
  -d "client_secret=YOUR_CLIENT_SECRET" \
  -d "grant_id=GRANT_UUID"
```

Returns **`200` with an empty body**. The operation is **idempotent** — revoking an unknown or already-revoked grant also returns `200`, so retries are safe.

<Warning>
  **Existing access tokens survive revocation for up to one hour.**

  Revocation immediately prevents *new* tokens from being issued for that grant. It does not invalidate tokens already minted: access tokens are stateless and are validated without a per-request grant lookup, so a token issued moments before revocation remains accepted until its `exp` — at most 3600 seconds later.

  If access must stop instantly, enforce it on your side as well: discard your cached tokens at revocation time and stop issuing requests.
</Warning>

After revoking, delete the organization id and grant id from your store. Re-connecting requires the full consent flow again.

## Next

See [Scopes, endpoints and errors](/docs/oauth-scopes-and-errors) for the full list of routes a Client Credentials token can call, and the errors it can return.
