Kinde SDKs
SDKs and APIs
Kinde is designed to help founders and developers build SaaS products by providing software infrastructure like authentication, feature flags, user management, and more.
We support connecting to Kinde through our SDKs, but everything we build follows the OAuth 2.0 standard, so you can integrate Kinde into any language or framework without an SDK.
Sign in to your Kinde dashboard and select Add application
Enter a name for the application (e.g., “My App”)
Select an application type:
Select Other
Select Save.
In your app settings page, select Details
Copy your app keys:
You will need these keys to connect your codebase with Kinde.
In your app’s Details page, scroll down to the Callback URLs section
Add the following URLs:
http://localhost:3000/auth/callback)http://localhost:3000)You can add more than one callback URL per line.
Authorization servers require the callback URL to be registered ahead of time to prevent abuse. Whatever path you use for your callback URL, it must be registered with Kinde. The logout redirect URL is where Kinde redirects users after they sign out — typically your app’s homepage.
Select Save
These are the OpenID endpoints for Kinde, found at:
<YOUR_DOMAIN>/.well-known/openid-configuration
Replace <YOUR_DOMAIN> with either your custom domain (e.g., https://auth.yourbusiness.com) or your Kinde domain (e.g., https://your_business.kinde.com)
<YOUR_DOMAIN>/oauth2/auth<YOUR_DOMAIN>/oauth2/token<YOUR_DOMAIN>/oauth2/v2/user_profile<YOUR_DOMAIN>/logoutOther endpoints:
<YOUR_DOMAIN>/.well-known/jwks<YOUR_DOMAIN>/oauth2/revoke<YOUR_DOMAIN>/oauth2/introspectIn your app, generate a random state value and a nonce:
import crypto from 'node:crypto';
const state = crypto.randomBytes(32).toString('hex');const nonce = crypto.randomBytes(32).toString('hex');Store state and nonce in your server-side session — you will need them when the user returns to the callback URL.
Send the user to Kinde’s authorization endpoint with the state and nonce parameters included in the URL:
GET <YOUR_DOMAIN>/oauth2/auth?response_type=code&client_id=<your_kinde_client_id>&redirect_uri=<your_app_redirect_url>&scope=openid%20profile%20email&state=<your_random_state_value_min_8_characters>&nonce=<your_random_nonce_value>In your app, generate a random state value, a nonce, a code_verifier, and derive a code_challenge from it:
function base64UrlEncode(buffer) { const bytes = new Uint8Array(buffer); let binary = ''; for (const byte of bytes) { binary += String.fromCharCode(byte); } return btoa(binary) .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=+$/, '');}
const stateBytes = new Uint8Array(32);crypto.getRandomValues(stateBytes);const state = Array.from(stateBytes, (b) => b.toString(16).padStart(2, '0')).join('');
const nonceBytes = new Uint8Array(32);crypto.getRandomValues(nonceBytes);const nonce = Array.from(nonceBytes, (b) => b.toString(16).padStart(2, '0')).join('');
// 64 random bytes, base64url-encoded = 86-char code_verifierconst verifierBytes = new Uint8Array(64);crypto.getRandomValues(verifierBytes);const codeVerifier = base64UrlEncode(verifierBytes);
// SHA-256 hash of the verifier, base64url-encoded = code_challengeconst digest = await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode(codeVerifier));const codeChallenge = base64UrlEncode(digest);Store state, nonce, and code_verifier in sessionStorage (for SPAs) and secure device storage (Keychain/Keystore) for mobile apps— you will need them when the user returns to the callback URL.
Send the user to Kinde’s authorization endpoint with the state, nonce, and PKCE parameters included in the URL:
GET <YOUR_DOMAIN>/oauth2/auth?response_type=code&client_id=<your_kinde_client_id>&redirect_uri=<your_app_redirect_url>&scope=openid%20profile%20email&state=<your_random_state_value_min_8_characters>&nonce=<your_random_nonce_value>&code_challenge=<code_challenge>&code_challenge_method=S256The redirect_uri is your app’s callback URL and must be registered with Kinde.
Kinde redirects the user back to your redirect_uri with an authorization code and the original state value as query parameters:
https://your-app.com/auth/callback?code=<AUTHORIZATION_CODE>&state=<your_random_state_value_min_8_characters>Validate that the returned state matches the one you generated in the previous step to prevent CSRF attacks.
Exchange the authorization code for tokens by making a POST request to the Kinde token endpoint:
POST <YOUR_DOMAIN>/oauth2/tokenContent-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=<AUTHORIZATION_CODE>&redirect_uri=<your_app_redirect_url>&client_id=<your_kinde_client_id>&client_secret=<your_kinde_client_secret>Exchange the authorization code for tokens by making a POST request to the Kinde token endpoint with the code_verifier — no client_secret needed:
POST <YOUR_DOMAIN>/oauth2/tokenContent-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=<AUTHORIZATION_CODE>&redirect_uri=<your_app_redirect_url>&client_id=<your_kinde_client_id>&code_verifier=<code_verifier>Kinde returns the tokens in the response body (same for both flows):
{ "access_token": "...", "id_token": "...", "refresh_token": "..." // only if the "offline" scope was requested}Redirect the user to a protected page in your app
Decode the id_token with any standard JWT library, or if you are using JavaScript, use the Kinde JavaScript library. Validate that the nonce claim matches the value you stored earlier to prevent replay attacks.
Use the decoded claims to display in your app:
{ "sub": "kp_7a4b2c...", "given_name": "Jane", "family_name": "Doe", "email": "jane@example.com", "picture": "https://gravatar.com/...", "nonce": "<your_random_nonce_value>", "iat": 1716800000, "exp": 1716886400}Alternatively, you can call the userinfo endpoint, passing the access_token as a Bearer token. This always returns an up-to-date version of the user’s profile. See When should I use the userinfo endpoint instead of decoding the id_token? for more details.
Complete the registration flow in your app and verify that you can sign in to a protected page (e.g., a dashboard).
The following link redirects the user to the Kinde sign-in page by default:
GET <YOUR_DOMAIN>/oauth2/auth?response_type=code&client_id=<your_kinde_client_id>&redirect_uri=<your_app_redirect_url>&scope=openid%20profile%20email&state=<your_random_state_value_min_8_characters>&nonce=<your_random_nonce_value>To send the user to the sign-up page instead, add prompt=create to the URL:
GET <YOUR_DOMAIN>/oauth2/auth?response_type=code&client_id=<your_kinde_client_id>&redirect_uri=<your_app_redirect_url>&scope=openid%20profile%20email&state=<your_random_state_value_min_8_characters>&nonce=<your_random_nonce_value>&prompt=createIf you offer mobile and desktop apps alongside a browser-based version, you’ll need to handle the post-authentication browser state. Rather than leaving a hanging screen, you can show users a success page. To do this, add the has_success_page parameter to the authorization URL.
Route protection works at two layers. Both are required — the frontend layer is UX, the backend layer is the real security gate.
Layer 1: Frontend — redirect unauthenticated users
Before rendering a protected page or screen, check that you have a valid, non-expired access token. If not, redirect to the Kinde authorization endpoint:
function isTokenExpired(token) { // JWT payloads are base64url-encoded; convert to standard base64 before decoding const base64Url = token.split('.')[1]; const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/'); const { exp } = JSON.parse(atob(base64)); return Date.now() >= exp * 1000;}
// Run this check on every protected page loadif (!accessToken || isTokenExpired(accessToken)) { window.location.href = '<YOUR_DOMAIN>/oauth2/auth?response_type=code&client_id=<CLIENT_ID>&...';}Never rely on the frontend check alone. A user can bypass client-side redirects and call your API directly. The backend must always verify the token independently.
Layer 2: Backend — verify the token on every request
Your server must validate the access token on every call to a protected endpoint. Send the token in the Authorization header:
GET /api/protected-resourceAuthorization: Bearer <access_token>On the server, verify the token before returning any data:
// Pseudocode — pattern is the same in Node, Python, Go, etc.function protectedRoute(req, res, next) { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) return res.status(401).json({ error: 'Unauthorized' });
try { const payload = verifyJWT(token, { jwksUri: '<YOUR_DOMAIN>/.well-known/jwks', issuer: '<YOUR_DOMAIN>', }); req.user = payload; // access sub, org_code, scp, permissions next(); } catch { return res.status(401).json({ error: 'Invalid or expired token' }); }}See Validate the access token below for the full list of claims to check.
Using claims for authorization
Once the token is verified, use the claims inside it to make access decisions:
| Claim | Use for |
|---|---|
sub | Identify the user — link to your own database records |
scp | Check the user has the required OAuth scopes |
permissions | Fine-grained access control (e.g. read:reports) |
org_code | Multi-tenant apps — confirm the user belongs to the right organization |
Handling token expiry
By default, access tokens expire after 24 hours. If you requested the offline scope, use the refresh token to get a new access token silently — without sending the user through the login flow again:
POST <YOUR_DOMAIN>/oauth2/tokenContent-Type: application/x-www-form-urlencoded
grant_type=refresh_token&client_id=<CLIENT_ID>&client_secret=<CLIENT_SECRET>&refresh_token=<REFRESH_TOKEN>POST <YOUR_DOMAIN>/oauth2/tokenContent-Type: application/x-www-form-urlencoded
grant_type=refresh_token&client_id=<CLIENT_ID>&refresh_token=<REFRESH_TOKEN>If no refresh token is available (i.e. offline scope was not requested), redirect the user to sign in again.
Always verify the access_token signature on your server before trusting any request. Kinde signs tokens with RS256 and publishes its public keys at the JWKS endpoint:
<YOUR_DOMAIN>/.well-known/jwksMost JWT libraries accept a JWKS URL directly — point your library at this URL and it will fetch and cache the correct public key automatically.
As part of verification, validate the following claims:
| Claim | Expected value |
|---|---|
iss | <YOUR_DOMAIN> |
aud | Your API audience if configured |
exp | Must be in the future |
iat | Must be in the past |
Reject any token that fails signature verification or has an unexpected claim value.
Call the Kinde Account API with the logged-in user’s access token to generate a self-serve portal link for the user:
const response = await fetch("<YOUR_DOMAIN>/account_api/v1/portal_link", { headers: { Authorization: `Bearer ${userAccessToken}` }});const data = await response.json();window.location = data.url;Optional parameters:
subnav – specify the portal section to open (e.g., organization_details, profile).return_url – where to redirect the user after exiting the portal.See: Self-serve portal for users.
To sign a user out:
Clear any session or token storage in your app
Redirect them to the Kinde logout endpoint with your logout URL:
<YOUR_DOMAIN>/logout?redirect=<your_logout_url>This ends their Kinde session. They will need to authenticate again to receive new tokens.
To register a logout URL, go to Settings > Applications > [Your app] > View details, then add it to the Allowed logout redirect URLs field.
Kinde implements RFC 9470 step-up authentication. Your app or API can require a stronger authentication context for sensitive operations, and Kinde will collect only what is missing.
GET <YOUR_DOMAIN>/oauth2/auth?response_type=code&client_id=<your_kinde_client_id>&redirect_uri=<your_app_redirect_url>&scope=openid%20profile%20email&state=<your_random_state_value_min_8_characters>&nonce=<your_random_nonce_value>&acr_values=mfa&max_age=300/oauth2/auth with acr_values, and optionally max_age.acr_values, and its last authentication must be more recent than max_age.mfa, the user verifies an MFA factor that they have already enrolled, for example an authenticator app or a one-time code. If the user has an enrolled factor, Kinde can also send a one-time code to the email address on their profile. Kinde does not ask the user to enrol a new factor to meet the request.phr, the user verifies with a passkey.max_age with acr_values, the user verifies only the factor that acr_values requires, as above. Kinde does not ask for the first factor again.max_age without acr_values, the user signs in again with their first factor. If your MFA policy requires a second factor, Kinde also asks for it.Because the user keeps their session throughout, a step-up does not sign them out of your other applications.
After a step-up, use the new tokens from that callback. If you refresh a token that was issued before the step-up, the refreshed token keeps the acr, amr and auth_time of the earlier authentication.
Tokens can carry three claims. Kinde adds them to tokens whether or not you request a context, so you can read them on each sign-in:
| Claim | Meaning |
|---|---|
acr | The context that was met: mfa or phr. Omitted when the user authenticated with a single factor. |
amr | The methods that were used, as an array. Omitted when Kinde has no record of the methods, for example for a session that started before Kinde recorded them. |
auth_time | When the user last actively authenticated, as seconds since the Unix epoch. Always present on the ID token; present on the access token when Kinde can establish it. Read it from the ID token. If step-up is not available (see Discovery), the ID token still has auth_time, but its value can be the time a token was issued rather than the time the user authenticated. |
acr is deliberately absent for a single-factor login. A single factor is the baseline every authenticated session meets. Read amr if you need to know what was used. An absent acr means “neither mfa nor phr”:
mfa or phr, an absent acr is not an error. Accept the token.mfa or phr, an absent acr does not meet the requirement. Reject the request, or start a step-up. Do the same when acr is present but lower than the requirement. phr meets an mfa requirement.Do this check on a token that you have verified. See Protecting an API with step-up.
amr values you may see:
| Value | Method |
|---|---|
pwd | Password |
passkey | Passkey, with the user verified by the device |
pop | Passkey, where the device confirmed presence but did not verify the user. On its own, this does not meet mfa or phr. |
email, sms | A one-time code used as the first factor (passwordless sign-in) |
otp:email, otp:sms | A one-time code used as a second factor |
totp | Authenticator app |
step_up:email_otp | A one-time code to the address on the user’s profile |
mfa | Recovery code |
Provider name, for example google | A social or enterprise connection |
A decoded ID token from the acr_values=mfa request above. amr lists each method that was used. In this case the user signed in with a password, then verified with an authenticator app:
{ "acr": "mfa", "amr": ["pwd", "totp"], "auth_time": 1716800000}If the user cannot satisfy the request, Kinde redirects to your callback with an error rather than issuing a token that does not meet it. For example:
phr request when passkeys are not enabled for the environment or organization, or the user has no passkey.mfa step-up from a user who has no enrolled MFA factor. A step-up only verifies a factor. It does not enrol one.mfa request on a new sign-in from a user who has no enrolled MFA factor, when your MFA policy does not ask the user to enrol one. If your policy asks the user to enrol, the user sees the usual enrolment screens and the request can be met. If the policy lets the user skip enrolment and the user skips, Kinde returns the error. The skip applies to this request only. Kinde does not record it as an opt-out.mfa request where the only factor left would prove the same channel again. For example, a one-time code to the email address or phone number that the user used for a passwordless sign-in does not count as a second factor.The error is:
302 https://YOUR_CALLBACK_URL?error=unmet_authentication_requirements&error_description=The+requested+authentication+context+could+not+be+met&state=YOUR_STATEHandle this as a definite answer, not a transient failure. Retrying the same request will produce the same error.
This error is for a requirement that the user cannot meet. If a step-up that has started fails for a different reason, Kinde shows its own error page. It does not redirect to your callback, so your app does not receive an OAuth error in that case.
With prompt=none, Kinde cannot show a screen to collect the extra factor, so it does not start a step-up:
acr_values, Kinde returns unmet_authentication_requirements.max_age, or the user has no session, Kinde returns login_required. Kinde keeps the session.In each case, send the user through an interactive request (without prompt=none) to complete the step-up.
An API that requires a stronger context for an operation must check the acr claim of the verified access token on each request. It must not trust the client to request the context.
If acr is absent, or is lower than the operation requires, return the RFC 9470 challenge:
HTTP/1.1 401 UnauthorizedWWW-Authenticate: Bearer error="insufficient_user_authentication", error_description="A different authentication level is required", acr_values="mfa"To require recent authentication, send max_age in the challenge and compare it with the auth_time claim. When your app gets this challenge, it sends the user to /oauth2/auth with the acr_values (and max_age) from the challenge, then calls the API again with the new access token.
The contexts Kinde can return are advertised at https://<your_kinde_subdomain>.kinde.com/.well-known/openid-configuration:
"acr_values_supported": ["mfa", "phr"],"claims_supported": ["aud", "exp", "iat", "iss", "sub", "acr", "amr", "auth_time"]Check for acr_values_supported before you rely on step-up. If the document does not list it, Kinde ignores acr_values and max_age, and does not add the acr and amr claims. The ID token still has auth_time, but its value can be the time a token was issued rather than the time the user authenticated. Do not use it to check recency in this case. To require a fresh sign-in, send prompt=login.
acr: "mfa" means two factors were presented and accepted under your environment’s MFA policy. It does not assert that the two factors are independent of one another - for example, a user who signs in with a social provider and then receives a one-time code at that same mailbox has proven one channel twice.
phr is stronger: a phishing site cannot use a passkey. But phr does not assert independence either. Synced passkeys qualify for phr, and a passkey can be registered from a session that proved only the first factor.
For payments and regulated workloads, assess factor independence, enrolment and recovery against your own configuration rather than relying on the acr value alone.
The Authorization Code Flow is the standard way to sign users in when your app runs on a server you control. Instead of returning tokens directly in the browser, Kinde redirects the user back to your app with a short-lived authorization code. Your server then exchanges that code for tokens by making a secure back-channel request to Kinde’s token endpoint, including your client_secret. This two-step design keeps tokens off the URL and out of the browser — the authorization code is useless on its own without your secret. Use this flow for server-rendered web applications, traditional back-end web apps, and any client that can store a client_secret securely on the server.
The Authorization Code Flow with PKCE (Proof Key for Code Exchange) works the same way, but replaces the client_secret with a one-time cryptographic proof. Before redirecting the user to Kinde, your app generates a random code_verifier and sends a hashed version (code_challenge) in the authorization request. When exchanging the authorization code for tokens, your app sends the original code_verifier instead of a secret. Kinde verifies they match, proving that the same app that started the login is completing it. This matters because single-page apps (SPAs) and mobile apps are “public clients” — their code runs on the user’s device, so a client_secret could be extracted and abused. PKCE gives you the security of the authorization code flow without needing a secret. Use PKCE for SPAs, native mobile apps, desktop apps, and any client that cannot keep a client_secret confidential.
The Implicit Flow was originally created for browser-based apps that couldn’t keep a secret — it returned the access_token directly in the URL fragment after login. That convenience came at a serious cost: tokens in URL fragments can leak through browser history, referrer headers, and server logs. The OAuth 2.0 Security Best Current Practice (RFC 9700) now explicitly recommends against using the Implicit Flow for any new application.
What to use instead: The Authorization Code Flow with PKCE is the correct choice for all public clients — including single-page apps and mobile apps — regardless of whether they can store a client secret. PKCE (Proof Key for Code Exchange) solves the same problem the Implicit Flow was trying to address, without the token-in-URL security exposure.
If you are migrating an existing app from the Implicit Flow, replace the flow type token with code and add the code_challenge and code_challenge_method parameters to your authorization request. No client secret is required.
The following scopes can be requested from Kinde.
openidRequests an id_token that contains information about the authenticated user.
emailRequests the email and email_verified claims in the id_token.
profileRequests profile details as part of the id_token, including given name, family name, and picture.
offlineRequests a refresh_token that can be used to silently refresh the access_token when it expires.
Kinde uses offline to request a refresh token. The OIDC specification defines this scope as offline_access, but Kinde does not support offline_access — using it will not return a refresh token and will not produce an error. Make sure to use offline exactly.
phoneRequests the phone_number and phone_number_verified claims in the id_token.
addressRequests the address claim in the id_token.
There are a few useful additional parameters that Kinde supports in the authorization URL.
response_typeShould always be code. Kinde does not support the Implicit Flow — see Does Kinde support the Implicit Flow? for details.
Type: string
Required: Yes
client_idThe unique ID of your application in Kinde.
Type: string
Required: Yes
redirect_uriThe URL that the user will be returned to after authentication.
Type: string
Required: Yes
scopeThe scopes to be requested from Kinde. Scopes include openid, profile, email, offline.
Type: string
Required: No
Recommended: openid profile email
Kinde uses offline to request a refresh token in place of the OIDC specification’s offline_access scope.
stateKinde will return this to your app so you can validate it came from us and prevent CSRF attacks.
Type: string
Required: Yes (min 8 characters)
You can also use the state parameter to store additional information about the user or the authentication flow.
nonceA single-use value included in the signed id_token to prevent replay attacks.
Type: string
Required: No (but recommended)
code_challengeA base64url-encoded SHA-256 hash of the code verifier.
Type: string
Required: for public clients (e.g., SPAs and mobile apps)
code_challenge_methodShould always be S256. This tells Kinde that the code challenge was generated using SHA-256.
Type: string
Required: For PKCE
promptAccepts login, create, consent, select_account, or none to control which page users land on. By default, users are directed to the sign-in page.
If you use none, Kinde will not show authentication screens to the user.
If the user has an active SSO session, this will redirect them to the callback URL where the token exchange can occur.
If the user does NOT have an active SSO session, this will redirect them to the callback URL with the following error:
302 <YOUR_CALLBACK_URL>?error=login_required&error_description=User+authentication+is+required&state=YOUR_STATEType: string
Required: No
acr_valuesRequest a minimum authentication context. Kinde recognises two values:
mfa - the user must complete a second factor. A step-up only verifies a factor that the user has already enrolled.phr - the user must complete a phishing-resistant factor: a passkey, with the user verified by the device (PIN or biometrics)If the user’s session already meets the requested context, Kinde issues tokens without prompting. If it does not, Kinde steps the user up - it asks only for the additional factor rather than making them sign in again from scratch.
Accepts a space-separated list; the highest context requested is applied. Values are case-insensitive. Unrecognised values are ignored.
Kinde also accepts some values that other providers use, so a client that you move to Kinde can keep its request: http://schemas.openid.net/pape/policies/2007/06/multi-factor and urn:okta:loa:2fa:any are read as mfa. Kinde does not return these values. When a token has an acr claim, its value is always mfa or phr. urn:okta:loa:2fa:any:ifpossible is not accepted, because Kinde cannot make an optional requirement. Kinde ignores this value.
If the requested context cannot be met - for example phr for a user with no usable passkey - Kinde returns an error to your callback rather than issuing a token that does not satisfy the request. See Step-up authentication.
Type: string
Required: No
max_ageThe maximum age, in seconds, of the user’s last authentication. If the user authenticated longer ago than this, Kinde asks them to authenticate again before issuing tokens.
If the user has a session, Kinde keeps the session. The user stays signed in to your other applications. What Kinde asks for depends on acr_values:
max_age without acr_values: the user signs in again with their first factor (for example, their password). max_age does not add a second factor. If your MFA policy requires a second factor, Kinde asks for it, as it does on every sign-in.max_age with acr_values=mfa or acr_values=phr: the user verifies only the factor that acr_values requires. Kinde does not ask for the first factor again. Kinde asks for this verification whenever the session is older than max_age, even if it already meets acr_values. The verification sets a new auth_time.Use max_age=0 to require re-authentication on every request.
Kinde ignores an empty value and the values undefined, null and NaN. Some SDKs send these values when no max_age is set. Kinde treats a different value that is not a non-negative whole number as requiring re-authentication.
Type: string
Required: No
login_hintWhen your project knows which user it is trying to authenticate, it can provide their email in this parameter as a hint to Kinde. Passing this hint pre-fills the email box on the sign-up and sign-in screens.
Type: string
Required: No
org_codeFor multi-tenant or platform apps, tell Kinde which organization a user is trying to sign in or sign up to.
Type: string
Required: No
is_create_orgSet to true to create a new organization for the user during sign-up.
Type: boolean
Required: No
org_nameIf is_create_org is true, you can optionally include the name of the organization to create.
Type: string
Required: No
audienceThe audience claim for the JWT. This can be used to protect your APIs and resource servers.
Type: string
Required: No
has_success_pageShows a success page at the end of the authentication flow. Useful when the callback URL opens a native app rather than a web page.
Type: boolean
Required: No
Sets the UI language for the authentication screens.
Type: string
Required: No
connection_idThe connection ID for the authentication method, used when implementing custom sign-up and sign-in pages.
Type: string
Required: When using custom sign-up and sign-in pages
supports_reauthWhen set to true, the user’s state is encrypted and returned to the application when they access an expired link.
Type: boolean
Required: No
reauth_stateWhen supports_reauth is true and authentication fails, the user’s state is returned alongside the error. Pass this value back to the authorization flow to resume where the user left off.
Type: string
Required: No
deploy_idThe ID of a workflow deployment to test. When set, a password will be requested on login.
Type: string
Required: No
plan_interestIndicates the plan the user has expressed interest in.
Type: string
Required: No
pricing_table_keyDefines which pricing table to display in the billing flow.
Type: string
Required: No
start_page (deprecated)Accepts login or registration. Use prompt instead.
Type: string
Required: No
id_token?Decoding the id_token is usually the fastest approach — it requires no extra network request. However, there are three situations where calling the userinfo endpoint is the right choice:
id_token is a snapshot frozen at login time. If the user updates their name, email, or avatar in Kinde after they’ve authenticated, the id_token stays stale until they get a new one. The userinfo endpoint queries Kinde live and always returns current data.access_token. The id_token is typically held by the frontend. If your server needs to look up the authenticated user’s profile, it can call the userinfo endpoint using the access_token from the request header — without needing the frontend to forward the id_token separately.openid scope. Without openid, no id_token is issued. The userinfo endpoint still works as long as you hold a valid access_token.In all other cases, prefer decoding the id_token — it’s faster and requires no round trip to Kinde.
To call the userinfo endpoint, pass the access_token as a Bearer token:
GET https://<YOUR_DOMAIN>/oauth2/v2/user_profileAuthorization: Bearer <access_token>The response returns the user’s current profile fields:
{ "sub": "kp_7a4b2c...", "given_name": "Jane", "family_name": "Doe", "email": "jane@example.com", "picture": "https://gravatar.com/..."}The fields returned depend on the scopes that were granted. email and email_verified require the email scope. given_name, family_name, and picture require the profile scope.