API Endpoints Reference
Table of Contents
- API Endpoints Reference
- Authentication
- Social Login (Multi-Provider)
- Magic Link (Passwordless)
- OTP Verification
- Password Management
- User Profile
- Two-Factor Authentication (2FA)
- RBAC — Permissions
- RBAC — Roles
- RBAC — User Roles & Permissions
- Applications
- Admin — User Management
- Admin — Security
- Admin — GDPR
- User — GDPR
- Dashboard
- Organizations (opt-in)
POST /organizations/GET /organizations/list/GET /organizations/detail/PATCH /organizations/update/org.manageDELETE /organizations/delete/org.ownerGET /organizations/tree/GET /organizations/members/POST /organizations/members/add/org.members.invitePATCH /organizations/members/<user_id>/org.members.manageDELETE /organizations/members/<user_id>/remove/org.members.removePOST /organizations/invitations/org.members.inviteGET /org-roles/
- WebAuthn / Passkeys (FIDO2)
- Legend
All endpoints are prefixed with your configured base path (e.g. /api/v1/auth/).
Every request must include application credentials:
Authenticated endpoints additionally require:
Multi-tenant endpoints (organizations) require:
Authentication
POST /register/
Register a new user.
Request:
{
"email": "user@example.com",
"phone_country_code": "+1",
"phone_number": "5551234567",
"password": "SecurePass123!",
"first_name": "John",
"last_name": "Doe",
"login": false,
"device_info": "v=1|os=windows;osv=11|device=desktop"
}
email or phone_country_code + phone_number is required.
login: If true, returns JWT tokens for immediate login.
device_info: Optional device fingerprinting info.
Response 201:
{
"message": "Registration successful",
"user": {
"id": "uuid-string",
"email": "user@example.com",
"username": null,
"phone": "+15551234567",
"avatar": null,
"bio": null,
"timezone": null,
"language": null,
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_email_verified": false,
"is_phone_verified": false,
"is_2fa_enabled": false,
"created_at": "2023-10-01T12:00:00Z",
"last_login": null,
"custom_fields": null,
"preferences": {
"email_notifications": true,
"sms_notifications": false,
"marketing_emails": false
},
"roles": [],
"permissions": []
},
"verification_required": {
"email": true,
"phone": false
}
}
If login: true in request, also includes:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_expires_in": 86400,
"device_summary": "Windows 11 Desktop"
}
POST /login/email/
Login with email + password.
Request:
{
"email": "user@example.com",
"password": "SecurePass123!",
"totp_code": "123456",
"device_info": "v=1|os=windows;osv=11|device=desktop"
}
totp_code is only required if 2FA is enabled.
device_info: Optional device fingerprinting info.
Response 200:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_expires_in": 86400,
"device_summary": "Windows 11 Desktop",
"user": {
"id": "uuid-string",
"email": "user@example.com",
"username": null,
"phone": "+15551234567",
"avatar": "https://cdn.example.com/avatars/user.jpg",
"bio": null,
"timezone": "Europe/Paris",
"language": "en",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_email_verified": true,
"is_phone_verified": false,
"is_2fa_enabled": false,
"created_at": "2023-10-01T12:00:00Z",
"last_login": "2023-10-02T08:30:00Z",
"custom_fields": null,
"preferences": {
"email_notifications": true,
"sms_notifications": false,
"marketing_emails": false
},
"roles": [],
"permissions": []
}
}
Response 401 (2FA required):
Response 401 (Invalid credentials):
Response 403 (Admin 2FA required):
Response 409 (Session limit exceeded):
Response 423 (Account locked):
{
"error": "Account locked due to too many failed login attempts",
"code": "ACCOUNT_LOCKED",
"details": {}
}
POST /login/phone/
Login with phone number + password.
Request:
{
"phone_country_code": "+1",
"phone_number": "5551234567",
"password": "SecurePass123!",
"totp_code": "123456",
"device_info": "v=1|os=windows;osv=11|device=desktop"
}
totp_code is only required if 2FA is enabled.
device_info: Optional device fingerprinting info.
Response 200:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_expires_in": 86400,
"device_summary": "Windows 11 Desktop",
"user": {
"id": "uuid-string",
"email": "user@example.com",
"username": null,
"phone": "+15551234567",
"avatar": "https://cdn.example.com/avatars/user.jpg",
"bio": null,
"timezone": "Europe/Paris",
"language": "en",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_email_verified": true,
"is_phone_verified": false,
"is_2fa_enabled": false,
"created_at": "2023-10-01T12:00:00Z",
"last_login": "2023-10-02T08:30:00Z",
"custom_fields": null,
"preferences": {
"email_notifications": true,
"sms_notifications": false,
"marketing_emails": false
},
"roles": [],
"permissions": []
}
}
Response 400 (Validation error):
{
"error": "Validation error",
"details": {
"phone_country_code": ["Invalid country code format. Use +XX format."],
"phone_number": ["Phone number must be 9-15 digits."]
}
}
Response 401 (2FA required):
Response 401 (Invalid credentials):
Response 403 (Admin 2FA required):
Response 409 (Session limit exceeded):
Response 423 (Account locked):
{
"error": "Account locked due to too many failed login attempts",
"code": "ACCOUNT_LOCKED",
"details": {}
}
Social Login (Multi-Provider)
Requires social provider configuration (Google, GitHub, Microsoft, Facebook).
POST /social/<provider>/
Authenticate via OAuth2 provider.
Providers: google, github, microsoft, facebook
Request (access_token):
Request (authorization code):
{
"code": "<authorization-code>",
"redirect_uri": "https://yourapp.com/auth/callback",
"code_verifier": "<PKCE-code-verifier>",
"device_info": "v=1|os=windows;osv=11|device=desktop"
}
code_verifier: Optional PKCE code verifier (RFC 7636). Required if the authorization request included a code_challenge.
Request (Google ID token):
device_info: Optional device fingerprinting info.
Response 200:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_expires_in": 86400,
"device_summary": "Windows 11 Desktop",
"user": {
"id": "uuid-string",
"email": "user@example.com",
"username": null,
"phone": null,
"avatar": "https://lh3.googleusercontent.com/a/...",
"bio": null,
"timezone": null,
"language": null,
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_email_verified": true,
"is_phone_verified": false,
"is_2fa_enabled": false,
"created_at": "2023-10-01T12:00:00Z",
"last_login": "2023-10-02T08:30:00Z",
"custom_fields": null,
"preferences": {
"email_notifications": true,
"sms_notifications": false,
"marketing_emails": false
},
"roles": [],
"permissions": []
},
"message": "Authentication successful",
"provider": "google",
"is_new_user": false
}
Response 400 (Invalid provider):
{
"error": "Unsupported provider",
"code": "INVALID_PROVIDER",
"supported_providers": ["google", "github", "microsoft", "facebook"]
}
Response 401 (Provider auth failed):
Response 401 (Social auth failed):
GET /social/<provider>/callback/
OAuth2 callback endpoint for authorization code flow.
Query Parameters:
- code (required): Authorization code from provider
- redirect_uri (required): Original redirect URI
- code_verifier (optional): PKCE code verifier (RFC 7636)
- state (optional): CSRF/state parameter
Response 200:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_expires_in": 86400,
"device_summary": "Windows 11 Desktop",
"user": {
"id": "uuid-string",
"email": "user@example.com",
"username": null,
"phone": null,
"avatar": "https://lh3.googleusercontent.com/a/...",
"bio": null,
"timezone": null,
"language": null,
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_email_verified": true,
"is_phone_verified": false,
"is_2fa_enabled": false,
"created_at": "2023-10-01T12:00:00Z",
"last_login": "2023-10-02T08:30:00Z",
"custom_fields": null,
"preferences": {
"email_notifications": true,
"sms_notifications": false,
"marketing_emails": false
},
"roles": [],
"permissions": []
},
"provider": "google",
"is_new_user": false
}
Response 302 (Redirect with tokens):
Response 400 (Invalid provider):
Response 400 (Missing code):
Response 400 (Missing redirect_uri):
Response 400 (Callback error):
Response 401 (Code exchange failed):
Response 401 (Provider auth failed):
Response 400 (Invalid redirect URI):
Response 401 (Social auth failed):
Magic Link (Passwordless)
Requires TENXYTE_MAGIC_LINK_ENABLED = True.
POST /magic-link/request/
Request a magic link sent by email.
Request:
Response 200:
Response 400 (Validation URL missing):
Response 429 (Rate limited):
GET /magic-link/verify/?token=<token>
Verify a magic link token and receive JWT tokens.
Response 200:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_expires_in": 86400,
"device_summary": null,
"user": {
"id": "uuid-string",
"email": "user@example.com",
"username": null,
"phone": null,
"avatar": "https://cdn.example.com/avatars/user.jpg",
"bio": null,
"timezone": "Europe/Paris",
"language": "en",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_email_verified": true,
"is_phone_verified": false,
"is_2fa_enabled": false,
"created_at": "2023-10-01T12:00:00Z",
"last_login": "2023-10-02T08:30:00Z",
"custom_fields": null,
"preferences": {
"email_notifications": true,
"sms_notifications": false,
"marketing_emails": false
},
"roles": [],
"permissions": []
},
"message": "Magic link verified successfully",
"session_id": "uuid-string",
"device_id": "uuid-string"
}
Response 400 (Token missing):
Response 401 (Invalid/used/expired token):
POST /refresh/
Refresh the access token.
Request:
Cookie mode: When
TENXYTE_REFRESH_TOKEN_COOKIE_ENABLED=True, therefresh_tokenfield is optional. If omitted or empty, the server reads it from theHttpOnlycookie set during login. In this mode the response also omitsrefresh_tokenfrom the JSON body (it is rotated viaSet-Cookieinstead).
Response 200:
{
"access_token": "eyJ...",
"refresh_token": "eyJ...",
"token_type": "Bearer",
"expires_in": 900,
"refresh_expires_in": 86400,
"device_summary": null
}
Response 400 (Missing refresh token):
Response 401 (Invalid/expired refresh token):
POST /logout/
Logout (revokes refresh token + blacklists access token).
Request:
Headers (optional):
Cookie mode: When
TENXYTE_REFRESH_TOKEN_COOKIE_ENABLED=True, the server also clears the refresh token cookie viaSet-Cookiewithmax-age=0.
Response 200:
Response 400 (Missing refresh token):
POST /logout/all/
Logout from all devices.
Headers (required):
Response 200:
Response 401 (Unauthorized):
OTP Verification
POST /otp/request/
Request an OTP code (email or phone verification).
Headers (required):
Request:
otp_type: "email" or "phone"
Response 200:
{
"message": "OTP verification code sent",
"otp_id": "uuid-string",
"expires_at": "2024-01-01T12:00:00Z",
"channel": "email",
"masked_recipient": "u***@example.com"
}
Response 400 (Validation error):
Response 429 (Rate limited):
POST /otp/verify/email/
Verify email with OTP code.
Headers (required):
Request:
Response 200:
{
"message": "Email verified successfully",
"email_verified": true,
"verified_at": "2024-01-01T12:00:00Z"
}
Response 400 (Validation error):
{
"error": "Validation error",
"details": {
"code": ["Ensure this field has no more than 6 characters."]
}
}
Response 401 (Invalid/expired code):
POST /otp/verify/phone/
Verify phone with OTP code.
Headers (required):
Request:
Response 200:
{
"message": "Phone verified successfully",
"phone_verified": true,
"verified_at": "2024-01-01T12:00:00Z",
"phone_number": "+33612345678"
}
Response 400 (Validation error):
{
"error": "Validation error",
"details": {
"code": ["Ensure this field has no more than 6 characters."]
}
}
Response 401 (Invalid/expired code):
Password Management
POST /password/reset/request/
Request a password reset email.
Request (email):
Request (phone):
Response 200:
{
"message": "Password reset code sent",
"otp_id": "uuid-string",
"expires_at": "2024-01-01T12:00:00Z",
"channel": "email"
}
Response 400 (Validation error):
{
"error": "Validation error",
"code": "VALIDATION_ERROR",
"details": {
"non_field_errors": ["Email or phone number is required"]
}
}
Response 429 (Rate limited):
POST /password/reset/confirm/
Confirm password reset with OTP code.
Request (email):
Request (phone):
{
"phone_country_code": "+33",
"phone_number": "612345678",
"otp_code": "123456",
"new_password": "NewSecurePass456!"
}
Response 200:
Response 400 (Validation error):
{
"error": "Validation error",
"details": {
"new_password": ["Password must be at least 8 characters long."]
}
}
Response 401 (Invalid/expired code):
POST /password/change/
Change password (requires current password).
Headers (required):
Request:
Response 200:
{
"message": "Password changed successfully",
"password_strength": "strong",
"sessions_revoked": 2
}
Response 400 (Validation error):
{
"error": "Validation error",
"details": {
"new_password": ["Password must be at least 8 characters long."]
}
}
Response 401 (Invalid current password):
POST /password/strength/
Check password strength without saving.
Request:
Response 200:
{
"score": 4,
"strength": "Strong",
"is_valid": true,
"errors": [],
"requirements": {
"min_length": 12,
"require_lowercase": true,
"require_uppercase": true,
"require_numbers": true,
"require_special": true
}
}
Response 200 (Weak password):
{
"score": 1,
"strength": "Weak",
"is_valid": false,
"errors": [
"Password must be at least 12 characters long.",
"Password must contain at least one number.",
"Password must contain at least one special character."
],
"requirements": {
"min_length": 12,
"require_lowercase": true,
"require_uppercase": true,
"require_numbers": true,
"require_special": true
}
}
GET /password/requirements/
Get the current password policy requirements.
Response 200:
{
"requirements": {
"min_length": 12,
"require_lowercase": true,
"require_uppercase": true,
"require_numbers": true,
"require_special": true
},
"min_length": 12,
"max_length": 128
}
User Profile
GET /me/
Get the current user's profile.
Headers (required):
Headers (optional):
Response 200:
{
"id": 12345,
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"username": "johndoe",
"phone": "+33612345678",
"avatar": "https://cdn.example.com/avatars/john.jpg",
"bio": "Software developer passionate about security",
"timezone": "Europe/Paris",
"language": "fr",
"is_active": true,
"is_verified": true,
"date_joined": "2024-01-15T10:30:00Z",
"last_login": "2024-01-20T14:22:00Z",
"custom_fields": {
"department": "Engineering",
"employee_id": "EMP001"
},
"preferences": {
"email_notifications": true,
"sms_notifications": false,
"marketing_emails": false,
"two_factor_enabled": true
},
"organization_context": {
"current_org": {
"id": "org_abc123",
"name": "Acme Corp",
"slug": "acme-corp"
},
"roles": ["admin"],
"permissions": ["users.view"]
}
}
PATCH /me/
Update the current user's profile.
Headers (required):
Headers (optional):
Request:
{
"first_name": "Jane",
"last_name": "Doe",
"username": "janedoe",
"phone": "+33612345678",
"bio": "Senior developer",
"timezone": "Europe/Paris",
"language": "fr",
"custom_fields": {
"department": "Engineering"
}
}
Response 200:
{
"message": "Profile updated successfully",
"updated_fields": ["first_name", "last_name"],
"user": {
"id": 12345,
"email": "john.doe@example.com",
"first_name": "Jane",
"last_name": "Doe",
"username": "janedoe",
"phone": "+33612345678",
"bio": "Senior developer",
"timezone": "Europe/Paris",
"language": "fr",
"is_active": true,
"is_verified": true,
"date_joined": "2024-01-15T10:30:00Z",
"last_login": "2024-01-20T14:22:00Z"
},
"verification_required": {
"email_changed": false,
"phone_changed": false
}
}
Response 400 (Validation error):
{
"error": "Validation error",
"details": {
"phone": ["Invalid phone format"],
"username": ["Username already taken"]
}
}
GET /me/roles/
Get the current user's roles and permissions.
Headers (required):
Headers (optional):
Response 200:
Two-Factor Authentication (2FA)
GET /2fa/status/
Get 2FA status for the current user.
Headers (required):
Response 200:
POST /2fa/setup/
Initiate 2FA setup. Returns QR code and backup codes.
Headers (required):
Response 200:
{
"message": "Scan the QR code with your authenticator app, then confirm with a code.",
"secret": "JBSWY3DPEHPK3PXP",
"qr_code": "data:image/png;base64,...",
"provisioning_uri": "otpauth://totp/...",
"backup_codes": ["abc123", "def456", "ghi789", "jkl012", "mno345", "pqr678", "stu901", "vwx234", "yza567", "bcd890"],
"warning": "Save the backup codes securely. They will not be shown again."
}
Response 400 (2FA already enabled):
POST /2fa/confirm/
Confirm 2FA activation with a TOTP code.
Headers (required):
Request:
Response 200:
Response 400 (Invalid code):
{
"error": "Invalid TOTP code",
"details": {
"code": ["The code provided is incorrect or outside the valid time window"]
},
"code": "INVALID_CODE"
}
Response 400 (Code missing):
POST /2fa/disable/
Disable 2FA (requires TOTP code or backup code).
Headers (required):
Request:
Response 200:
Response 400 (Invalid code):
{
"error": "Invalid TOTP code",
"details": {
"code": ["The code provided is incorrect"]
},
"code": "INVALID_CODE"
}
Response 400 (Code missing):
POST /2fa/backup-codes/
Regenerate backup codes (invalidates old ones).
Headers (required):
Request:
Response 200:
{
"message": "Backup codes regenerated",
"backup_codes": ["AB12CD34", "EF56GH78", "IJ90KL12", "MN34OP56", "QR78ST90", "UV12WX34", "YZ56AB78", "CD90EF12", "GH34IJ56", "KL78MN90"],
"warning": "Save these codes securely. They will not be shown again."
}
Response 400 (Invalid code):
{
"error": "Invalid TOTP code",
"details": {
"code": ["The TOTP code provided is incorrect"]
},
"code": "INVALID_CODE"
}
Response 400 (Code missing):
RBAC — Permissions
GET /permissions/ permissions.view
List all permissions.
Headers (required):
Query Parameters (optional):
- search: Search in code, name
- parent: Filter by parent (null for root permissions, or parent ID)
- ordering: Order by code, name, created_at (default: code)
Response 200:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "1",
"code": "users.view",
"name": "View users",
"description": "Can view user list"
}
]
}
POST /permissions/ permissions.manage
Create a permission.
Headers (required):
Request:
{
"code": "posts.publish",
"name": "Publish Posts",
"description": "Can publish blog posts",
"parent_code": "posts.manage"
}
Response 201:
{
"id": "2",
"code": "posts.publish",
"name": "Publish Posts",
"description": "Can publish blog posts",
"parent": {
"id": "1",
"code": "posts.manage"
},
"children": [],
"created_at": "2024-01-01T12:00:00Z"
}
Response 400 (Validation error):
{
"error": "Validation error",
"details": {
"code": ["Permission with this code already exists."]
}
}
GET /permissions/<id>/ permissions.view
Get a permission.
Headers (required):
Response 200:
{
"id": "1",
"code": "users.view",
"name": "View users",
"description": "Can view user list",
"parent": null,
"children": [
{
"id": "2",
"code": "users.view.profile"
}
],
"created_at": "2024-01-01T12:00:00Z"
}
Response 404 (Not found):
PUT /permissions/<id>/ permissions.manage
Update a permission.
Headers (required):
Request:
{
"name": "View all users",
"description": "Can view all users in the system",
"parent_code": null
}
Response 200:
{
"id": "1",
"code": "users.view",
"name": "View all users",
"description": "Can view all users in the system",
"parent": null,
"children": [
{
"id": "2",
"code": "users.view.profile"
}
],
"created_at": "2024-01-01T12:00:00Z"
}
Response 400 (Validation error):
Response 404 (Not found):
DELETE /permissions/<id>/ permissions.manage
Delete a permission.
Headers (required):
Response 200:
Response 404 (Not found):
RBAC — Roles
GET /roles/ roles.view
List all roles.
Headers (required):
Query Parameters (optional):
- search: Search in code, name
- is_default: Filter by is_default (true/false)
- ordering: Order by code, name, created_at (default: name)
Response 200:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "1",
"code": "editor",
"name": "Editor",
"is_default": false
}
]
}
POST /roles/ roles.manage
Create a role.
Headers (required):
Request:
{
"code": "editor",
"name": "Editor",
"description": "Can edit content",
"permission_codes": ["posts.edit", "posts.view"],
"is_default": false
}
Response 201:
{
"id": "1",
"code": "editor",
"name": "Editor",
"description": "Can edit content",
"permissions": [
{
"id": "1",
"code": "posts.edit",
"name": "Edit posts",
"description": "Can edit blog posts"
}
],
"is_default": false,
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T12:00:00Z"
}
Response 400 (Validation error):
GET /roles/<id>/ roles.view
Get a role.
Headers (required):
Response 200:
{
"id": "1",
"code": "editor",
"name": "Editor",
"description": "Can edit content",
"permissions": [
{
"id": "1",
"code": "posts.edit",
"name": "Edit posts",
"description": "Can edit blog posts"
}
],
"is_default": false,
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T12:00:00Z"
}
Response 404 (Not found):
PUT /roles/<id>/ roles.manage
Update a role.
Headers (required):
Request:
{
"name": "Senior Editor",
"description": "Can edit and publish content",
"permission_codes": ["posts.edit", "posts.publish", "posts.view"],
"is_default": false
}
Response 200:
{
"id": "1",
"code": "editor",
"name": "Senior Editor",
"description": "Can edit and publish content",
"permissions": [
{
"id": "1",
"code": "posts.edit",
"name": "Edit posts",
"description": "Can edit blog posts"
},
{
"id": "2",
"code": "posts.publish",
"name": "Publish posts",
"description": "Can publish blog posts"
}
],
"is_default": false,
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T13:00:00Z"
}
Response 400 (Validation error):
{
"error": "Validation error",
"details": {
"permission_codes": ["Permission 'invalid.code' not found"]
}
}
Response 404 (Not found):
DELETE /roles/<id>/ roles.manage
Delete a role.
Headers (required):
Response 200:
Response 404 (Not found):
GET /roles/<id>/permissions/ roles.view
List permissions assigned to a role.
Headers (required):
Response 200:
{
"role_id": "1",
"role_code": "editor",
"permissions": [
{
"id": "1",
"code": "posts.publish",
"name": "Publish Posts",
"description": "Can publish blog posts",
"parent": null,
"children": [],
"created_at": "2024-01-01T12:00:00Z"
}
]
}
Response 404 (Not found):
POST /roles/<id>/permissions/ roles.manage
Assign permissions to a role.
Headers (required):
Request:
Response 200:
{
"message": "2 permission(s) added",
"added": ["posts.edit", "posts.publish"],
"role_code": "editor",
"permissions": [
{
"id": "1",
"code": "posts.edit",
"name": "Edit posts",
"description": "Can edit blog posts"
},
{
"id": "2",
"code": "posts.publish",
"name": "Publish posts",
"description": "Can publish blog posts"
}
]
}
Response 200 (Some already assigned):
{
"message": "1 permission(s) added",
"added": ["posts.publish"],
"already_assigned": ["posts.edit"],
"role_code": "editor",
"permissions": ["posts.edit", "posts.view", "posts.publish"]
}
Response 400 (Validation error):
Response 400 (Permissions not found):
{
"error": "Some permissions not found",
"code": "PERMISSIONS_NOT_FOUND",
"not_found": ["invalid.permission"]
}
RBAC — User Roles & Permissions
GET /users/<id>/roles/ users.manage
List roles assigned to a user.
Headers (required):
Response 200:
{
"user_id": "1",
"roles": [
{
"id": "1",
"code": "editor",
"name": "Editor",
"is_default": false
}
]
}
Response 404 (Not found):
POST /users/<id>/roles/ users.manage
Assign a role to a user.
Headers (required):
Request:
Response 200:
Response 400 (Validation error):
Response 404 (User not found):
Response 404 (Role not found):
DELETE /users/<id>/roles/ users.manage
Remove a role from a user.
Headers (required):
Query Parameters (required):
- role_code: Role code to remove
Request:
Response 200:
Response 400 (Missing parameter):
Response 404 (User not found):
Response 404 (Role not found):
GET /users/<id>/permissions/ users.manage
List direct permissions for a user.
Headers (required):
Response 200:
{
"user_id": "1",
"email": "user@example.com",
"direct_permissions": [
{
"id": "1",
"code": "posts.view",
"name": "View posts",
"description": "Can view blog posts",
"parent": null,
"children": [],
"created_at": "2024-01-01T12:00:00Z"
}
],
"all_permissions": ["posts.view", "posts.edit", "users.view"]
}
Response 404 (Not found):
POST /users/<id>/permissions/ users.manage
Assign a direct permission to a user.
Headers (required):
Request:
Response 200:
{
"message": "2 permission(s) added",
"added": ["posts.edit", "posts.publish"],
"user_id": "1",
"direct_permissions": [
{
"id": "1",
"code": "posts.edit",
"name": "Edit posts",
"description": "Can edit blog posts"
},
{
"id": "2",
"code": "posts.publish",
"name": "Publish posts",
"description": "Can publish blog posts"
}
]
}
Response 200 (Some already assigned):
{
"message": "1 permission(s) added",
"added": ["posts.publish"],
"already_assigned": ["posts.edit"],
"user_id": "1",
"direct_permissions": ["posts.edit", "posts.view", "posts.publish"]
}
Response 400 (Validation error):
Response 400 (Permissions not found):
{
"error": "Some permissions not found",
"code": "PERMISSIONS_NOT_FOUND",
"not_found": ["invalid.permission"]
}
Applications
GET /applications/ applications.view
List all applications.
Headers (required):
Query Parameters (optional):
- search: Search in name, description
- is_active: Filter by active status (true/false)
- ordering: Order by name, created_at (default: name)
Response 200:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "app_123",
"name": "My Client App",
"description": "Frontend application for user dashboard",
"access_key": "ak_abc123def456",
"is_active": true,
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T12:00:00Z"
}
]
}
POST /applications/ applications.manage
Create an application.
Headers (required):
Request:
Response 201:
{
"message": "Application created successfully",
"application": {
"id": "app_124",
"name": "My Next.js App",
"description": "Frontend client",
"access_key": "ak_abc123def456",
"is_active": true,
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T12:00:00Z"
},
"credentials": {
"access_key": "ak_abc123def456",
"access_secret": "as_def456ghi789"
},
"warning": "Save the access_secret now! It will never be shown again."
}
Response 400 (Validation error):
GET /applications/<id>/ applications.view
Get an application.
Headers (required):
Response 200:
{
"id": "app_124",
"name": "My Next.js App",
"description": "Frontend client application",
"access_key": "ak_abc123def456",
"is_active": true,
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T12:00:00Z"
}
Response 404 (Not found):
PUT /applications/<id>/ applications.manage
Update an application.
Headers (required):
Request:
Response 200:
{
"id": "app_124",
"name": "Updated App Name",
"description": "Updated description",
"access_key": "ak_abc123def456",
"is_active": true,
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T13:00:00Z"
}
Response 400 (Validation error):
Response 404 (Not found):
DELETE /applications/<id>/ applications.manage
Delete an application.
Headers (required):
Response 200:
Response 404 (Not found):
POST /applications/<id>/regenerate/ applications.manage
Regenerate the application's access secret.
Headers (required):
Request:
Response 200:
{
"message": "Credentials regenerated successfully",
"application": {
"id": "app_124",
"name": "My Next.js App",
"description": "Frontend client",
"access_key": "ak_new123def456",
"is_active": true,
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T13:00:00Z"
},
"credentials": {
"access_key": "ak_new123def456",
"access_secret": "as_new789ghi012"
},
"warning": "Save the access_secret now! It will never be shown again.",
"old_credentials_invalidated": true
}
Response 400 (Confirmation required):
Response 404 (Not found):
Admin — User Management
GET /admin/users/ users.view
List all users with filtering and pagination.
Headers (required):
Query Parameters (optional):
- search: Search in email, first_name, last_name
- is_active: Filter by active status (true/false)
- is_locked: Filter by locked account (true/false)
- is_banned: Filter by banned account (true/false)
- is_deleted: Filter by deleted account (true/false)
- is_email_verified: Filter by email verified (true/false)
- is_2fa_enabled: Filter by 2FA enabled (true/false)
- role: Filter by role code
- date_from: Created after (YYYY-MM-DD)
- date_to: Created before (YYYY-MM-DD)
- ordering: Sort by email, created_at, last_login, first_name
- page: Page number
- page_size: Items per page (max 100)
Response 200:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "1",
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_locked": false,
"is_banned": false,
"is_deleted": false,
"is_email_verified": true,
"is_phone_verified": false,
"is_2fa_enabled": true,
"roles": ["admin", "user"],
"created_at": "2024-01-01T12:00:00Z",
"last_login": "2024-01-01T13:00:00Z"
}
]
}
GET /admin/users/<id>/ users.view
Get a user's full profile.
Headers (required):
Response 200:
{
"id": "1",
"email": "user@example.com",
"username": "johndoe",
"phone": "+33612345678",
"phone_country_code": "+33",
"phone_number": "612345678",
"avatar": "https://cdn.example.com/avatars/john.jpg",
"bio": "Software developer",
"timezone": "Europe/Paris",
"language": "en",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_locked": false,
"locked_until": null,
"is_banned": false,
"is_deleted": false,
"deleted_at": null,
"is_email_verified": true,
"is_phone_verified": false,
"is_2fa_enabled": true,
"is_staff": false,
"is_superuser": false,
"max_sessions": 5,
"max_devices": 3,
"custom_fields": {
"department": "Engineering",
"employee_id": "EMP001"
},
"preferences": {
"email_notifications": true,
"sms_notifications": false
},
"roles": ["admin", "user"],
"permissions": ["users.view", "users.manage", "posts.edit"],
"created_at": "2024-01-01T12:00:00Z",
"updated_at": "2024-01-01T13:00:00Z",
"last_login": "2024-01-01T14:00:00Z"
}
Response 404 (Not found):
POST /admin/users/<id>/ban/ users.ban
Ban a user.
Headers (required):
Request:
Response 200:
{
"message": "User banned successfully",
"user": {
"id": "1",
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"is_active": false,
"is_banned": true,
"roles": ["user"],
"created_at": "2024-01-01T12:00:00Z",
"last_login": "2024-01-01T13:00:00Z"
}
}
Response 400 (Already banned):
Response 404 (Not found):
POST /admin/users/<id>/unban/ users.ban
Unban a user.
Headers (required):
Request:
Response 200:
{
"message": "User unbanned successfully",
"user": {
"id": "1",
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_banned": false,
"roles": ["user"],
"created_at": "2024-01-01T12:00:00Z",
"last_login": "2024-01-01T13:00:00Z"
}
}
Response 400 (Not banned):
Response 404 (Not found):
POST /admin/users/<id>/lock/ users.lock
Lock a user account.
Headers (required):
Request:
Response 200:
{
"message": "User locked for 60 minutes",
"user": {
"id": "1",
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_locked": true,
"locked_until": "2024-01-01T14:00:00Z",
"roles": ["user"],
"created_at": "2024-01-01T12:00:00Z",
"last_login": "2024-01-01T13:00:00Z"
}
}
Response 400 (Already locked):
Response 404 (Not found):
POST /admin/users/<id>/unlock/ users.lock
Unlock a user account.
Headers (required):
Request:
Response 200:
{
"message": "User unlocked successfully",
"user": {
"id": "1",
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"is_locked": false,
"locked_until": null,
"roles": ["user"],
"created_at": "2024-01-01T12:00:00Z",
"last_login": "2024-01-01T13:00:00Z"
}
}
Response 400 (Not locked):
Response 404 (Not found):
Admin — Security
GET /admin/audit-logs/ audit.view
List audit log entries.
Headers (required):
Query Parameters (optional):
- user_id: Filter by user ID
- action: Filter by action (login, login_failed, password_change, etc.)
- ip_address: Filter by IP address
- application_id: Filter by application ID
- date_from: After date (YYYY-MM-DD)
- date_to: Before date (YYYY-MM-DD)
- ordering: Sort by created_at, action, user
- page: Page number
- page_size: Items per page (max 100)
Response 200:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "1",
"user": "123",
"user_email": "user@example.com",
"action": "login",
"ip_address": "127.0.0.1",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"application": "app_456",
"application_name": "My Client App",
"details": {
"success": true,
"method": "password"
},
"created_at": "2024-01-01T12:00:00Z"
}
]
}
GET /admin/audit-logs/<id>/ audit.view
Get a single audit log entry.
Headers (required):
Response 200:
{
"id": "1",
"user": "123",
"user_email": "user@example.com",
"action": "login",
"ip_address": "127.0.0.1",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"application": "app_456",
"application_name": "My Client App",
"details": {
"success": true,
"method": "password"
},
"created_at": "2024-01-01T12:00:00Z"
}
Response 404 (Not found):
GET /admin/login-attempts/ audit.view
List login attempts.
Headers (required):
Query Parameters (optional):
- identifier: Filter by identifier (email/phone)
- ip_address: Filter by IP address
- success: Filter by success/failure (true/false)
- date_from: After date (YYYY-MM-DD)
- date_to: Before date (YYYY-MM-DD)
- ordering: Sort by created_at, identifier, ip_address
- page: Page number
- page_size: Items per page (max 100)
Response 200:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "1",
"identifier": "user@example.com",
"ip_address": "127.0.0.1",
"application": "app_456",
"success": false,
"failure_reason": "Invalid password",
"created_at": "2024-01-01T12:00:00Z"
}
]
}
GET /admin/blacklisted-tokens/ audit.view
List active blacklisted tokens.
Headers (required):
Query Parameters (optional):
- user_id: Filter by user ID
- reason: Filter by reason (logout, password_change, security)
- expired: Filter by expired (true/false)
- ordering: Sort by blacklisted_at, expires_at
- page: Page number
- page_size: Items per page (max 100)
Response 200:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "1",
"token_jti": "jti123456789",
"user": "123",
"user_email": "user@example.com",
"blacklisted_at": "2024-01-01T12:00:00Z",
"expires_at": "2024-01-01T18:00:00Z",
"reason": "logout",
"is_expired": false
}
]
}
POST /admin/blacklisted-tokens/cleanup/ security.view
Remove expired blacklisted tokens.
Headers (required):
Request:
Response 200:
GET /admin/refresh-tokens/ audit.view
List active refresh tokens.
Headers (required):
Query Parameters (optional):
- user_id: Filter by user ID
- application_id: Filter by application ID
- is_revoked: Filter by revoked (true/false)
- expired: Filter by expired (true/false)
- ordering: Sort by created_at, expires_at, last_used_at
- page: Page number
- page_size: Items per page (max 100)
Response 200:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "1",
"user": "123",
"user_email": "user@example.com",
"application": "app_456",
"application_name": "My Client App",
"device_info": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"ip_address": "127.0.0.1",
"is_revoked": false,
"is_expired": false,
"expires_at": "2024-02-01T12:00:00Z",
"created_at": "2024-01-01T12:00:00Z",
"last_used_at": "2024-01-01T13:00:00Z"
}
]
}
POST /admin/refresh-tokens/<id>/revoke/ security.view
Revoke a specific refresh token.
Headers (required):
Request:
Response 200:
{
"message": "Token revoked successfully",
"token": {
"id": "1",
"user": "123",
"user_email": "user@example.com",
"application": "app_456",
"application_name": "My Client App",
"device_info": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
"ip_address": "127.0.0.1",
"is_revoked": true,
"is_expired": false,
"expires_at": "2024-02-01T12:00:00Z",
"created_at": "2024-01-01T12:00:00Z",
"last_used_at": "2024-01-01T13:00:00Z"
}
}
Response 400 (Already revoked):
Response 404 (Not found):
Admin — GDPR
GET /admin/deletion-requests/ gdpr.view
List account deletion requests.
Headers (required):
Query Parameters (optional):
- user_id: Filter by user ID
- status: Filter by status (pending, confirmation_sent, confirmed, completed, cancelled)
- date_from: Requested after date (YYYY-MM-DD)
- date_to: Requested before date (YYYY-MM-DD)
- grace_period_expiring: Filter by grace period expiring (true/false)
- ordering: Sort by requested_at, grace_period_ends_at, status
- page: Page number
- page_size: Items per page (max 100)
Response 200:
{
"count": 1,
"next": null,
"previous": null,
"results": [
{
"id": "1",
"user": "123",
"user_email": "user@example.com",
"status": "pending",
"requested_at": "2024-01-01T12:00:00Z",
"confirmed_at": null,
"grace_period_ends_at": "2024-01-31T12:00:00Z",
"completed_at": null,
"ip_address": "127.0.0.1",
"reason": "No longer need the account",
"admin_notes": null,
"processed_by": null,
"processed_by_email": null,
"is_grace_period_expired": false
}
]
}
GET /admin/deletion-requests/<id>/ gdpr.admin
Get a deletion request.
Headers (required):
Response 200:
{
"id": "1",
"user": "123",
"user_email": "user@example.com",
"status": "pending",
"requested_at": "2024-01-01T12:00:00Z",
"confirmed_at": null,
"grace_period_ends_at": "2024-01-31T12:00:00Z",
"completed_at": null,
"ip_address": "127.0.0.1",
"reason": "No longer need the account",
"admin_notes": null,
"processed_by": null,
"processed_by_email": null,
"is_grace_period_expired": false
}
Response 404 (Not found):
POST /admin/deletion-requests/<id>/process/ gdpr.process
Process (execute) a deletion request.
Headers (required):
Request:
{
"confirmation": "PERMANENTLY DELETE",
"admin_notes": "Processed per user request - GDPR compliance"
}
Response 200:
{
"message": "Account deletion processed successfully",
"deletion_completed": true,
"processed_at": "2024-01-15T10:30:00Z",
"data_anonymized": true,
"audit_log_id": "123",
"user_notified": true,
"request": {
"id": "1",
"user": "123",
"user_email": "user@example.com",
"status": "completed",
"requested_at": "2024-01-01T12:00:00Z",
"confirmed_at": "2024-01-02T12:00:00Z",
"grace_period_ends_at": "2024-01-31T12:00:00Z",
"completed_at": "2024-01-15T10:30:00Z",
"ip_address": "127.0.0.1",
"reason": "No longer need the account",
"admin_notes": "Processed per user request - GDPR compliance",
"processed_by": "456",
"processed_by_email": "admin@example.com",
"is_grace_period_expired": false
}
}
Response 400 (Confirmation required):
Response 400 (Not confirmed):
{
"error": "Cannot process request with status \"pending\". Only confirmed requests can be processed.",
"code": "REQUEST_NOT_CONFIRMED"
}
Response 404 (Not found):
POST /admin/deletion-requests/process-expired/ gdpr.process
Process all expired grace period deletions.
Headers (required):
Request:
Response 200:
User — GDPR
POST /request-account-deletion/
Request account deletion (starts grace period).
Headers (required):
Request:
Response 201:
{
"message": "Account deletion request created successfully",
"deletion_request_id": 123,
"scheduled_deletion_date": "2024-02-15T10:30:00Z",
"grace_period_days": 30,
"cancellation_token": "cancel_abc123def456",
"data_retention_policy": {
"anonymization_after": "30 days",
"final_deletion_after": "90 days"
}
}
Response 400 (Invalid password):
Response 400 (Already pending):
{
"error": "Account deletion already pending",
"code": "DELETION_ALREADY_PENDING",
"existing_request": {
"scheduled_deletion_date": "2024-02-15T10:30:00Z",
"cancellation_token": "cancel_abc123"
}
}
POST /confirm-account-deletion/
Confirm account deletion request.
Request:
Response 200:
{
"message": "Account deletion confirmed successfully",
"deletion_confirmed": true,
"grace_period_ends": "2024-02-15T10:30:00Z",
"cancellation_instructions": "Use the cancellation token from the initial request to cancel before the grace period ends."
}
Response 400 (Token required):
Response 400 (Invalid token):
Response 410 (Token expired):
{
"error": "Confirmation token has expired",
"code": "TOKEN_EXPIRED",
"expired_at": "2024-01-16T10:30:00Z"
}
POST /cancel-account-deletion/
Cancel a pending deletion request.
Headers (required):
Request:
Response 200:
{
"message": "Account deletion cancelled successfully",
"deletion_cancelled": true,
"account_reactivated": true,
"cancellation_time": "2024-01-15T14:30:00Z",
"security_note": "Your account has been reactivated and you can continue using the service normally."
}
Response 400 (Invalid password):
Response 404 (No pending deletion):
GET /account-deletion-status/
Get the status of the current deletion request.
Headers (required):
Response 200:
{
"total_requests": 2,
"active_request": {
"id": "123",
"status": "pending",
"requested_at": "2024-01-15T10:30:00Z",
"grace_period_ends_at": "2024-02-14T10:30:00Z",
"days_remaining": 15
},
"history": [
{
"id": "123",
"status": "pending",
"requested_at": "2024-01-15T10:30:00Z",
"confirmed_at": null,
"completed_at": null,
"reason": "No longer using the service"
},
{
"id": "100",
"status": "cancelled",
"requested_at": "2023-12-01T09:00:00Z",
"confirmed_at": null,
"completed_at": "2023-12-02T10:00:00Z",
"reason": "Changed mind"
}
]
}
POST /export-user-data/
Export all personal data (GDPR Article 20).
Headers (required):
Request:
Response 200:
{
"user_info": {
"id": "123",
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"created_at": "2024-01-01T12:00:00Z",
"last_login": "2024-01-15T10:30:00Z"
},
"roles": [
{
"id": "1",
"name": "user",
"description": "Standard user role"
}
],
"permissions": [
"profile.view",
"profile.edit"
],
"applications": [
{
"id": "app_456",
"name": "My Client App",
"created_at": "2024-01-05T09:00:00Z"
}
],
"audit_logs": [
{
"action": "login",
"timestamp": "2024-01-15T10:30:00Z",
"ip_address": "127.0.0.1"
}
],
"export_metadata": {
"exported_at": "2024-01-15T14:30:00Z",
"export_format": "json",
"total_records": 15,
"data_retention_policy": "Available for 30 days"
}
}
Response 400 (Invalid password):
Dashboard
All dashboard endpoints require dashboard.view permission.
GET /dashboard/stats/ dashboard.view
Global cross-module statistics.
Headers (required):
Query Parameters (optional):
- period: Analysis period (7d, 30d, 90d) - default: 7d
- compare: Include comparison with previous period (true/false)
- X-Org-Slug: Organization slug for filtering by organization
Response 200:
{
"summary": {
"total_users": 1500,
"active_users": 1200,
"total_organizations": 25,
"total_applications": 85,
"active_sessions": 240,
"pending_deletions": 3
},
"trends": {
"user_growth": 0.15,
"login_success_rate": 0.95,
"application_usage": 0.08,
"security_incidents": 0.02
},
"organization_context": {
"current_org": {
"id": "org_123",
"name": "Acme Corp",
"user_count": 150
},
"org_users_only": true
},
"charts": {
"daily_logins": [
{"date": "2024-01-09", "count": 350},
{"date": "2024-01-10", "count": 380}
],
"user_registrations": [
{"date": "2024-01-09", "count": 15},
{"date": "2024-01-10", "count": 18}
],
"security_events": [
{"date": "2024-01-09", "count": 5},
{"date": "2024-01-10", "count": 3}
]
}
}
GET /dashboard/auth/ dashboard.view
Detailed authentication statistics (login rates, token stats, charts).
Headers (required):
Response 200:
{
"login_stats": {
"today": {
"total": 350,
"success_count": 338,
"failed_count": 12,
"success_rate": 0.966
},
"this_week": {
"total": 2450,
"success_count": 2365,
"failed_count": 85,
"success_rate": 0.965
},
"this_month": {
"total": 10500,
"success_count": 10185,
"failed_count": 315,
"success_rate": 0.970
}
},
"login_by_method": {
"password": 320,
"social_google": 25,
"social_github": 5
},
"registration_stats": {
"today": 15,
"this_week": 95,
"this_month": 420
},
"token_stats": {
"active_refresh_tokens": 240,
"blacklisted_tokens": 8,
"expired_today": 12
},
"top_login_failure_reasons": [
{"reason": "Invalid password", "count": 45},
{"reason": "Account not found", "count": 28},
{"reason": "Account locked", "count": 12}
],
"charts": {
"logins_per_day_7d": [
{"date": "2024-01-09", "success": 338, "failed": 12},
{"date": "2024-01-10", "success": 355, "failed": 15}
]
}
}
GET /dashboard/security/ dashboard.view
Security statistics (audit summary, blacklisted tokens, suspicious activity).
Headers (required):
Response 200:
{
"audit_summary_24h": {
"total_events": 1250,
"login_attempts": 350,
"failed_logins": 12,
"password_changes": 8,
"account_locks": 2
},
"blacklisted_tokens": {
"active": 8,
"expired_today": 12,
"total_created_24h": 5
},
"suspicious_activity": {
"last_24h": 3,
"last_7d": 18,
"top_ips": [
{"ip_address": "192.168.1.100", "events": 15},
{"ip_address": "10.0.0.50", "events": 8}
]
},
"account_security": {
"locked_accounts": 2,
"banned_accounts": 5,
"2fa_adoption_rate": 0.35,
"password_changes_today": 8
}
}
GET /dashboard/gdpr/ dashboard.view
GDPR compliance statistics.
Headers (required):
Response 200:
{
"deletion_requests": {
"total": 18,
"by_status": {
"pending": 3,
"confirmation_sent": 2,
"confirmed": 5,
"completed": 15,
"cancelled": 2
},
"grace_period_expiring_7d": 2
},
"data_exports": {
"total_today": 2,
"total_this_month": 8
}
}
GET /dashboard/organizations/ dashboard.view
Organization statistics (only if TENXYTE_ORGANIZATIONS_ENABLED=True).
Headers (required):
Response 200 (Enabled):
{
"enabled": true,
"total_organizations": 45,
"active": 40,
"with_sub_orgs": 12,
"members": {
"total": 382,
"avg_per_org": 8.5,
"by_role": {
"owner": 45,
"admin": 90,
"member": 247
}
},
"top_organizations": [
{
"name": "Acme Corp",
"slug": "acme-corp",
"members": 25
},
{
"name": "Tech Startup",
"slug": "tech-startup",
"members": 18
}
]
}
Response 200 (Disabled):
Organizations (opt-in)
Enable with TENXYTE_ORGANIZATIONS_ENABLED = True.
All organization endpoints require the X-Org-Slug header to identify the target organization:
POST /organizations/
Create an organization.
Headers (required):
Request:
{
"name": "Acme Corp",
"slug": "acme-corp",
"description": "Technology company specializing in software solutions",
"parent_id": null,
"metadata": {
"industry": "technology",
"size": "medium"
},
"max_members": 100
}
Response 201:
{
"id": 1,
"name": "Acme Corp",
"slug": "acme-corp",
"description": "Technology company specializing in software solutions",
"created_at": "2024-01-15T10:30:00Z",
"is_active": true,
"member_count": 1,
"max_members": 100,
"parent": null,
"metadata": {
"industry": "technology",
"size": "medium"
}
}
Response 400 (Validation error):
{
"slug": ["Organization with this slug already exists"],
"parent_id": ["Parent organization not found"]
}
GET /organizations/list/
List organizations the current user belongs to.
Headers (required):
Query Parameters (optional):
- search: Search in name and slug
- is_active: Filter by active status (true/false)
- parent: Filter by parent (null = root organizations)
- ordering: Sort by name, slug, created_at (with - for descending)
- page: Page number
- page_size: Items per page (max 100)
Response 200:
{
"count": 2,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"name": "Acme Corp",
"slug": "acme-corp",
"description": "Technology company specializing in software solutions",
"member_count": 15,
"max_members": 100,
"is_active": true,
"created_at": "2024-01-15T10:30:00Z"
},
{
"id": 2,
"name": "Tech Startup",
"slug": "tech-startup",
"description": "Innovative tech startup",
"member_count": 8,
"max_members": 50,
"is_active": true,
"created_at": "2024-01-20T14:15:00Z"
}
]
}
GET /organizations/detail/
Get organization details.
Headers (required):
Response 200:
{
"id": 1,
"name": "Acme Corp",
"slug": "acme-corp",
"description": "Technology company specializing in software solutions",
"metadata": {
"industry": "technology",
"size": "medium"
},
"is_active": true,
"created_at": "2024-01-15T10:30:00Z",
"updated_at": "2024-01-20T14:15:00Z",
"member_count": 15,
"max_members": 100,
"parent": null,
"children": [
{
"id": 5,
"name": "Acme Subsidiary",
"slug": "acme-subsidiary"
}
],
"user_role": "owner",
"user_permissions": [
"org.manage",
"org.members.invite",
"org.members.manage"
]
}
Response 403 (Not member):
Response 404 (Not found):
PATCH /organizations/update/ org.manage
Update an organization.
Headers (required):
Request:
{
"name": "Acme Corporation",
"slug": "acme-corporation",
"description": "Updated technology company description",
"parent_id": null,
"metadata": {
"industry": "technology",
"size": "large"
},
"max_members": 200,
"is_active": true
}
Response 200:
{
"id": 1,
"name": "Acme Corporation",
"slug": "acme-corporation",
"description": "Updated technology company description",
"updated_at": "2024-01-20T15:30:00Z",
"is_active": true,
"member_count": 15,
"max_members": 200,
"parent": null,
"metadata": {
"industry": "technology",
"size": "large"
}
}
Response 400 (Validation error):
Response 403 (Insufficient permissions):
{
"error": "You don't have permission to manage this organization",
"code": "INSUFFICIENT_PERMISSIONS"
}
DELETE /organizations/delete/ org.owner
Delete an organization.
Headers (required):
Response 200:
Response 400 (Has child organizations):
Response 403 (Not owner):
Response 404 (Not found):
GET /organizations/tree/
Get the full organization hierarchy tree.
Headers (required):
Response 200:
{
"id": 1,
"name": "Acme Corp",
"slug": "acme-corp",
"depth": 0,
"is_root": true,
"member_count": 25,
"children": [
{
"id": 5,
"name": "Acme Subsidiary",
"slug": "acme-subsidiary",
"depth": 1,
"is_root": false,
"member_count": 8,
"children": [
{
"id": 12,
"name": "Acme Team",
"slug": "acme-team",
"depth": 2,
"is_root": false,
"member_count": 3,
"children": []
}
]
},
{
"id": 6,
"name": "Acme Division",
"slug": "acme-division",
"depth": 1,
"is_root": false,
"member_count": 12,
"children": []
}
]
}
GET /organizations/members/
List organization members.
Headers (required):
Query Parameters (optional):
- search: Search in email, first name, last name
- role: Filter by role (owner, admin, member)
- status: Filter by status (active, inactive, pending)
- ordering: Sort by joined_at, user.email, role
- page: Page number
- page_size: Items per page (max 100)
Response 200:
{
"count": 15,
"next": null,
"previous": null,
"results": [
{
"id": 1,
"user": {
"id": 42,
"email": "admin@acme.com",
"first_name": "John",
"last_name": "Doe"
},
"role": "admin",
"role_display": "Administrator",
"permissions": [
"org.manage",
"org.members.invite",
"org.members.manage"
],
"inherited_permissions": [],
"effective_permissions": [
"org.manage",
"org.members.invite",
"org.members.manage"
],
"joined_at": "2024-01-15T10:30:00Z",
"status": "active"
},
{
"id": 2,
"user": {
"id": 43,
"email": "user@acme.com",
"first_name": "Jane",
"last_name": "Smith"
},
"role": "member",
"role_display": "Member",
"permissions": [
"org.view"
],
"inherited_permissions": [
"org.view"
],
"effective_permissions": [
"org.view"
],
"joined_at": "2024-01-20T14:15:00Z",
"status": "active"
}
]
}
POST /organizations/members/add/ org.members.invite
Add a member to an organization.
Headers (required):
Request:
Response 201:
{
"id": 25,
"user": {
"id": 2,
"email": "newmember@acme.com",
"first_name": "Jane",
"last_name": "Smith"
},
"role": "member",
"role_display": "Member",
"joined_at": "2024-01-20T15:30:00Z",
"status": "active"
}
Response 400 (Validation error):
Response 403 (Insufficient permissions):
Response 404 (User not found):
PATCH /organizations/members/<user_id>/ org.members.manage
Update a member's role.
Headers (required):
Path Parameters:
- user_id: ID of the user to update
Request:
Response 200:
{
"id": 25,
"user": {
"id": 2,
"email": "member@acme.com",
"first_name": "Jane",
"last_name": "Smith"
},
"role": "admin",
"role_display": "Administrator",
"updated_at": "2024-01-20T16:00:00Z"
}
Response 400 (Cannot demote last owner):
{
"error": "Cannot demote the last owner of the organization",
"code": "LAST_OWNER_CANNOT_BE_DEMOTED"
}
Response 403 (Insufficient permissions):
Response 404 (Member not found):
DELETE /organizations/members/<user_id>/remove/ org.members.remove
Remove a member from an organization.
Headers (required):
Path Parameters:
- user_id: ID of the user to remove
Response 200:
Response 400 (Cannot remove last owner):
{
"error": "Cannot remove the last owner of the organization",
"code": "LAST_OWNER_CANNOT_BE_REMOVED"
}
Response 403 (Insufficient permissions):
Response 404 (Member not found):
POST /organizations/invitations/ org.members.invite
Invite a user to an organization by email.
Headers (required):
Request:
Response 201:
{
"id": 123,
"email": "newuser@example.com",
"role": "member",
"role_display": "Member",
"token": "inv_abc123def456",
"expires_at": "2024-01-27T15:30:00Z",
"invited_by": {
"id": 42,
"email": "admin@acme.com",
"first_name": "John",
"last_name": "Doe"
},
"organization": {
"id": 1,
"name": "Acme Corp",
"slug": "acme-corp"
},
"status": "pending",
"created_at": "2024-01-20T15:30:00Z"
}
Response 400 (Validation error):
Response 403 (Insufficient permissions):
GET /org-roles/
List organization-scoped roles.
Headers (required):
Response 200:
[
{
"code": "owner",
"name": "Owner",
"description": "Full control over the organization",
"weight": 100,
"permissions": [
{
"code": "org.manage",
"name": "Manage Organization",
"description": "Can manage all organization settings"
},
{
"code": "org.members.invite",
"name": "Invite Members",
"description": "Can invite new members to the organization"
},
{
"code": "org.members.manage",
"name": "Manage Members",
"description": "Can manage existing members"
},
{
"code": "org.members.remove",
"name": "Remove Members",
"description": "Can remove members from organization"
}
],
"is_system_role": true,
"created_at": "2024-01-01T00:00:00Z"
},
{
"code": "admin",
"name": "Administrator",
"description": "Administrative access without ownership",
"weight": 80,
"permissions": [
{
"code": "org.members.invite",
"name": "Invite Members",
"description": "Can invite new members to the organization"
},
{
"code": "org.members.manage",
"name": "Manage Members",
"description": "Can manage existing members"
}
],
"is_system_role": true,
"created_at": "2024-01-01T00:00:00Z"
},
{
"code": "member",
"name": "Member",
"description": "Standard organization member",
"weight": 20,
"permissions": [
{
"code": "org.view",
"name": "View Organization",
"description": "Can view organization details"
}
],
"is_system_role": true,
"created_at": "2024-01-01T00:00:00Z"
}
]
WebAuthn / Passkeys (FIDO2)
Requires TENXYTE_WEBAUTHN_ENABLED = True and pip install py-webauthn.
POST /webauthn/register/begin/
Begin passkey registration. Returns a challenge.
Headers (required):
Response 200:
{
"challenge": "A3B5C7D9E1F2G4H6I8J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6",
"rp": {
"name": "Tenxyte",
"id": "localhost:8000"
},
"user": {
"id": "MTIzNDU2Nzg5MA",
"name": "user@example.com",
"displayName": "user@example.com"
},
"pubKeyCredParams": [
{
"type": "public-key",
"alg": -7
},
{
"type": "public-key",
"alg": -257
},
{
"type": "public-key",
"alg": -8
}
],
"timeout": 300000,
"authenticatorSelection": {
"authenticatorAttachment": "platform",
"userVerification": "preferred",
"requireResidentKey": false
},
"attestation": "direct"
}
Response 400 (WebAuthn disabled):
POST /webauthn/register/complete/
Complete passkey registration with the authenticator response.
Headers (required):
Request:
{
"challenge_id": 12345,
"credential": {
"id": "A3B5C7D9E1F2G4H6I8J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6",
"rawId": "A3B5C7D9E1F2G4H6I8J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uY3JlYXRlIiwiY2hhbGxlbmdlIjoiQTNINUQ3RTlGMUcyRzRIOEk4SjBLMUwyTTNONE81UDZRN1I4UzlUMFUxVjJXM1g0WTVaNiIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0OjgwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2V9",
"attestationObject": "o2NmbXRkbm9uZWdhdHRTdG10oGhhdXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGZ1bGxzY3JlZW5fYXR0ZXN0YXRpb26hYXRoRGF0YVjESZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2NBAAAAAAAAAAAAAAAAAAAAAAAAAAAAEGZ1bGxzY3JlZW5fYXR0ZXN0YXRpb24"
},
"type": "public-key",
"clientExtensionResults": {}
},
"device_name": "iPhone 14 Pro"
}
Response 201:
{
"message": "Passkey registered successfully",
"credential": {
"id": "A3B5C7D9E1F2G4H6I8J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6",
"name": "iPhone 14 Pro",
"created_at": "2024-01-20T16:30:00Z",
"last_used_at": null,
"device_type": "mobile",
"is_active": true
}
}
Response 400 (Invalid credential):
Response 400 (Duplicate credential):
POST /webauthn/authenticate/begin/
Begin passkey authentication. Returns a challenge.
Request:
Response 200:
{
"challenge": "A3B5C7D9E1F2G4H6I8J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6",
"rpId": "localhost:8000",
"allowCredentials": [
{
"id": "A3B5C7D9E1F2G4H6I8J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6",
"type": "public-key"
}
],
"userVerification": "preferred",
"timeout": 300000
}
Response 200 (Resident key mode):
{
"challenge": "A3B5C7D9E1F2G4H6I8J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6",
"rpId": "localhost:8000",
"allowCredentials": [],
"userVerification": "required",
"timeout": 300000
}
Response 400 (User not found):
POST /webauthn/authenticate/complete/
Complete passkey authentication. Returns JWT tokens.
Request:
{
"challenge_id": 12345,
"credential": {
"id": "A3B5C7D9E1F2G4H6I8J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6",
"rawId": "A3B5C7D9E1F2G4H6I8J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6",
"response": {
"clientDataJSON": "eyJ0eXBlIjoid2ViYXV0aG4uZ2V0IiwiY2hhbGxlbmdlIjoiQTNINUQ3RTlGMUcyRzRIOEk4SjBLMUwyTTNONE81UDZRN1I4UzlUMFUxVjJXM1g0WTVaNiIsIm9yaWdpbiI6Imh0dHBzOi8vbG9jYWxob3N0OjgwMDAiLCJjcm9zc09yaWdpbiI6ZmFsc2UsImF1dGhlbnRpY2F0b3JEYXRhIjoiU1RaTVlJYlJibUZpYkdsemNHRnpjM2R2Y21WeVgybGtJam9pUTFWVFZFOU5SVkpmTVRJek5EVTJJaXdpYVhOemRXVmtYMlJoZEdVaU9pSXlNREkwTFRFd0xURXdWREV3T2pBd09qQXdXaUlzSW1WNGNHbHllVjlrWVhSbElqb2lNakF5TlMweE1DMHhNRlF4TURvd01Eb3dNRm9pTENKd2NtOWtkV04wSWpvaWRIbHJMVzl3WlhKaGRHOXlJaXdpYldGamFHbHVaVjltYVc1blpYSndjbWx1ZENJNklqRXlNelExTmpjNE9UQXhNak0wSW4wPQ",
"authenticatorData": "SZYN5YgOjGh0NBcPZHZgW4_krrmihjLHmVzzuoMdl2MBAAAAAQ",
"signature": "MEUCIQCdwBCrP_zZyGLYQh9a5r3U9k4FzJg2dJ7L7fJgQIgYKj8pXuYqJ5fX9r8tY2L3K4J7G6H5F4Z3E2D1C0B8A",
"userHandle": "MTIzNDU2Nzg5MA"
},
"type": "public-key",
"clientExtensionResults": {}
},
"device_info": "Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15"
}
Response 200:
{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"user": {
"id": 42,
"email": "user@example.com",
"first_name": "John",
"last_name": "Doe",
"is_active": true,
"last_login": "2024-01-20T17:00:00Z"
},
"message": "Authentication successful",
"credential_used": "A3B5C7D9E1F2G4H6I8J0K1L2M3N4O5P6Q7R8S9T0U1V2W3X4Y5Z6"
}
Response 400 (Invalid assertion):
Response 401 (Authentication failed):
GET /webauthn/credentials/
List registered passkeys for the current user.
Headers (required):
Response 200:
{
"credentials": [
{
"id": 1,
"device_name": "iPhone 14 Pro",
"created_at": "2024-01-15T10:30:00Z",
"last_used_at": "2024-01-20T16:45:00Z",
"authenticator_type": "platform",
"is_resident_key": true,
"is_active": true
},
{
"id": 2,
"device_name": "YubiKey 5",
"created_at": "2024-01-10T14:20:00Z",
"last_used_at": "2024-01-18T09:15:00Z",
"authenticator_type": "cross-platform",
"is_resident_key": false,
"is_active": true
}
]
}
DELETE /webauthn/credentials/<id>/
Delete a registered passkey.
Headers (required):
Path Parameters:
- id: ID of the passkey to delete
Response 204:
(no content - successful deletion)
Response 404 (Not found):
AI Agent Endpoints (AIRS)
Tenxyte includes a full set of AI/Agent Identity & Runtime Security endpoints for managing agent tokens, heartbeats, pending actions, and usage reporting:
| Method | Endpoint | Description |
|---|---|---|
GET/POST |
/ai/tokens/ |
List / create agent tokens |
GET/PUT/DELETE |
/ai/tokens/<id>/ |
Agent token detail |
POST |
/ai/tokens/<id>/revoke/ |
Revoke an agent token |
POST |
/ai/tokens/<id>/suspend/ |
Suspend an agent token |
POST |
/ai/tokens/<id>/heartbeat/ |
Agent heartbeat ping |
POST |
/ai/tokens/<id>/report-usage/ |
Report agent usage metrics |
POST |
/ai/tokens/revoke-all/ |
Revoke all agent tokens |
GET |
/ai/pending-actions/ |
List pending human-in-the-loop actions |
POST |
/ai/pending-actions/<id>/approve/ |
Approve a pending action |
POST |
/ai/pending-actions/<id>/reject/ |
Reject a pending action |
→ See AIRS Guide for full request/response documentation, clearance levels, and agent lifecycle management.
Legend
- — Requires
Authorization: Bearer <access_token> permission.code— Requires that specific permission