> ## Documentation Index
> Fetch the complete documentation index at: https://api.simkl.org/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth client libraries

> Every popular OAuth library mints real Simkl tokens with default config. Copy-paste working code per language.

<Tip>
  **Fastest path — skip the OAuth library entirely.** Simkl's OAuth flow is two HTTP steps:

  1. **Redirect** the user to `https://simkl.com/oauth/authorize?response_type=code&client_id=...&redirect_uri=...&state=...` — they approve in the browser, Simkl bounces back to your `redirect_uri` with `?code=...&state=...` in the query string.
  2. **POST** that `code` to `https://api.simkl.com/oauth/token` with `client_id`, `client_secret`, `redirect_uri`, and `grant_type=authorization_code` in the body — the response is `{"access_token": "...", "token_type": "bearer", "scope": "public", "expires_in": 157680000}`. Send that token as `Authorization: Bearer ...` on every authenticated request.

  That's it. **No refresh-token rotation, no scope dance** — Simkl tokens are long-lived (`expires_in` is 5 years) and only invalidate when the user revokes from [Connected Apps](https://simkl.com/settings/connected-apps/). For the full walkthroughs, see [OAuth 2.0 flow](/api-reference/oauth) (server-side with `client_secret`) or [PKCE flow](/api-reference/oauth-pkce) (mobile / SPA / desktop without `client_secret`).
</Tip>

Simkl's `POST /oauth/token` accepts both `application/x-www-form-urlencoded` (the RFC 6749 §3.2 default) and `application/json`, and reads client credentials from **either** the request body **or** an `Authorization: Basic` header (RFC 6749 §2.3.1). Discovery metadata is at [https://simkl.com/.well-known/oauth-authorization-server](https://simkl.com/.well-known/oauth-authorization-server) (RFC 8414) — modern libraries can auto-configure from it.

## Quick library status

Every library below was driven through the full browser-consent → real authorize code → real token mint flow against `api.simkl.com`. Each one returned a real `access_token` we then used to call `/users/settings` successfully.

