Kinde Management API Quickstart Guide
SDKs and APIs
Use the OAuth2 client credentials flow to get a machine-to-machine (M2M) access token and make authenticated requests to the Kinde Management API.
Get the app keys for your M2M application (Domain, Client ID, and Client Secret).
Make a POST request to the https://<your_subdomain>.kinde.com/oauth2/token endpoint using your preferred programming language:
Make sure to replace <your_subdomain>, <your_m2m_client_id> and <your_m2m_client_secret> with your own details.
curl --request POST \ --url 'https://<your_subdomain>.kinde.com/oauth2/token' \ --header 'content-type: application/x-www-form-urlencoded' \ --data grant_type=client_credentials \ --data 'client_id=<your_m2m_client_id>' \ --data 'client_secret=<your_m2m_client_secret>' \ --data 'audience=https://<your_subdomain>.kinde.com/api'var client = new RestClient("https://<your_subdomain>.kinde.com/oauth2/token");var request = new RestRequest(Method.POST);request.AddHeader("content-type", "application/x-www-form-urlencoded");request.AddParameter("application/x-www-form-urlencoded", "grant_type=client_credentials&client_id=<your_m2m_client_id>&client_secret=<your_m2m_client_secret>&audience=https%3A%2F%2F<your_subdomain>.kinde.com%2Fapi", ParameterType.RequestBody);IRestResponse response = client.Execute(request);package main
import ( "fmt" "io/ioutil" "net/http" "strings")
func main() {
url := "https://<your_subdomain>.kinde.com/oauth2/token"
payload := strings.NewReader("grant_type=client_credentials&client_id=<your_m2m_client_id>&client_secret=<your_m2m_client_secret>&audience=https%3A%2F%2F<your_subdomain>.kinde.com%2Fapi")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("content-type", "application/x-www-form-urlencoded")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body)
fmt.Println(res) fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://<your_subdomain>.kinde.com/oauth2/token") .header("content-type", "application/x-www-form-urlencoded") .body("grant_type=client_credentials&client_id=<your_m2m_client_id>&client_secret=<your_m2m_client_secret>&audience=https%3A%2F%2F<your_subdomain>.kinde.com%2Fapi") .asString();async function getToken() { try { const response = await fetch(`https://<your_subdomain>.kinde.com/oauth2/token`, { method: "POST", headers: { "content-type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ audience: "https://<your_subdomain>.kinde.com/api", grant_type: "client_credentials", client_id: "<your_m2m_client_id>", client_secret: "<your_m2m_client_secret>" }) });
if (!response.ok) { throw new Error(`Response status: ${response.status}`); }
const json = await response.json(); console.log(json); } catch (error) { console.error(error.message); }}
getToken();#import <Foundation/Foundation.h>
NSDictionary *headers = @{ @"content-type": @"application/x-www-form-urlencoded" };
NSMutableData *postData = [[NSMutableData alloc] initWithData:[@"grant_type=client_credentials" dataUsingEncoding:NSUTF8StringEncoding]];[postData appendData:[@"&client_id=<your_m2m_client_id>" dataUsingEncoding:NSUTF8StringEncoding]];[postData appendData:[@"&client_secret=<your_m2m_client_secret>" dataUsingEncoding:NSUTF8StringEncoding]];[postData appendData:[@"&audience=https://<your_subdomain>.kinde.com/api" dataUsingEncoding:NSUTF8StringEncoding]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://<your_subdomain>.kinde.com/oauth2/token"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0];[request setHTTPMethod:@"POST"];[request setAllHTTPHeaderFields:headers];[request setHTTPBody:postData];
NSURLSession *session = [NSURLSession sharedSession];NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { NSLog(@"%@", error); } else { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; NSLog(@"%@", httpResponse); } }];[dataTask resume];$curl = curl_init();
curl_setopt_array($curl, [ CURLOPT_URL => "https://<your_subdomain>.kinde.com/oauth2/token", CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => "", CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "POST", CURLOPT_POSTFIELDS => "grant_type=client_credentials&client_id=<your_m2m_client_id>&client_secret=<your_m2m_client_secret>&audience=https%3A%2F%2F<your_subdomain>.kinde.com%2Fapi", CURLOPT_HTTPHEADER => [ "content-type: application/x-www-form-urlencoded" ],]);
$response = curl_exec($curl);$err = curl_error($curl);
curl_close($curl);
if ($err) { echo "cURL Error #:" . $err;} else { echo $response;}import http.client
conn = http.client.HTTPSConnection("<your_subdomain>.kinde.com")
payload = "grant_type=client_credentials&client_id=<your_m2m_client_id>&client_secret=<your_m2m_client_secret>&audience=https%3A%2F%2F<your_subdomain>.kinde.com%2Fapi"
headers = { 'content-type': "application/x-www-form-urlencoded" }
conn.request("POST", "/oauth2/token", payload, headers)
res = conn.getresponse()data = res.read()
print(data.decode("utf-8"))require 'uri'require 'net/http'
url = URI("https://<your_subdomain>.kinde.com/oauth2/token")
http = Net::HTTP.new(url.host, url.port)http.use_ssl = true
request = Net::HTTP::Post.new(url)request["content-type"] = 'application/x-www-form-urlencoded'request.body = "grant_type=client_credentials&client_id=<your_m2m_client_id>&client_secret=<your_m2m_client_secret>&audience=https%3A%2F%2F<your_subdomain>.kinde.com%2Fapi"
response = http.request(request)puts response.read_bodyimport Foundation
let headers = ["content-type": "application/x-www-form-urlencoded"]
let postData = NSMutableData(data: "grant_type=client_credentials".data(using: String.Encoding.utf8)!)postData.append("&client_id=<your_m2m_client_id>".data(using: String.Encoding.utf8)!)postData.append("&client_secret=<your_m2m_client_secret>".data(using: String.Encoding.utf8)!)postData.append("&audience=https://<your_subdomain>.kinde.com/api".data(using: String.Encoding.utf8)!)
let request = NSMutableURLRequest(url: NSURL(string: "https://<your_subdomain>.kinde.com/oauth2/token")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)request.httpMethod = "POST"request.allHTTPHeaderFields = headersrequest.httpBody = postData as Data
let session = URLSession.sharedlet dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) }})
dataTask.resume()The response includes an access token:
{ "access_token": "<your_access_token>", // the JWT access token "expires_in": 86399, "scope": "", // any subset of the scopes you defined in the request "token_type": "Bearer",}The scope field in the response JSON will be empty if you did not request a subset of scopes.
The access_token field will contain a signed JWT for you to use in the API requests. Use the Kinde Online JWT decoder to decode the token.
Here is the decoded M2M access token:
{ "aud": [ "https://<your_subdomain>.kinde.com/api" ], "azp": "<your_m2m_client_id>", "exp": 1777887031, "gty": [ "client_credentials" ], "iat": 1777800631, "iss": "https://<your_subdomain>.kinde.com", "jti": "61859f4b-75e1-4ac8-bda7-8a69a5f6660e", "scope": "create:users read:users", "scp": [], "v": "2"}When you use a subset of scopes in the token, the scp field will also include the currently requested scopes. Otherwise, it will be empty.
Authorization header of your request. For example, to retrieve all users:curl --request GET \ --url 'https://<your_subdomain>.kinde.com/api/v1/users' \ --header 'authorization: Bearer <m2m_access_token>' \ --header 'content-type: application/json'var client = new RestClient("https://<your_subdomain>.kinde.com/api/v1/users");var request = new RestRequest(Method.GET);request.AddHeader("content-type", "application/json");request.AddHeader("authorization", "Bearer <m2m_access_token>");IRestResponse response = client.Execute(request);package main
import ( "fmt" "io/ioutil" "net/http")
func main() {
url := "https://<your_subdomain>.kinde.com/api/v1/users"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("content-type", "application/json") req.Header.Add("authorization", "Bearer <m2m_access_token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close() body, _ := ioutil.ReadAll(res.Body)
fmt.Println(res) fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://<your_subdomain>.kinde.com/api/v1/users") .header("content-type", "application/json") .header("authorization", "Bearer <m2m_access_token>") .asString();async function getUsers() { try { const response = await fetch(`https://<your_subdomain>.kinde.com/api/v1/users`, { method: "GET", headers: { "content-type": "application/json", authorization: "Bearer <m2m_access_token>" } });
if (!response.ok) { throw new Error(`Response status: ${response.status}`); }
const json = await response.json(); console.log(json); } catch (error) { console.error(error.message); }}
getUsers();#import <Foundation/Foundation.h>
NSDictionary *headers = @{ @"content-type": @"application/json", @"authorization": @"Bearer <m2m_access_token>" };
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"https://<your_subdomain>.kinde.com/api/v1/users"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10.0];[request setHTTPMethod:@"GET"];[request setAllHTTPHeaderFields:headers];
NSURLSession *session = [NSURLSession sharedSession];NSURLSessionDataTask *dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) { if (error) { NSLog(@"%@", error); } else { NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response; NSLog(@"%@", httpResponse); } }];[dataTask resume];$curl = curl_init();
curl_setopt_array($curl, [ CURLOPT_URL => "https://<your_subdomain>.kinde.com/api/v1/users", CURLOPT_RETURNTRANSFER => true, CURLOPT_ENCODING => "", CURLOPT_MAXREDIRS => 10, CURLOPT_TIMEOUT => 30, CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1, CURLOPT_CUSTOMREQUEST => "GET", CURLOPT_HTTPHEADER => [ "authorization: Bearer <m2m_access_token>", "content-type: application/json" ],]);
$response = curl_exec($curl);$err = curl_error($curl);
curl_close($curl);
if ($err) { echo "cURL Error #:" . $err;} else { echo $response;}import http.client
conn = http.client.HTTPSConnection("<your_subdomain>.kinde.com")
headers = { 'content-type': "application/json", 'authorization': "Bearer <m2m_access_token>"}
conn.request("GET", "/api/v1/users", headers=headers)
res = conn.getresponse()data = res.read()
print(data.decode("utf-8"))require 'uri'require 'net/http'
url = URI("https://<your_subdomain>.kinde.com/api/v1/users")
http = Net::HTTP.new(url.host, url.port)http.use_ssl = true
request = Net::HTTP::Get.new(url)request["content-type"] = 'application/json'request["authorization"] = 'Bearer <m2m_access_token>'
response = http.request(request)puts response.read_bodyimport Foundation
let headers = [ "content-type": "application/json", "authorization": "Bearer <m2m_access_token>"]
let request = NSMutableURLRequest(url: NSURL(string: "https://<your_subdomain>.kinde.com/api/v1/users")! as URL, cachePolicy: .useProtocolCachePolicy, timeoutInterval: 10.0)request.httpMethod = "GET"request.allHTTPHeaderFields = headers
let session = URLSession.sharedlet dataTask = session.dataTask(with: request as URLRequest, completionHandler: { (data, response, error) -> Void in if (error != nil) { print(error) } else { let httpResponse = response as? HTTPURLResponse print(httpResponse) }})
dataTask.resume()You can include an optional scope parameter in the request to the /oauth2/token endpoint to request a subset of scopes for the token.
For example, if you have enabled the following scopes for your M2M application:
create:usersread:usersupdate:usersdelete:usersUsing the scope parameter, you can request a subset of those scopes for the token — in this case, create:users and read:users.
curl --request POST \ --url 'https://<your_subdomain>.kinde.com/oauth2/token' \ --header 'content-type: application/x-www-form-urlencoded' \ --data grant_type=client_credentials \ --data 'client_id=<your_m2m_client_id>' \ --data 'client_secret=<your_m2m_client_secret>' \ --data 'audience=https://<your_subdomain>.kinde.com/api' \ --data 'scope=create:users read:users'The token will be scoped to only the permissions you requested, which reduces the attack surface and limits exposure if the token is compromised.
An M2M token is generated each time you call the /oauth2/token Kinde API endpoint to retrieve an M2M access token.
Where an access token is re-used - say where the same token is used to make another API request, this does not count as a new token. Similarly, where a token is re-used to make calls to other Kinde-registered APIs, this is also not counted as another token.
Kinde’s free and Pro plans have a generous amount of M2M tokens included, before we start charging for them. For details, see our pricing page.
With a working access token, you can now call the Management API to automate account actions. See the Kinde Management API for all available endpoints.