Skip to main content

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.

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. For the full walkthroughs, see OAuth 2.0 flow (server-side with client_secret) or PKCE flow (mobile / SPA / desktop without client_secret).
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 (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.
LanguageLibraryDefault config status
Pythonauthlib✅ Works as-is
Pythonrequests-oauthlib✅ Works as-is
Pythonhttpx-oauth✅ Works as-is
Node.jsopenid-client v6 (panva)✅ Works — pass {algorithm: 'oauth2'} to discovery()
Node.jsoauth4webapi (panva)✅ Works — pass {algorithm: 'oauth2'} to discoveryRequest()
Node.jssimple-oauth2✅ Works as-is
Node.jspassport-oauth2✅ Works as-is
Node.js@badgateway/oauth2-client✅ Works as-is
JavaNimbus OAuth 2.0 SDK✅ Works as-is
JavaSpring Security OAuth2 Client✅ Works as-is
JavaGoogle OAuth Client for Java✅ Works as-is
Javascribejava-core✅ Works as-is
Gogolang.org/x/oauth2✅ Works as-is
PHPleague/oauth2-client✅ Works as-is
Anyraw HTTP (curl, fetch, requests, …)✅ 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. One-line fix shown in their snippets.

The wire format

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"
Success response:
{
  "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

# 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

# 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

# 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

// 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

// 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

// 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

// 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

// 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

// 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

# 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

// 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

// 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 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

// 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

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.
LanguageLibraryInferred
PythonDjango allauth, python-social-auth, fastapi-usersWraps requests-oauthlib — should work as-is.
Node.jsNextAuth.js / Auth.js, express-openid-connectWraps openid-client — works with {algorithm: 'oauth2'} discovery.
Node.jsgrant (server middleware), oidc-client-tsDefault RFC config matches Simkl.
.NETDuende.IdentityModel, OpenIddict (client), MS.AspNetCore.Authentication.OpenIdConnectDefault Basic Auth / discovery flows align with Simkl.
.NETMicrosoft.Identity.Client (MSAL)Auth-code grant should work; broker / conditional access flows out of scope.
Server-side PHPHWIOAuthBundle (Symfony)Wraps league/oauth2-client — should work as-is.
Rubyoauth2 gem, omniauth-oauth2Default auth_scheme: :basic_auth accepted.
Rustoauth2 crate, openidconnect crate (ramosbugs)Default AuthType::BasicAuth accepted.
MobileAppAuth-iOS / AppAuth-Android / AppAuth-JS / react-native-app-authRFC default — should work as-is.
PostmanBuilt-in OAuth 2.0 helperBoth “Send as Basic Auth header” and “Send client credentials in body” modes accepted.
OpenAPI GeneratorGenerated 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 — we’d appreciate the capture so we can promote the library to the live matrix.

See also

OAuth 2.0 walkthrough

Confidential (server-side) flow.

Public PKCE walkthrough

Mobile / SPA / desktop flow without client_secret.

PIN flow

TV / console / CLI flow with a 5-character code.