Set up Kinde Management API access
SDKs and APIs
This guide shows you how to manage your Kinde configuration as code across dev, staging, and prod. Because Kinde environments are isolated and changes don’t auto-promote, we’ll treat your setup like any other piece of infrastructure: versioned, repeatable, and automated through the Kinde Management API.
In this guide we will use a JavaScript seeding script to create configurations using the Kinde Management API.
Select the environment drop-down in the top left of the Kinde home screen, and select All environments.
Select Add environment. A dialog opens.
Enter a Name for the environment (e.g, Development)
Enter a Code for the environment to reference it in code dev.
Select Save.
Create any additional environments you want to create (e.g. Staging, Testing).
Kinde’s Free plan allows you to create up to 2 environments.
On the Kinde home screen, select Add application.
Enter a name for the application (e.g, Kinde Seed Script) and select Machine to machine (M2M) as the application type.
Select Save. The details page for the application opens.
Take note of the Domain, Client ID and Client secret. You will need it to run your seed script in the next step.
Go to APIs and select the three dots next to Kinde Management API and select Authorize application.
Copy the Audience value to configure your Kinde script in the next step.
Select the three dots again and select Manage scopes.
Enable the following scopes and select Save.
To create application and add auth redirect and auth logout URLS: create:applications, create:application_redirect_uris, create:application_logout_uris
To create environment variables: create:environment_variables
To create feature flags: create:feature_flags
To create API and scopes: create:apis, create:api_scopes
To create roles and permissions: create:roles, create:permissions, update:role_permissions
Do the above for all of your environments, and you will have an environment and an M2M application credential set for prod, dev, and staging environments.
Create a new directory and switch to it using the following bash command in your terminal:
mkdir kinde-seed-scriptcd kinde-seed-scriptCreate the configuration files:
mkdir configtouch config/dev.json config/prod.json config/staging.jsonEnter the following seed data into your config files and save changes. Modify the sample data below, according to your needs.
{ "application": { "name": "My Kinde App, Created with Script", "type": "reg", "redirectUrls": ["http://localhost:3000/callback"], "logoutUrls": ["http://localhost:3000/"] }, "apis": [ { "key": "orders-api", "name": "Orders API", "audience": "orders", "scopes": ["orders:read", "orders:write"] } ], "roles": [ { "key": "admin", "name": "Admin", "permissions": [ { "key": "orders:read", "name": "Read Orders" }, { "key": "orders:write", "name": "Write Orders" } ] } ], "featureFlags": [ { "name": "New Checkout", "key": "new_checkout", "type": "bool", "allow_override_level": "env", "default_value": true } ], "envVars": [ { "key": "PAYMENTS_GATEWAY", "value": "sandbox", "sensitive": false }, { "key": "SECRET_PAYMENTS_GATEWAY", "value": "sandbox", "sensitive": true } ]}Create the seed script with the following command.
mkdir scriptstouch scripts/seed-kinde.mjsEnter the following code into the seed script and save changes. Use the function calls you need for your specific use case.
import fs from "node:fs/promises"
const { KINDE_DOMAIN, KINDE_CLIENT_ID, KINDE_CLIENT_SECRET, KINDE_AUDIENCE, KINDE_SCOPES, CONFIG_PATH,} = process.env
if ( !KINDE_DOMAIN || !KINDE_CLIENT_ID || !KINDE_CLIENT_SECRET || !KINDE_AUDIENCE) { throw new Error("Missing Kinde env vars")}
const cfg = JSON.parse(await fs.readFile(CONFIG_PATH, "utf8"))
async function getToken() { const body = new URLSearchParams({ grant_type: "client_credentials", client_id: KINDE_CLIENT_ID, client_secret: KINDE_CLIENT_SECRET, audience: KINDE_AUDIENCE, scope: KINDE_SCOPES, })
const res = await fetch(`https://${KINDE_DOMAIN}/oauth2/token`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body, })
if (!res.ok) { const text = await res.text() // <-- see why it failed throw new Error(`Token error ${res.status}, ${text}`) } const json = await res.json() return json.access_token}
function client(token) { const base = `https://${process.env.KINDE_DOMAIN}` async function call(method, path, body) { const url = new URL(`/api/v1${path}`, base) const res = await fetch(url, { method, headers: { "content-type": "application/json", authorization: `Bearer ${token}`, }, body: body ? JSON.stringify(body) : undefined, }) if (!res.ok) { throw new Error(`${method} ${base} ${path} -> ${res.status}`) } return res.status === 204 ? null : res.json() } return { call }}
// Helper functions
async function createApplicationWithUrls( api, name, type, redirects = [], logouts = []) { console.log("Creating application...")
const errors = [] let appObj = {}
try { appObj = await api.call("POST", "/applications", { name, type, }) } catch { console.log("Cannot create an application, exiting the function") return }
const { id } = appObj.application
console.log("Creating auth redirect URLs...")
try { await api.call("POST", `/applications/${id}/auth_redirect_urls`, { urls: redirects, }) } catch (error) { errors.push("Cannot create redirect urls") }
console.log("Creating auth logout URLs...")
try { await api.call("POST", `/applications/${id}/auth_logout_urls`, { urls: logouts, }) } catch (error) { errors.push("Cannot create logout urls") }
console.log("Created a Kinde application with id", id) if (errors.length) console.log("with some errors", errors)}
async function createEnvVariable(api, item) { console.log("Creating environment variable...")
try { const { key, value, sensitive = false } = item await api.call("POST", `/environment_variables`, { key, value, is_secret: sensitive, }) console.log("Created environment variable", item.key) } catch { console.log("Could not create env variable", item.key) }}
async function createFeatureFlag(api, flag) { try { await api.call("POST", `/feature_flags`, flag) console.log("Created feature flag:", flag.name) } catch { console.log("Could not create feature flag:", flag.name) }}
async function createApiAndScopes(api, { name, audience, scopes = [] }) { console.log("Creating API and Scopes...")
let apiObj = null
try { apiObj = await api.call("POST", `/apis`, { name, audience }) apiObj.name = name console.log("Created API:", name) } catch { console.log("Error creating API:", name) return }
// You will need a Kinde paid plan // To add scope to your API
// Un-comment this block to use it: // for (const s of scopes) { // try { // await api.call("POST", `/apis/${apiObj.api.id}/scopes`, { // key: s, // description: s, // }) // console.log(`Created scope "${s}" to the API ${apiObj.name}`) // } catch (error) { // console.log("Error creating scope:", s) // } // }}
async function createRoleAndPermissions(api, role) { console.log("Creating roles and permissions...")
let roleObj = null
try { roleObj = await api.call("POST", `/roles`, { key: role.key, name: role.name, }) roleObj.name = role.name console.log("Created role:", role.name) } catch { console.log("Error creating role:", role.name) console.log("Exiting.")
return }
const permObjs = [] let permObj = null
for (const p of role.permissions) { try { permObj = await api.call("POST", `/permissions`, { key: p.key, name: p.name, }) permObj.name = p.name console.log("Permission created:", p.name) permObjs.push(permObj) } catch { console.log("Error creating the permission:", p.name) } }
for (const perm of permObjs) { const { id } = perm.permission try { await api.call("PATCH", `/roles/${roleObj.role.id}/permissions`, { permissions: [{ id }], }) console.log( `Added the permission "${perm.name}" to the role ${roleObj.name}` ) } catch { console.log("Error creating the permission") } }}
// Token and create the clientconst token = await getToken()const api = client(token)
// Destructuring the valuesconst { name, type, redirectUrls, logoutUrls } = cfg.applicationconst { envVars, apis, featureFlags, roles } = cfg
// Execute the scriptawait Promise.all([ createApplicationWithUrls(api, name, type, redirectUrls, logoutUrls), ...(envVars ?? []).map((v) => createEnvVariable(api, v)), ...(apis ?? []).map((a) => createApiAndScopes(api, a)), ...(featureFlags ?? []).map((f) => createFeatureFlag(api, f)), ...(roles ?? []).map((r) => createRoleAndPermissions(api, r)),])
console.log("Kinde Seed Complete")Run the following code in your terminal to set your environment variables:
export KINDE_DOMAIN=<YOUR_KINDE_DOMAIN>.kinde.comexport KINDE_CLIENT_ID=<KINDE_CLIENT_ID>export KINDE_CLIENT_SECRET=<KINDE_CLIENT_SECRET>export KINDE_AUDIENCE=https://<YOUR_KINDE_DOMAIN>.kinde.com/apiexport KINDE_SCOPES="create:applications create:application_redirect_uris create:application_logout_uris create:environment_variables create:feature_flags create:apis create:api_scopes create:roles create:permissions update:role_permissions"export CONFIG_PATH=./config/dev.jsonRun the script with the following terminal command. You should see a success message without any errors.
node scripts/seed-kinde.mjsOutput:
Creating application...Creating environment variable...Creating environment variable...Creating API and Scopes...Creating roles and permissions...Creating auth redirect URLs...Created role: AdminCreated API: Orders APICreated environment variable PAYMENTS_GATEWAYCreated environment variable SECRET_PAYMENTS_GATEWAYCreated feature flag: New CheckoutCreating auth logout URLs...Permission created: Read OrdersCreated a Kinde application with id <created_app_id>Permission created: Write OrdersAdded the permission "Read Orders" to the role AdminAdded the permission "Write Orders" to the role AdminKinde Seed completeGo to your Kinde dashboard and switch to the correct environment.
You will see the script created:
A new application in your dashboard with your designated redirect and logout URLs.
Environment variables:
Feature flags:
APIs and scopes (requires a paid plan):
Admin roles, permissions, and add the permissions to the role.
Run the same script for all of your environments and config presets as required.
Your Kinde configuration is now managed as code across dev, staging, and prod. Choose the fit for your team and CI: least-privilege M2M per environment, review changes via PRs, and re-apply to promote config between environments. With this setup, your Kinde stack is reproducible, auditable, and easy to roll forward, no manual clicks required.