| Language | Library                                                              | Default config status                                          |
| -------- | -------------------------------------------------------------------- | -------------------------------------------------------------- |
| Python   | [authlib](#python-authlib)                                           | ✅ Works as-is                                                  |
| Python   | [requests-oauthlib](#python-requests-oauthlib)                       | ✅ Works as-is                                                  |
| Python   | [httpx-oauth](#python-httpx-oauth)                                   | ✅ Works as-is                                                  |
| Node.js  | [openid-client v6 (panva)](#node-openid-client-v6)                   | ✅ Works — pass `{algorithm: 'oauth2'}` to `discovery()`        |
| Node.js  | [oauth4webapi (panva)](#node-oauth4webapi)                           | ✅ Works — pass `{algorithm: 'oauth2'}` to `discoveryRequest()` |
| Node.js  | [simple-oauth2](#node-simple-oauth2)                                 | ✅ Works as-is                                                  |
| Node.js  | [passport-oauth2](#node-passport-oauth2)                             | ✅ Works as-is                                                  |
| Node.js  | [@badgateway/oauth2-client](#node-badgatewayoauth2-client)           | ✅ Works as-is                                                  |
| Java     | [Nimbus OAuth 2.0 SDK](#java-nimbus-oauth-2-0-sdk)                   | ✅ Works as-is                                                  |
| Java     | [Spring Security OAuth2 Client](#java-spring-security-oauth2-client) | ✅ Works as-is                                                  |
| Java     | [Google OAuth Client for Java](#java-google-oauth-client-for-java)   | ✅ Works as-is                                                  |
| Java     | [scribejava-core](#java-scribejava-core)                             | ✅ Works as-is                                                  |
| Go       | [golang.org/x/oauth2](#go-golangorgxoauth2)                          | ✅ Works as-is                                                  |
| PHP      | [league/oauth2-client](#php-leagueoauth2-client)                     | ✅ Works as-is                                                  |
| Any      | [raw HTTP (curl, fetch, requests, …)](#raw-http)                     | ✅ Reference path                                               |

Most libraries need no configuration beyond the two endpoint URLs and your credentials. The only outliers are `openid-client v6` and `oauth4webapi` — both default to OIDC discovery (`/.well-known/openid-configuration`), but Simkl is OAuth2-only, so they need an explicit `{ algorithm: 'oauth2' }` option to use [our RFC 8414 metadata endpoint](https://simkl.com/.well-known/oauth-authorization-server). One-line fix shown in their snippets.

## The wire format

<CodeGroup>
  ```bash form-encoded (RFC 6749 §3.2 default) theme={"theme":{"light":"github-light","dark":"vesper"}}
  curl -X POST https://api.simkl.com/oauth/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -H "User-Agent: my-app-name/1.0" \
    --data-urlencode "client_id=YOUR_CLIENT_ID" \
    --data-urlencode "client_secret=YOUR_CLIENT_SECRET" \
    --data-urlencode "code=AUTHORIZATION_CODE" \
    --data-urlencode "redirect_uri=YOUR_REDIRECT_URI" \
    --data-urlencode "grant_type=authorization_code"
  ```

  ```bash JSON body theme={"theme":{"light":"github-light","dark":"vesper"}}
  curl -X POST https://api.simkl.com/oauth/token \
    -H "Content-Type: application/json" \
    -H "User-Agent: my-app-name/1.0" \
    -d '{
      "client_id":     "YOUR_CLIENT_ID",
      "client_secret": "YOUR_CLIENT_SECRET",
      "code":          "AUTHORIZATION_CODE",
      "redirect_uri":  "YOUR_REDIRECT_URI",
      "grant_type":    "authorization_code"
    }'
  ```

  ```bash Basic Auth header (RFC 6749 §2.3.1) theme={"theme":{"light":"github-light","dark":"vesper"}}
  curl -X POST https://api.simkl.com/oauth/token \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -H "User-Agent: my-app-name/1.0" \
    -u "YOUR_CLIENT_ID:YOUR_CLIENT_SECRET" \
    --data-urlencode "code=AUTHORIZATION_CODE" \
    --data-urlencode "redirect_uri=YOUR_REDIRECT_URI" \
    --data-urlencode "grant_type=authorization_code"
  ```
</CodeGroup>

Success response:

```json theme={"theme":{"light":"github-light","dark":"vesper"}}
{
  "access_token": "<64-hex-char bearer token>",
  "token_type":   "bearer",
  "scope":        "public",
  "expires_in":   157680000
}
```

The PKCE variant swaps `client_secret` for `code_verifier`.

***

## Python authlib

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
# pip install authlib httpx
from authlib.integrations.httpx_client import OAuth2Client

client = OAuth2Client("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET",
                      token_endpoint="https://api.simkl.com/oauth/token")
token = client.fetch_token(
    "https://api.simkl.com/oauth/token",
    code="AUTHORIZATION_CODE_FROM_REDIRECT",
    redirect_uri="YOUR_REDIRECT_URI",
)
print(token["access_token"])
```

***

## Python requests-oauthlib

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
# pip install requests-oauthlib
from requests_oauthlib import OAuth2Session

session = OAuth2Session("YOUR_CLIENT_ID", redirect_uri="YOUR_REDIRECT_URI")
token = session.fetch_token(
    "https://api.simkl.com/oauth/token",
    code="AUTHORIZATION_CODE_FROM_REDIRECT",
    client_secret="YOUR_CLIENT_SECRET",
)
print(token["access_token"])
```

***

## Python httpx-oauth

```python theme={"theme":{"light":"github-light","dark":"vesper"}}
# pip install httpx-oauth
import asyncio
from httpx_oauth.oauth2 import BaseOAuth2

client = BaseOAuth2(
    "YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET",
    authorize_endpoint="https://simkl.com/oauth/authorize",
    access_token_endpoint="https://api.simkl.com/oauth/token",
)
token = asyncio.run(client.get_access_token(
    code="AUTHORIZATION_CODE_FROM_REDIRECT",
    redirect_uri="YOUR_REDIRECT_URI",
))
print(token["access_token"])
```

***

## Node openid-client v6

```js theme={"theme":{"light":"github-light","dark":"vesper"}}
// npm install openid-client
import * as client from "openid-client";

// {algorithm: 'oauth2'} → use /.well-known/oauth-authorization-server (RFC 8414).
// Default would call /.well-known/openid-configuration which Simkl is not.
const config = await client.discovery(
  new URL("https://simkl.com"),
  "YOUR_CLIENT_ID",
  "YOUR_CLIENT_SECRET",
  undefined,
  { algorithm: "oauth2" },
);

const callback = new URL("YOUR_REDIRECT_URI?code=AUTHORIZATION_CODE&state=YOUR_STATE");
const token = await client.authorizationCodeGrant(config, callback);
console.log(token.access_token);
```

***

## Node oauth4webapi

```js theme={"theme":{"light":"github-light","dark":"vesper"}}
// npm install oauth4webapi
import * as oauth from "oauth4webapi";

const issuer = new URL("https://simkl.com");
// {algorithm: 'oauth2'} → use RFC 8414 metadata, not OIDC.
const discoveryResp = await oauth.discoveryRequest(issuer, { algorithm: "oauth2" });
const as = await oauth.processDiscoveryResponse(issuer, discoveryResp);

const clientObj = { client_id: "YOUR_CLIENT_ID" };
const clientAuth = oauth.ClientSecretBasic("YOUR_CLIENT_SECRET");

const callbackUrl = new URL("YOUR_REDIRECT_URI?code=AUTHORIZATION_CODE&state=YOUR_STATE");
const params = oauth.validateAuthResponse(as, clientObj, callbackUrl, "YOUR_STATE");
const response = await oauth.authorizationCodeGrantRequest(
  as, clientObj, clientAuth, params, "YOUR_REDIRECT_URI", oauth.nopkce,
);
const token = await response.json();
console.log(token.access_token);
```

***

## Node simple-oauth2

```js theme={"theme":{"light":"github-light","dark":"vesper"}}
// npm install simple-oauth2
import { AuthorizationCode } from "simple-oauth2";

const oauth = new AuthorizationCode({
  client: { id: "YOUR_CLIENT_ID", secret: "YOUR_CLIENT_SECRET" },
  auth:   { tokenHost: "https://api.simkl.com", tokenPath: "/oauth/token" },
});

const result = await oauth.getToken({
  code: "AUTHORIZATION_CODE_FROM_REDIRECT",
  redirect_uri: "YOUR_REDIRECT_URI",
});
console.log(result.token.access_token);
```

***

## Node passport-oauth2

```js theme={"theme":{"light":"github-light","dark":"vesper"}}
// npm install passport-oauth2
import OAuth2Strategy from "passport-oauth2";

passport.use(new OAuth2Strategy(
  {
    authorizationURL: "https://simkl.com/oauth/authorize",
    tokenURL:         "https://api.simkl.com/oauth/token",
    clientID:         "YOUR_CLIENT_ID",
    clientSecret:     "YOUR_CLIENT_SECRET",
    callbackURL:      "YOUR_REDIRECT_URI",
  },
  (accessToken, refreshToken, profile, done) => done(null, { accessToken }),
));
```

***

## Node @badgateway/oauth2-client

```js theme={"theme":{"light":"github-light","dark":"vesper"}}
// npm install @badgateway/oauth2-client
import { OAuth2Client } from "@badgateway/oauth2-client";

const client = new OAuth2Client({
  clientId:              "YOUR_CLIENT_ID",
  clientSecret:          "YOUR_CLIENT_SECRET",
  tokenEndpoint:         "https://api.simkl.com/oauth/token",
  authorizationEndpoint: "https://simkl.com/oauth/authorize",
});

const token = await client.authorizationCode.getTokenFromCodeRedirect(
  "YOUR_REDIRECT_URI?code=AUTHORIZATION_CODE&state=YOUR_STATE",
  { redirectUri: "YOUR_REDIRECT_URI" },
);
console.log(token.accessToken);
```

***

## Java Nimbus OAuth 2.0 SDK

```java theme={"theme":{"light":"github-light","dark":"vesper"}}
// Maven: com.nimbusds:oauth2-oidc-sdk:11.21
import com.nimbusds.oauth2.sdk.*;
import com.nimbusds.oauth2.sdk.auth.*;
import com.nimbusds.oauth2.sdk.id.ClientID;
import java.net.URI;

TokenRequest req = new TokenRequest(
    new URI("https://api.simkl.com/oauth/token"),
    new ClientSecretBasic(new ClientID("YOUR_CLIENT_ID"), new Secret("YOUR_CLIENT_SECRET")),
    new AuthorizationCodeGrant(
        new AuthorizationCode("AUTHORIZATION_CODE_FROM_REDIRECT"),
        new URI("YOUR_REDIRECT_URI")
    )
);
AccessTokenResponse tok = TokenResponse.parse(req.toHTTPRequest().send()).toSuccessResponse();
System.out.println(tok.getTokens().getAccessToken().getValue());
```

***

## Java Spring Security OAuth2 Client

```yaml theme={"theme":{"light":"github-light","dark":"vesper"}}
# application.yml — discovery auto-configures the rest.
spring:
  security:
    oauth2:
      client:
        registration:
          simkl:
            client-id:                YOUR_CLIENT_ID
            client-secret:            YOUR_CLIENT_SECRET
            authorization-grant-type: authorization_code
            redirect-uri:             "{baseUrl}/login/oauth2/code/{registrationId}"
        provider:
          simkl:
            issuer-uri: https://simkl.com
```

Spring fetches `/.well-known/oauth-authorization-server` from the `issuer-uri` and wires up `authorization_endpoint` + `token_endpoint` automatically. Default `client_secret_basic` works.

***

## Java Google OAuth Client for Java

```java theme={"theme":{"light":"github-light","dark":"vesper"}}
// Maven: com.google.oauth-client:google-oauth-client:1.38.0 + google-http-client-jackson2:1.45.3
import com.google.api.client.auth.oauth2.AuthorizationCodeTokenRequest;
import com.google.api.client.http.BasicAuthentication;
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.jackson2.JacksonFactory;

var req = new AuthorizationCodeTokenRequest(
    new NetHttpTransport(), JacksonFactory.getDefaultInstance(),
    new GenericUrl("https://api.simkl.com/oauth/token"),
    "AUTHORIZATION_CODE_FROM_REDIRECT");
req.setRedirectUri("YOUR_REDIRECT_URI");
req.setClientAuthentication(new BasicAuthentication("YOUR_CLIENT_ID", "YOUR_CLIENT_SECRET"));
System.out.println(req.execute().getAccessToken());
```

***

## Java scribejava-core

```java theme={"theme":{"light":"github-light","dark":"vesper"}}
// Maven: com.github.scribejava:scribejava-core:8.3.3 + scribejava-java8:8.3.3
import com.github.scribejava.core.builder.ServiceBuilder;
import com.github.scribejava.core.builder.api.DefaultApi20;
import com.github.scribejava.core.oauth2.clientauthentication.*;

class SimklApi extends DefaultApi20 {
  public String getAccessTokenEndpoint()     { return "https://api.simkl.com/oauth/token"; }
  protected String getAuthorizationBaseUrl() { return "https://simkl.com/oauth/authorize"; }
  @Override public ClientAuthentication getClientAuthentication() {
    return HttpBasicAuthenticationScheme.instance();
  }
}

var service = new ServiceBuilder("YOUR_CLIENT_ID")
    .apiSecret("YOUR_CLIENT_SECRET")
    .callback("YOUR_REDIRECT_URI")
    .build(new SimklApi());
var token = service.getAccessToken("AUTHORIZATION_CODE_FROM_REDIRECT");
System.out.println(token.getAccessToken());
```

***

## Go golang.org/x/oauth2

```go theme={"theme":{"light":"github-light","dark":"vesper"}}
// go get golang.org/x/oauth2
import (
    "context"
    "golang.org/x/oauth2"
)

cfg := &oauth2.Config{
    ClientID:     "YOUR_CLIENT_ID",
    ClientSecret: "YOUR_CLIENT_SECRET",
    RedirectURL:  "YOUR_REDIRECT_URI",
    Endpoint: oauth2.Endpoint{
        AuthURL:  "https://simkl.com/oauth/authorize",
        TokenURL: "https://api.simkl.com/oauth/token",
    },
}
tok, _ := cfg.Exchange(context.Background(), "AUTHORIZATION_CODE_FROM_REDIRECT")
fmt.Println(tok.AccessToken)
```

***

## PHP league/oauth2-client

```php theme={"theme":{"light":"github-light","dark":"vesper"}}
// composer require league/oauth2-client
require __DIR__ . "/vendor/autoload.php";

$provider = new \League\OAuth2\Client\Provider\GenericProvider([
    "clientId"                => "YOUR_CLIENT_ID",
    "clientSecret"            => "YOUR_CLIENT_SECRET",
    "redirectUri"             => "YOUR_REDIRECT_URI",
    "urlAuthorize"            => "https://simkl.com/oauth/authorize",
    "urlAccessToken"          => "https://api.simkl.com/oauth/token",
    "urlResourceOwnerDetails" => "https://api.simkl.com/users/settings",
]);
$token = $provider->getAccessToken("authorization_code", ["code" => "AUTHORIZATION_CODE_FROM_REDIRECT"]);
echo $token->getToken();
```

***

## Raw HTTP

```bash theme={"theme":{"light":"github-light","dark":"vesper"}}
curl -X POST https://api.simkl.com/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -H "User-Agent: my-app-name/1.0" \
  --data-urlencode "client_id=YOUR_CLIENT_ID" \
  --data-urlencode "client_secret=YOUR_CLIENT_SECRET" \
  --data-urlencode "code=AUTHORIZATION_CODE_FROM_REDIRECT" \
  --data-urlencode "redirect_uri=YOUR_REDIRECT_URI" \
  --data-urlencode "grant_type=authorization_code"
```

***

### Other OAuth libraries (inferred from RFC compliance)

These libraries aren't in our live test harness, so the status below is read from each library's source/docs — not from a captured request to `api.simkl.com`. Most wrap one of the live-tested libraries above; the rest follow the same RFC defaults Simkl now accepts.

| Language          | Library                                                                               | Inferred                                                                               |
| ----------------- | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
| Python            | Django allauth, python-social-auth, fastapi-users                                     | Wraps `requests-oauthlib` — should work as-is.                                         |
| Node.js           | NextAuth.js / Auth.js, express-openid-connect                                         | Wraps `openid-client` — works with `{algorithm: 'oauth2'}` discovery.                  |
| Node.js           | grant (server middleware), oidc-client-ts                                             | Default RFC config matches Simkl.                                                      |
| .NET              | Duende.IdentityModel, OpenIddict (client), MS.AspNetCore.Authentication.OpenIdConnect | Default Basic Auth / discovery flows align with Simkl.                                 |
| .NET              | Microsoft.Identity.Client (MSAL)                                                      | Auth-code grant should work; broker / conditional access flows out of scope.           |
| Server-side PHP   | HWIOAuthBundle (Symfony)                                                              | Wraps `league/oauth2-client` — should work as-is.                                      |
| Ruby              | oauth2 gem, omniauth-oauth2                                                           | Default `auth_scheme: :basic_auth` accepted.                                           |
| Rust              | oauth2 crate, openidconnect crate (ramosbugs)                                         | Default `AuthType::BasicAuth` accepted.                                                |
| Mobile            | AppAuth-iOS / AppAuth-Android / AppAuth-JS / react-native-app-auth                    | RFC default — should work as-is.                                                       |
| Postman           | Built-in OAuth 2.0 helper                                                             | Both "Send as Basic Auth header" and "Send client credentials in body" modes accepted. |
| OpenAPI Generator | Generated clients (all languages)                                                     | Generators emit RFC-conformant clients.                                                |

If you hit a library not on this list, the sanity check is one HTTP capture: confirm the token POST hits `https://api.simkl.com/oauth/token`, sends `client_id` / `code` / `redirect_uri` / `grant_type` (and `client_secret` either in the body or in `Authorization: Basic`), and see what comes back. If the request looks RFC-shaped and you still hit an error, [let us know](/support) — we'd appreciate the capture so we can promote the library to the live matrix.

## See also

<CardGroup cols={3}>
  <Card title="OAuth 2.0 walkthrough" icon="lock" href="/api-reference/oauth" horizontal>
    Confidential (server-side) flow.
  </Card>

  <Card title="Public PKCE walkthrough" icon="shield-keyhole" href="/api-reference/oauth-pkce" horizontal>
    Mobile / SPA / desktop flow without `client_secret`.
  </Card>

  <Card title="PIN flow" icon="key" href="/api-reference/pin" horizontal>
    TV / console / CLI flow with a 5-character code.
  </Card>
</CardGroup>
