> ## 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.

<Note>
  **This page is for [AUTH V1](/api-reference/auth-v1), which is being retired around April 2027.** If you are starting something new, or [migrating](/guides/migrating-v1-to-v2), the equivalent is [Client libraries (AUTH V2)](/api-reference/oauth2-libraries) — V2 is standards-compliant, so the library configuration is shorter and discovery actually works there.
</Note>

<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).

<Warning>
  **Do not use RFC 8414 discovery for a V1 app.** The metadata document at `https://simkl.com/.well-known/oauth-authorization-server` describes **OAuth V2**, not the V1 flow on this page. A V1 app that auto-configures from it will point at the V2 endpoints, which reject V1 client IDs with `invalid_client` — and the error message talks about OAuth 2.0 not being enabled, which is confusing when you are already using OAuth.

  Every snippet on this page therefore sets the two endpoint URLs **explicitly**:

  | | |
  | - | - |
  | Authorize | `https://simkl.com/oauth/authorize` |
  | Token | `https://api.simkl.com/oauth/token` |

  Discovery is the right tool if you are building on **OAuth V2**, where the metadata is accurate and auto-configuration is the recommended path. See [AUTH V2](/api-reference/auth-v2).
</Warning>

## 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.

<Note>
  Three snippets — `openid-client v6`, `oauth4webapi` and Spring Security — originally configured themselves through RFC 8414 discovery, which is how they were tested. They now set the two endpoints inline instead, because Simkl's discovery document was subsequently published and describes V2. The flow they perform is unchanged and the endpoints are the same ones the other snippets use, but **that specific edit has not itself been re-driven end to end**. If you hit a problem with one of them, the raw HTTP reference at the bottom of the page is the ground truth.
</Note>

| 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 — set endpoints explicitly, do not call `discovery()` |
| Node.js | [oauth4webapi (panva)](#node-oauth4webapi) | ✅ Works — set endpoints explicitly, do not call `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-badgateway-oauth2-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-golang-x-oauth2) | ✅ Works as-is |
| PHP | [league/oauth2-client](#php-league-oauth2-client) | ✅ Works as-is |
| Any | [raw HTTP (curl, fetch, requests, …)](#raw-http) | ✅ Reference path |

**Every library here needs the same thing: the two endpoint URLs and your credentials.** Nothing on this page requires discovery, and for V1 you should not use it — see the warning above.

The libraries worth a second look are the three that *prefer* to discover: `openid-client v6`, `oauth4webapi` and Spring Security. Left to their defaults they will fetch a metadata document and configure themselves from it, which for a V1 app is exactly the wrong outcome. Their snippets below set the endpoints inline instead, which is a one-line difference and removes a network call from your startup path.

## 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";

// Endpoints are set explicitly. Do NOT call client.discovery() for a V1 app:
// Simkl's discovery document describes OAuth V2 and would point you at
// endpoints that reject this client_id.
const config = new client.Configuration(
  {
    issuer: "https://simkl.com",
    authorization_endpoint: "https://simkl.com/oauth/authorize",
    token_endpoint: "https://api.simkl.com/oauth/token",
  },
  "YOUR_CLIENT_ID",
  "YOUR_CLIENT_SECRET",
);

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";

// Authorization server described inline. Do NOT use oauth.discoveryRequest()
// for a V1 app: Simkl's discovery document describes OAuth V2 and would give
// you endpoints that reject this client_id.
const as = {
  issuer: "https://simkl.com",
  authorization_endpoint: "https://simkl.com/oauth/authorize",
  token_endpoint: "https://api.simkl.com/oauth/token",
};

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 }),
));
```

***

<a id="node-badgateway-oauth2-client" />

## 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 — endpoints set explicitly, no discovery.
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:
            authorization-uri: https://simkl.com/oauth/authorize
            token-uri:         https://api.simkl.com/oauth/token
```

Set `authorization-uri` and `token-uri` directly rather than `issuer-uri`, as above — this is the one place a V1 app must not take the shortcut. `issuer-uri` resolves, and it resolves to **V2**: Spring reads `/.well-known/oauth-authorization-server`, configures itself for `/oauth2/*`, and your V1 `client_id` is then rejected with `invalid_client`. Default `client_secret_basic` works either way.

***

## 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());
```

***

<a id="go-golang-x-oauth2" />

## 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)
```

***

<a id="php-league-oauth2-client" />

## 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` — set the two endpoints explicitly rather than using discovery (see the warning at the top of this page). |
| 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 aligns with Simkl. Configure the authorize and token URLs directly; do not point them at an issuer for discovery. |
| .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>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.