-
Notifications
You must be signed in to change notification settings - Fork 49
chore: Fix incorrect Management API docs in Python SDK #675
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1386,239 +1386,230 @@ You don't need to manually manage tokens or sessions - the SDK handles this auto | |
|
|
||
| ## Management API | ||
|
|
||
| The Kinde Python SDK provides a Management API client for interacting with Kinde's management endpoints. This allows you to programmatically manage users, organizations, and other resources. The Management API supports both sync and async patterns. | ||
| The Kinde Python SDK includes a separate Management API client for interacting with Kinde's management endpoints. This allows you to programmatically manage users, organizations, roles, permissions, feature flags, and other resources. | ||
|
|
||
| ### Getting started | ||
| The Management API client is independent of the OAuth authentication clients (`OAuth`, `AsyncOAuth`, `SmartOAuth`). You initialize it directly with your management API credentials. | ||
|
|
||
| #### With OAuth client (Framework-based) | ||
| ### Getting started | ||
|
|
||
| ```python | ||
| from kinde_sdk.auth.oauth import OAuth | ||
| from flask import Flask | ||
| import asyncio | ||
| from kinde_sdk.management.management_client import ManagementClient | ||
|
|
||
| app = Flask(__name__) | ||
| oauth = OAuth(framework="flask", app=app) | ||
| management = ManagementClient( | ||
| domain="your-domain.kinde.com", | ||
| client_id="your-management-client-id", | ||
| client_secret="your-management-client-secret" | ||
| ) | ||
| ``` | ||
|
|
||
| # Get the management client | ||
| management = oauth.get_management() | ||
| You can also load credentials from a `.env` file. This requires the `python-dotenv` package: | ||
|
|
||
| # Use with asyncio in Flask | ||
| def list_users_sync(): | ||
| loop = asyncio.get_event_loop() | ||
| users = loop.run_until_complete(management.get_users()) | ||
| return users | ||
| ```bash | ||
| pip install python-dotenv | ||
| ``` | ||
|
|
||
| #### With AsyncOAuth client (Native async) | ||
| Call `load_dotenv()` before initializing `ManagementClient` so the environment variables are available: | ||
|
|
||
| ```python | ||
| from kinde_sdk.auth.async_oauth import AsyncOAuth | ||
|
|
||
| oauth = AsyncOAuth() | ||
| import os | ||
| from dotenv import load_dotenv | ||
| from kinde_sdk.management.management_client import ManagementClient | ||
|
|
||
| # Get the management client (native async) | ||
| management = await oauth.get_management() | ||
| load_dotenv() | ||
|
|
||
| # All methods are async | ||
| users = await management.get_users() | ||
| management = ManagementClient( | ||
| domain=os.getenv("KINDE_HOST", "").replace("https://", ""), | ||
| client_id=os.getenv("MGMT_API_CLIENT_ID"), | ||
| client_secret=os.getenv("MGMT_API_CLIENT_SECRET") | ||
| ) | ||
| ``` | ||
|
|
||
| #### With SmartOAuth client (Context-aware) | ||
| ### Available endpoints | ||
|
|
||
| The `ManagementClient` dynamically discovers all auto-generated API classes and exposes them as snake_case properties. Access endpoints through these API class properties: | ||
|
|
||
| **User management:** | ||
|
|
||
| ```python | ||
| from kinde_sdk.auth.smart_oauth import SmartOAuth | ||
| # List users | ||
| users = management.users_api.get_users(page_size=10) | ||
|
|
||
| # Get a specific user | ||
| user = management.users_api.get_user_data(id="user_123") | ||
|
|
||
| # Create a new user | ||
| new_user = management.users_api.create_user( | ||
| create_user_request={ | ||
| "profile": { | ||
| "given_name": "John", | ||
| "family_name": "Doe" | ||
| }, | ||
| "identities": [{ | ||
| "type": "email", | ||
| "details": {"email": "user@example.com"} | ||
| }] | ||
| } | ||
| ) | ||
|
|
||
| oauth = SmartOAuth() | ||
| # Update a user | ||
| management.users_api.update_user( | ||
| id="user_123", | ||
| update_user_request={"given_name": "Johnny"} | ||
| ) | ||
|
|
||
| # Works in async context | ||
| async def async_get_users(): | ||
| management = await oauth.get_management() | ||
| return await management.get_users() | ||
|
|
||
| # Works in sync context (if supported) | ||
| def sync_get_users(): | ||
| management = oauth.get_management() | ||
| return management.get_users() | ||
| # Delete a user | ||
| management.users_api.delete_user(id="user_123") | ||
| ``` | ||
|
|
||
| ### Available endpoints | ||
| **Other common API classes:** | ||
|
|
||
| The Management API provides methods for common operations on resources. All examples use async patterns: | ||
| ```python | ||
| management.organizations_api # Organization management | ||
| management.roles_api # Role management | ||
| management.permissions_api # Permission management | ||
| management.feature_flags_api # Feature flag management | ||
| management.applications_api # Application management | ||
| management.subscribers_api # Subscriber management | ||
| management.connections_api # Connection management | ||
| management.webhooks_api # Webhook management | ||
| ``` | ||
|
|
||
| **User management:** | ||
| When new API classes are added to the generated module, they are automatically available on the client without any code changes. | ||
|
|
||
| ### Organization management | ||
|
|
||
| ```python | ||
| # List users (async) | ||
| users = await management.get_users() | ||
| from kinde_sdk.management.management_client import ManagementClient | ||
|
|
||
| # Get a specific user (async) | ||
| user = await management.get_user(user_id="user_123") | ||
| management = ManagementClient( | ||
| domain="your-domain.kinde.com", | ||
| client_id="your-management-client-id", | ||
| client_secret="your-management-client-secret" | ||
| ) | ||
|
|
||
| # List organizations | ||
| orgs = management.organizations_api.get_organizations(page_size=10) | ||
|
|
||
| # Get a specific organization | ||
| org = management.organizations_api.get_organization(code="org_1234") | ||
|
|
||
| # Create a new user (async) | ||
| new_user = await management.create_user( | ||
| email="user@example.com", | ||
| given_name="John", | ||
| family_name="Doe" | ||
| # Create a new organization | ||
| new_org = management.organizations_api.create_organization( | ||
| create_organization_request={"name": "My Organization"} | ||
| ) | ||
|
|
||
| # Update a user (async) | ||
| updated_user = await management.update_user( | ||
| user_id="user_123", | ||
| given_name="Johnny" | ||
| # Update an organization | ||
| management.organizations_api.update_organization( | ||
| org_code="org_1234", | ||
| update_organization_request={"name": "Updated Name"} | ||
| ) | ||
|
|
||
| # Delete a user (async) | ||
| await management.delete_user(user_id="user_123") | ||
| # Delete an organization | ||
| management.organizations_api.delete_organization(org_code="org_1234") | ||
| ``` | ||
|
|
||
| ### Organization management | ||
|
|
||
| **Using Management API with FastAPI (OAuth client):** | ||
| **Using the Management API in a FastAPI application:** | ||
|
|
||
| ```python | ||
| from fastapi import FastAPI, HTTPException | ||
| from kinde_sdk.auth.oauth import OAuth | ||
| from kinde_sdk.management.management_client import ManagementClient | ||
|
|
||
| app = FastAPI() | ||
| oauth = OAuth(framework="fastapi", app=app) | ||
| management = ManagementClient( | ||
| domain="your-domain.kinde.com", | ||
| client_id="your-management-client-id", | ||
| client_secret="your-management-client-secret" | ||
| ) | ||
|
|
||
| @app.get("/organizations") | ||
| async def list_organizations(): | ||
| management = oauth.get_management() | ||
| orgs = await management.get_organizations() | ||
| def list_organizations(): | ||
| orgs = management.organizations_api.get_organizations() | ||
| return orgs | ||
|
|
||
| @app.get("/organizations/{org_id}") | ||
| async def get_organization(org_id: str): | ||
| management = oauth.get_management() | ||
| org = await management.get_organization(org_id=org_id) | ||
| @app.get("/organizations/{org_code}") | ||
| def get_organization(org_code: str): | ||
| org = management.organizations_api.get_organization(code=org_code) | ||
| return org | ||
|
|
||
| @app.post("/organizations") | ||
| async def create_organization(name: str): | ||
| management = oauth.get_management() | ||
| new_org = await management.create_organization(name=name) | ||
| return new_org | ||
|
|
||
| @app.put("/organizations/{org_id}") | ||
| async def update_organization(org_id: str, name: str): | ||
| management = oauth.get_management() | ||
| updated_org = await management.update_organization( | ||
| org_id=org_id, | ||
| name=name | ||
| def create_organization(name: str): | ||
| new_org = management.organizations_api.create_organization( | ||
| create_organization_request={"name": name} | ||
| ) | ||
| return updated_org | ||
| return new_org | ||
|
|
||
| @app.delete("/organizations/{org_id}") | ||
| async def delete_organization(org_id: str): | ||
| management = oauth.get_management() | ||
| await management.delete_organization(org_id=org_id) | ||
| @app.delete("/organizations/{org_code}") | ||
| def delete_organization(org_code: str): | ||
| management.organizations_api.delete_organization(org_code=org_code) | ||
| return {"message": "Organization deleted"} | ||
| ``` | ||
|
|
||
| **Using Management API with AsyncOAuth client:** | ||
| ### Error handling | ||
|
|
||
| The Management API methods raise exceptions for HTTP errors. Wrap calls in try/except blocks: | ||
|
|
||
| ```python | ||
| from kinde_sdk.auth.async_oauth import AsyncOAuth | ||
| from kinde_sdk.management.management_client import ManagementClient | ||
|
|
||
| oauth = AsyncOAuth() | ||
| management = ManagementClient( | ||
| domain="your-domain.kinde.com", | ||
| client_id="your-management-client-id", | ||
| client_secret="your-management-client-secret" | ||
| ) | ||
|
|
||
| async def manage_organizations(): | ||
| # Get management client | ||
| management = await oauth.get_management() | ||
|
|
||
| # List organizations | ||
| orgs = await management.get_organizations() | ||
|
|
||
| # Get a specific organization | ||
| org = await management.get_organization(org_id="org_123") | ||
|
|
||
| # Create a new organization | ||
| new_org = await management.create_organization( | ||
| name="My Organization" | ||
| ) | ||
|
|
||
| # Update an organization | ||
| updated_org = await management.update_organization( | ||
| org_id="org_123", | ||
| name="Updated Name" | ||
| ) | ||
|
|
||
| # Delete an organization | ||
| await management.delete_organization(org_id="org_123") | ||
|
|
||
| return orgs | ||
| try: | ||
| user = management.users_api.get_user_data(id="user_123") | ||
| except Exception as e: | ||
| print(f"Error: {e}") | ||
| ``` | ||
|
|
||
| ### Error handling | ||
|
|
||
| The Management API methods will raise exceptions for API errors. It's recommended to handle these appropriately: | ||
|
|
||
| **Example with OAuth client (FastAPI):** | ||
| **In a FastAPI application:** | ||
|
|
||
| ```python | ||
| import logging | ||
| from fastapi import FastAPI, HTTPException | ||
| from kinde_sdk.auth.oauth import OAuth | ||
| from kinde_sdk.exceptions import KindeAPIException | ||
| from kinde_sdk.management.management_client import ManagementClient | ||
| from kinde_sdk.management.exceptions import ApiException | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| app = FastAPI() | ||
| oauth = OAuth(framework="fastapi", app=app) | ||
| management = ManagementClient( | ||
| domain="your-domain.kinde.com", | ||
| client_id="your-management-client-id", | ||
| client_secret="your-management-client-secret" | ||
| ) | ||
|
|
||
| @app.get("/users/{user_id}") | ||
| async def get_user(user_id: str): | ||
| management = oauth.get_management() | ||
| try: | ||
| user = await management.get_user(user_id=user_id) | ||
| return user | ||
| except KindeAPIException as e: | ||
| raise HTTPException(status_code=e.status_code, detail=str(e)) | ||
| except Exception as e: | ||
| raise HTTPException(status_code=500, detail=f"Internal error: {str(e)}") | ||
| ``` | ||
|
|
||
| **Example with AsyncOAuth client:** | ||
|
|
||
| ```python | ||
| from kinde_sdk.auth.async_oauth import AsyncOAuth | ||
| from kinde_sdk.exceptions import KindeAPIException | ||
|
|
||
| oauth = AsyncOAuth() | ||
|
|
||
| async def get_user_safely(user_id: str): | ||
| management = await oauth.get_management() | ||
| def get_user(user_id: str): | ||
| try: | ||
| user = await management.get_user(user_id=user_id) | ||
| return user | ||
| except KindeAPIException as e: | ||
| print(f"API Error {e.status_code}: {e.message}") | ||
| return None | ||
| return management.users_api.get_user_data(id=user_id) | ||
| except ApiException as e: | ||
| logger.error("Kinde API error: %s %s", e.status, e.body) | ||
| raise HTTPException(status_code=e.status, detail="Failed to fetch user") | ||
| except Exception as e: | ||
| print(f"Unexpected error: {str(e)}") | ||
| return None | ||
| logger.exception("Unexpected error fetching user %s", user_id) | ||
| raise HTTPException(status_code=500, detail="Internal server error") | ||
| ``` | ||
coderabbitai[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ### Token management | ||
|
|
||
| The Management API client has its own token management system for API authentication, which is separate from the core SDK's user session token management. The Management API client automatically handles: | ||
| The `ManagementClient` has its own token management system that is separate from the OAuth authentication clients. It uses the OAuth2 client credentials flow and automatically handles: | ||
|
|
||
| - **accessing Kinde Management API endpoints**: Obtains tokens for accessing Kinde's management endpoints | ||
| - **Token refresh**: Automatically refreshes management API tokens when they expire | ||
| - **Token storage**: Securely stores management API tokens | ||
| - **Thread safety**: Ensures thread-safe token handling for concurrent requests | ||
| - **Token acquisition**: Obtains tokens using a client credentials grant on the first API call | ||
| - **Token caching**: Tokens are cached and reused to avoid unnecessary requests | ||
| - **Automatic re-acquisition**: Requests new tokens via a client credentials grant when existing ones near expiry (60 seconds before expiration) | ||
| - **Shared tokens**: Multiple `ManagementClient` instances with the same domain and client ID share the same token | ||
|
|
||
| You don't need to manually manage Management API tokens - the client handles this for you. This is different from the core SDK's user session token management, which handles user authentication tokens automatically. | ||
| You don't need to manually manage tokens -- the client handles this for you. Note that credentials are validated on the first API call, not during client initialization. If your credentials are incorrect, the error will surface when you make your first request. | ||
|
Comment on lines
+1597
to
+1604
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: # First, let's find if there's a kinde-python-sdk directory or reference in the repository
find . -type d -name "*python*" -o -type d -name "*kinde*" 2>/dev/null | head -20Repository: kinde-oss/documentation Length of output: 280 🏁 Script executed: # Search for any references to ManagementClient and token refresh in the current repo
rg -i "managementclient" --type=md -A 3 -B 3 | head -50Repository: kinde-oss/documentation Length of output: 49 🏁 Script executed: # Look for the specific documentation file mentioned
find . -path "*python-sdk.mdx" 2>/dev/nullRepository: kinde-oss/documentation Length of output: 129 🏁 Script executed: # Read the specific lines mentioned in the review
sed -n '1590,1610p' ./src/content/docs/developer-tools/sdks/backend/python-sdk.mdxRepository: kinde-oss/documentation Length of output: 1565 🌐 Web query:
💡 Result: In If you configure your Kinde application’s access token lifetime to 60 seconds, then the management access token will naturally expire every ~60 seconds and the SDK will renew it that often. Token lifetimes are configured in the Kinde portal per application (Settings → Environment → Applications → View details → Tokens → set expiry in seconds). [2] Kinde’s Python SDK docs also note the Management API client’s token handling is separate from user-session token management and that it auto-refreshes management API tokens on expiry. [3] Sources: Citations:
🌐 Web query:
💡 Result: The kinde-python-sdk GitHub repository is: (Starter kit repo: 🏁 Script executed: # Let's look at the kinde-python-sdk repository to verify the actual token refresh logic
# Search for token refresh/renewal code references
curl -s "https://api.github.com/repos/kinde-oss/kinde-python-sdk/contents/" | jq -r '.[].name' | head -20Repository: kinde-oss/documentation Length of output: 131 🏁 Script executed: # Search within the documentation repo for any other token-related discussions
rg -i "60 seconds|token.*expir" ./src/content/docs/developer-tools/sdks/ -A 2 -B 2 --type=md | head -60Repository: kinde-oss/documentation Length of output: 49 Clarify ManagementClient token refresh behavior — it's not a hardcoded 60-second buffer. The statement "Requests new tokens via a client credentials grant when existing ones near expiry (60 seconds before expiration)" is misleading. The SDK doesn't have a hardcoded 60-second pre-expiry refresh. Instead, token lifetime is configurable in the Kinde portal per application, and the SDK automatically refreshes when tokens expire. The "60 seconds" only applies if the application's token lifetime is configured to 60 seconds. Rephrase to: "Requests new tokens via a client credentials grant when existing tokens expire" and remove the specific timeout reference. The token caching and shared-token-across-instances claims are accurate. 🤖 Prompt for AI Agents |
||
|
|
||
| ### Best practices | ||
|
|
||
| 1. **Always use async/await when calling Management API methods**: The Management API is async-native for better performance | ||
| 2. **Handle API errors appropriately**: Use try/except blocks and handle `KindeAPIException` specifically | ||
| 1. **Use the API class properties**: Always use `management.users_api`, `management.organizations_api`, etc. The top-level convenience methods (e.g. `management.get_users()`) are deprecated | ||
| 2. **Handle API errors appropriately**: Use try/except blocks for all Management API calls | ||
| 3. **Cache results when appropriate**: Reduce API calls by caching user data, organizations, and permissions | ||
| 4. **Use appropriate error handling for production**: Implement logging, monitoring, and graceful error recovery | ||
| 5. **Keep your client credentials secure**: Use environment variables, never commit secrets to version control | ||
| 6. **Use connection pooling**: For high-traffic applications, configure HTTP connection pooling | ||
| 7. **Implement retry logic**: Add retry logic with exponential backoff for transient failures | ||
| 8. **Monitor token expiration**: Handle token refresh gracefully to avoid authentication failures | ||
| 4. **Keep your client credentials secure**: Use environment variables, never commit secrets to version control | ||
| 5. **Implement retry logic**: Add retry logic with exponential backoff for transient failures | ||
|
|
||
| For more information about the Management API endpoints and capabilities, see the [Kinde Management API documentation](https://docs.kinde.com/kinde-apis/management/). | ||
|
|
||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.