The Kinde SDK for Python.
You can also use the Python starter kit here.
If you're upgrading from Kinde Python SDK v1, the API has changed significantly. The KindeClient class has been completely replaced with OAuth.
📖 Migration Guide - Complete step-by-step migration instructions
📋 Quick Reference - At-a-glance v1 to v2 conversion table
KindeClient→OAuth(main authentication class)client.get_flag()→await feature_flags.get_flag()(feature flags)client.get_permission()→await permissions.get_permission()(permissions)- Most operations are now asynchronous
For details on integrating this SDK into your project, head over to the Kinde docs and see the Python SDK doc 👍🏼.
The Kinde Python SDK provides seamless integration with popular Python web frameworks. Below are detailed guides for using Kinde with FastAPI and Flask.
The kinde_fastapi module provides easy integration with FastAPI applications.
pip install fastapi uvicorn python-multipartfromfastapiimportFastAPIfromkinde_sdk.auth.oauthimportOAuth# Initialize FastAPI appapp=FastAPI()
# Initialize Kinde OAuth with FastAPI frameworkkinde_oauth=OAuth(
framework="fastapi",
app=app
)
# Example home route@app.get("/")asyncdefhome(request: Request):
ifkinde_oauth.is_authenticated():
user=kinde_oauth.get_user_info()
returnf"Welcome, {user.get('email', 'User')}!"return"Please log in"Create a .env file with your Kinde credentials:
KINDE_CLIENT_ID=your_client_idKINDE_CLIENT_SECRET=your_client_secretKINDE_REDIRECT_URI=http://localhost:8000/callbackKINDE_DOMAIN=your_kinde_domainThe FastAPI integration automatically provides these routes:
/login- Redirects to Kinde login/callback- Handles OAuth callback/logout- Logs out the user/register- Redirects to Kinde registration/user- Returns user information
fromfastapiimportHTTPException@app.get("/protected")asyncdefprotected_route():
ifnotkinde_oauth.is_authenticated():
raiseHTTPException(status_code=401, detail="Not authenticated")
return {"message": "This is a protected route"}The kinde_flask module provides easy integration with Flask applications.
pip install flask python-dotenv flask-sessionfromflaskimportFlaskfromkinde_sdk.auth.oauthimportOAuth# Initialize Flask appapp=Flask(__name__)
# Configure Flask sessionapp.config['SECRET_KEY'] ='your-secret-key'app.config['SESSION_TYPE'] ='filesystem'app.config['SESSION_PERMANENT'] =False# Initialize Kinde OAuth with Flask frameworkkinde_oauth=OAuth(
framework="flask",
app=app
)
# Example home route@app.route('/')defhome():
ifkinde_oauth.is_authenticated():
user=kinde_oauth.get_user_info()
returnf"Welcome, {user.get('email', 'User')}!"return"Please log in"Create a .env file with your Kinde credentials:
KINDE_CLIENT_ID=your_client_idKINDE_CLIENT_SECRET=your_client_secretKINDE_REDIRECT_URI=http://localhost:5000/callbackKINDE_DOMAIN=your_kinde_domainThe Flask integration automatically provides these routes:
/login- Redirects to Kinde login/callback- Handles OAuth callback/logout- Logs out the user/register- Redirects to Kinde registration/user- Returns user information
fromfunctoolsimportwrapsfromflaskimportsession, redirectdeflogin_required(f):
@wraps(f)defdecorated_function(*args, **kwargs):
ifnotkinde_oauth.is_authenticated():
returnredirect('/login')
returnf(*args, **kwargs)
returndecorated_function@app.route('/protected')@login_requireddefprotected_route():
return {"message": "This is a protected route"}For both FastAPI and Flask integrations:
- Always use HTTPS in production
- Use a secure session secret key
- Implement proper state parameter validation
- Handle OAuth errors appropriately
- Implement proper session management
- Consider implementing CSRF protection
This module provides a client for the Kinde Management API, allowing you to manage users, organizations, roles, permissions, and feature flags programmatically.
Note for v1 users: The Management API usage has changed in v2. See the Migration Guide for details on the new
ManagementClientclass.
No additional installation is required if you already have the Kinde Python SDK installed. The Management API module is included as part of the SDK.
The Management API client requires:
- Your Kinde domain
- Client ID
- Client secret
Create a ManagementClient with your M2M application's client credentials. It
authenticates automatically using the client_credentials grant — no callback
URL or grant type is required.
fromkinde_sdk.managementimportManagementClient# Initialize the client with client credentials (M2M application)management=ManagementClient(
domain="your-domain.kinde.com",
client_id="your-client-id",
client_secret="your-client-secret",
)Each API group is exposed as a <resource>_api attribute, e.g.
management.users_api, management.organizations_api, management.roles_api.
The examples below use these resource APIs. Note that the client is
synchronous — calls are not awaited.
fromkinde_sdk.management.models.create_user_requestimportCreateUserRequestfromkinde_sdk.management.models.create_user_request_profileimportCreateUserRequestProfilefromkinde_sdk.management.models.create_user_request_identities_innerimportCreateUserRequestIdentitiesInnerfromkinde_sdk.management.models.update_user_requestimportUpdateUserRequest# List usersusers=management.users_api.get_users(page_size=10)
# Get a specific useruser=management.users_api.get_user_data(id="user_id")
# Create a new usernew_user=management.users_api.create_user(
create_user_request=CreateUserRequest(
profile=CreateUserRequestProfile(given_name="John", family_name="Doe"),
identities=[
CreateUserRequestIdentitiesInner(
type="email",
details={"email": "john.doe@example.com"},
)
],
)
)
# Update a userupdated_user=management.users_api.update_user(
id="user_id",
update_user_request=UpdateUserRequest(given_name="John", family_name="Smith"),
)
# Delete a userresult=management.users_api.delete_user(id="user_id")fromkinde_sdk.management.models.create_organization_requestimportCreateOrganizationRequestfromkinde_sdk.management.models.update_organization_requestimportUpdateOrganizationRequest# List organizationsorganizations=management.organizations_api.get_organizations(page_size=10)
# Get a specific organizationorg=management.organizations_api.get_organization(code="org_code")
# Create a new organizationnew_org=management.organizations_api.create_organization(
create_organization_request=CreateOrganizationRequest(name="Example Organization")
)
# Update an organizationupdated_org=management.organizations_api.update_organization(
org_code="org_code",
update_organization_request=UpdateOrganizationRequest(name="Updated Organization Name"),
)
# Delete an organizationresult=management.organizations_api.delete_organization(org_code="org_code")fromkinde_sdk.management.models.create_organization_invite_requestimportCreateOrganizationInviteRequest# List invites for an organizationinvites=management.organizations_api.get_organization_invites(org_code="org_code")
# Create an invitenew_invite=management.organizations_api.create_organization_invite(
org_code="org_code",
create_organization_invite_request=CreateOrganizationInviteRequest(
email="invitee@example.com",
first_name="Jane",
last_name="Doe",
roles=["member"], # role keys to assign on acceptance
),
)
# Get a single inviteinvite=management.organizations_api.get_organization_invite(
org_code="org_code", invite_code="invite_code"
)
# Delete an inviteresult=management.organizations_api.delete_organization_invite(
org_code="org_code", invite_code="invite_code"
)fromkinde_sdk.management.models.create_role_requestimportCreateRoleRequestfromkinde_sdk.management.models.update_roles_requestimportUpdateRolesRequest# List rolesroles=management.roles_api.get_roles(page_size=10)
# Get a specific rolerole=management.roles_api.get_role(role_id="role_id")
# Create a new rolenew_role=management.roles_api.create_role(
create_role_request=CreateRoleRequest(
name="Admin",
description="Administrator role",
key="admin_role",
)
)
# Update a roleupdated_role=management.roles_api.update_roles(
role_id="role_id",
update_roles_request=UpdateRolesRequest(
name="Super Admin",
key="admin_role",
description="Super administrator role",
),
)
# Delete a roleresult=management.roles_api.delete_role(role_id="role_id")Feature flags are created against your business and read back per organization or environment.
fromkinde_sdk.management.models.create_feature_flag_requestimportCreateFeatureFlagRequest# List feature flags for an organizationflags=management.organizations_api.get_organization_feature_flags(org_code="org_code")
# Create a new feature flagnew_flag=management.feature_flags_api.create_feature_flag(
create_feature_flag_request=CreateFeatureFlagRequest(
name="Dark Mode",
key="dark_mode",
description="Enable dark mode theme",
type="bool",
allow_override_level="env",
default_value="false",
)
)
# Update a feature flag (identified by its key)updated_flag=management.feature_flags_api.update_feature_flag(
feature_flag_key="dark_mode",
name="Dark Theme",
description="Enable dark theme for the application",
type="bool",
allow_override_level="env",
default_value="false",
)
# Delete a feature flagresult=management.feature_flags_api.delete_feature_flag(feature_flag_key="dark_mode")The Management API client automatically handles token management using client credentials:
- Tokens are automatically obtained when needed
- Tokens are cached to avoid unnecessary requests
- Tokens are refreshed when they expire
- Multiple instances of the client with the same domain and client ID share the same token
All API methods can raise exceptions for HTTP errors. It's recommended to wrap calls in try/except blocks:
try:
user=management.users_api.get_user_data(id="non_existent_id")
exceptExceptionase:
print(f"Error: {e}")Complete example given below
fromkinde_sdk.managementimportManagementClientfromkinde_sdk.management.models.create_user_requestimportCreateUserRequestfromkinde_sdk.management.models.create_user_request_profileimportCreateUserRequestProfilefromkinde_sdk.management.models.create_user_request_identities_innerimportCreateUserRequestIdentitiesInnerfromkinde_sdk.management.models.create_organization_requestimportCreateOrganizationRequestdefmain():
"""Demonstrates Management API usage with the synchronous ManagementClient."""# Initialize with your M2M application's client credentials.management=ManagementClient(
domain="your-domain.kinde.com", # Replace with your Kinde domainclient_id="your-client-id", # Your M2M client IDclient_secret="your-client-secret", # Your M2M client secret
)
user_id=Noneorg_code=None# Example 1: List usersprint("Example 1: List users")
print("-"*50)
users_result=management.users_api.get_users(page_size=10)
foruserinusers_result.usersor []:
print(f"User: {user.first_name}{user.last_name} ({user.email})")
print()
# Example 2: Create a new userprint("Example 2: Create a new user")
print("-"*50)
try:
new_user=management.users_api.create_user(
create_user_request=CreateUserRequest(
profile=CreateUserRequestProfile(given_name="Test", family_name="User"),
identities=[
CreateUserRequestIdentitiesInner(
type="email",
details={"email": "testuser@example.com"},
)
],
)
)
user_id=new_user.idprint(f"User created: {user_id}")
exceptExceptionase:
print(f"Error creating user: {e}")
print()
# Example 3: List organizationsprint("Example 3: List organizations")
print("-"*50)
orgs_result=management.organizations_api.get_organizations(page_size=10)
fororginorgs_result.organizationsor []:
print(f"Organization: {org.name} (Code: {org.code})")
print()
# Example 4: Create a new organizationprint("Example 4: Create a new organization")
print("-"*50)
try:
new_org=management.organizations_api.create_organization(
create_organization_request=CreateOrganizationRequest(name="Test Organization")
)
org_code=new_org.organization.codeprint(f"Organization created: {org_code}")
exceptExceptionase:
print(f"Error creating organization: {e}")
print()
# Example 5: List organization invitesiforg_code:
print("Example 5: List organization invites")
print("-"*50)
try:
invites=management.organizations_api.get_organization_invites(org_code=org_code)
print(f"Invites: {invites}")
exceptExceptionase:
print(f"Error listing invites: {e}")
print()
# Example 6: Clean up created resourcesprint("Example 6: Clean up")
print("-"*50)
iforg_code:
try:
management.organizations_api.delete_organization(org_code=org_code)
print("Organization deleted")
exceptExceptionase:
print(f"Error deleting organization: {e}")
ifuser_id:
try:
management.users_api.delete_user(id=user_id)
print("User deleted")
exceptExceptionase:
print(f"Error deleting user: {e}")
if__name__=="__main__":
main()This section covers direct interaction with the StorageManager for custom storage solutions.
This is considered an advanced approach. For most use cases, the framework integrations provide sufficient storage handling.
Note for v1 users: Storage management has been completely redesigned in v2. See the Migration Guide for details on the new storage abstraction layer.
fromkinde_sdk.authimportOAuthfromkinde_sdk.core.storageimportStorageManager# Basic initialization via OAuth# This is the recommended way to initialize the storage system# OAuth automatically initializes the StorageManager with the provided configoauth=OAuth(
client_id="your_client_id",
client_secret="your_client_secret",
redirect_uri="your_redirect_uri"
)
# Direct access to the storage manager# This is safe to use after OAuth initializationstorage_manager=StorageManager()
# Store authentication datastorage_manager.set("user_tokens", {
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_at": 1678901234
})
# Retrieve tokenstokens=storage_manager.get("user_tokens")
iftokens:
access_token=tokens.get("access_token")
# Use the access token for API requests# Delete tokens when logging outstorage_manager.delete("user_tokens")oauth=OAuth(
client_id="your_client_id",
storage_config={
"type": "local_storage",
"options": {
# backend-specific options
}
}
)The StorageManager automatically assigns a unique device ID to each client instance, ensuring that the same user logged in on different devices won't experience session clashes. Keys are namespaced with the device ID by default.
# Get the current device IDdevice_id=storage_manager.get_device_id()
print(f"Current device ID: {device_id}")
# Clear all data for the current device (useful for logout)storage_manager.clear_device_data()
# For data that should be shared across all devices for the same user# Use the "user:" prefixstorage_manager.set("user:shared_preferences", {"theme": "dark"})
# For data that should be global across all users and devices# Use the "global:" prefixstorage_manager.set("global:app_settings", {"version": "1.0.0"})Always initialize OAuth first: The OAuth constructor initializes the StorageManager, so create your OAuth instance before accessing the storage.
Manual initialization (if needed): If you need to use StorageManager before creating an OAuth instance, explicitly initialize it first:
# Manual initializationstorage_manager=StorageManager()
storage_manager.initialize({"type": "memory"}) # or your preferred storage config# You can also provide a specific device IDstorage_manager.initialize(
config={"type": "memory"},
device_id="custom-device-identifier"
)
# Now safe to usestorage_manager.set("some_key", {"some": "value"})- Safe access pattern: If you're unsure about initialization status, you can use this pattern:
storage_manager=StorageManager()
ifnotstorage_manager._initialized:
storage_manager.initialize()
# Now safe to usedata=storage_manager.get("some_key")Single configuration: Configure the storage only once at application startup. Changing storage configuration mid-operation may lead to data inconsistency.
Access from anywhere: After initialization, you can safely access the StorageManager from any part of your application without passing it around.
Device-specific data: Understand that by default, data is stored with device-specific namespacing. To share data across devices, use the appropriate prefixes.
Complete logout: To ensure all device-specific data is cleared during logout, call
storage_manager.clear_device_data().
The implementation generates headers in the exact format specified:
No Framework: Python/2.0.0
With Framework: Python-Flask/2.0.0/3.11.0/python
Auto-detects these frameworks:
Django, Flask, FastAPI (more frameworks can be added)
SDK Version: Automatically detected from package metadata
Python Version: Detected from sys.version_info
Fallback: Uses "2.0.0-dev" during development
The core team handles publishing.
If you're upgrading from v1 of the Kinde Python SDK, we've prepared comprehensive migration resources:
- Migration Guide - Detailed step-by-step instructions for upgrading from v1 to v2
- Quick Reference - At-a-glance conversion table for common v1 to v2 changes
- Troubleshooting - Solutions for common migration issues
Please refer to Kinde's contributing guidelines.
To set up the development environment, install the package in editable mode with development dependencies:
pip install -e ".[dev]"Note: The dev optional dependency group includes all development tools (pytest, mypy, pylint, etc.). Pylint is conditionally installed based on your Python version:
- Python 3.10+: pylint >=4.0.0
- Python 3.9: pylint >=2.0, <4.0
This ensures compatibility with Python 3.9 while allowing newer Python versions to use the latest pylint features.
By contributing to Kinde, you agree that your contributions will be licensed under its MIT License.