Skip to main content

User Management API Documentation

Overview

The User Management API provides complete CRUD operations for managing users within your multi-tenant Django application. Each tenant has its own isolated user base, and this API allows you to create, read, update, and delete users within the current tenant's schema.

Base URL: /api/users/

Authentication: JWT Bearer Token (required for all endpoints)

Permissions:

  • All endpoints require authentication
  • Non-superusers can only see active users
  • Non-superusers cannot delete superusers
  • Users cannot delete their own accounts

API Response Format

All API responses follow a consistent wrapper format using AppResponse and AppDataResponse classes:

Success Responses (with data)

Endpoints that return data (GET, POST, PUT) wrap the response in this format:

{
"success": true,
"message": "Operation description",
"status_code": 200,
"data": {
// Actual response data here
}
}

Success Responses (without data)

Endpoints that don't return data (DELETE) use this format:

{
"success": true,
"message": "Operation completed successfully",
"status_code": 200
}

Paginated Responses

List endpoints that use pagination wrap the results in:

{
"success": true,
"message": "Data retrieved successfully",
"status_code": 200,
"data": [
// Array of items
],
"total": 100,
"page": 1,
"page_size": 10,
"total_pages": 10
}

Error Responses

Error responses follow this format:

{
"success": false,
"message": "Error description",
"status_code": 400
}

Validation Error Responses

Validation errors include field-level errors:

{
"success": false,
"message": "Validation failed",
"status_code": 400,
"data": {
"field_name": ["Error message"]
},
"error_code": "VALIDATION_ERROR"
}

Note: In the examples below, the wrapper format is shown for clarity. All responses follow this pattern.


Table of Contents

  1. List Users
  2. Create User
  3. Get User Details
  4. Update User
  5. Delete User (Soft Delete)
  6. Get Current User
  7. User Attributes
  8. API Token Management (Knox)
  9. Error Codes
  10. Important Notes
  11. Complete Workflow Example
  12. Integration with Social Authentication

1. List Users

Get a list of all users in the current tenant.

Endpoint: GET /api/users/

Authentication: Required

Query Parameters:

ParameterTypeDescription
searchstringSearch by username, email, first_name, or last_name
is_activebooleanFilter by active status (true/false)
is_staffbooleanFilter by staff status (true/false)
is_superuserbooleanFilter by superuser status (true/false)
is_deletedbooleanFilter by deleted status (true/false)
orderingstringOrder by field (e.g., username, -date_joined, email)
pageintegerPage number for pagination
page_sizeintegerNumber of results per page

Example Request:

curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Example Request with Filters:

# Search for users
curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/?search=john" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

# Get only active users
curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/?is_active=true" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

# Get staff users ordered by date joined
curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/?is_staff=true&ordering=-date_joined" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Response (200 OK):

{
"success": true,
"message": "Data retrieved successfully",
"status_code": 200,
"data": [
{
"id": 1,
"username": "john.doe",
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"is_active": true,
"is_staff": false,
"is_deleted": false,
"date_joined": "2025-01-15T10:30:00Z",
"last_login": "2025-01-20T14:22:00Z",
"attributes": {}
},
{
"id": 2,
"username": "jane.smith",
"email": "jane.smith@example.com",
"first_name": "Jane",
"last_name": "Smith",
"full_name": "Jane Smith",
"is_active": true,
"is_staff": true,
"is_deleted": false,
"date_joined": "2025-01-14T09:15:00Z",
"last_login": "2025-01-19T16:45:00Z",
"attributes": {
"department": "HR",
"phone_number": "1234567890"
}
}
],
"total": 10,
"page": 1,
"page_size": 10,
"total_pages": 1
}

Note:

  • Returns all users by default (including soft-deleted users)
  • Non-superusers can only see active users
  • Use ?is_deleted=false to filter out deleted users
  • Default ordering is by most recent (-date_joined)

2. Create User

Create a new user in the current tenant.

Endpoint: POST /api/users/

Authentication: Required

Request Body:

FieldTypeRequiredDescription
usernamestringYesUnique username for the user
emailstringYesUnique email address
passwordstringYes*User password (min 8 characters recommended)
confirm_passwordstringYes*Must match password field
first_namestringNoUser's first name
last_namestringNoUser's last name
is_activebooleanNoActive status (default: true)
is_staffbooleanNoStaff status (default: false)

*Note: If password is not provided, the user will have an unusable password (suitable for OAuth-only users).

Example 1: Create Standard User

Request:

curl -X POST "http://tenant1.127.0.0.1.nip.io:8000/api/users/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "john.doe",
"email": "john.doe@example.com",
"password": "SecurePass123!",
"confirm_password": "SecurePass123!",
"first_name": "John",
"last_name": "Doe",
"is_active": true
}'

Response (201 Created):

{
"success": true,
"message": "User created successfully",
"status_code": 201,
"data": {
"id": 5,
"username": "john.doe",
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"is_deleted": false,
"date_joined": "2025-01-20T15:30:00Z",
"last_login": null,
"groups": [],
"user_permissions": [],
"attributes": {},
"missing_attributes": {}
}
}

Example 2: Create Staff User

Request:

curl -X POST "http://tenant1.127.0.0.1.nip.io:8000/api/users/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "admin.user",
"email": "admin@example.com",
"password": "AdminPass123!",
"confirm_password": "AdminPass123!",
"first_name": "Admin",
"last_name": "User",
"is_active": true,
"is_staff": true
}'

Example 3: Create OAuth-Only User (No Password)

Request:

curl -X POST "http://tenant1.127.0.0.1.nip.io:8000/api/users/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "oauth.user",
"email": "oauth@example.com",
"first_name": "OAuth",
"last_name": "User"
}'

Note: This user can only authenticate via social login (OAuth).

Validation Errors

Missing Required Field:

{
"success": false,
"message": "User validation failed",
"status_code": 400,
"data": {
"username": ["This field is required."]
},
"error_code": "VALIDATION_ERROR"
}

Passwords Don't Match:

{
"success": false,
"message": "User validation failed",
"status_code": 400,
"data": {
"confirm_password": ["Passwords do not match."]
},
"error_code": "VALIDATION_ERROR"
}

Duplicate Username:

{
"success": false,
"message": "User validation failed",
"status_code": 400,
"data": {
"username": ["A user with this username already exists."]
},
"error_code": "VALIDATION_ERROR"
}

Duplicate Email:

{
"success": false,
"message": "User validation failed",
"status_code": 400,
"data": {
"email": ["A user with this email already exists."]
},
"error_code": "VALIDATION_ERROR"
}

3. Get User Details

Retrieve detailed information about a specific user, including their groups and permissions.

Endpoint: GET /api/users/{username}/

Authentication: Required

Example Request:

curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/john.doe/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Response (200 OK):

{
"success": true,
"message": "User retrieved successfully",
"status_code": 200,
"data": {
"id": 5,
"username": "john.doe",
"email": "john.doe@example.com",
"first_name": "John",
"last_name": "Doe",
"full_name": "John Doe",
"is_active": true,
"is_staff": false,
"is_superuser": false,
"is_deleted": false,
"date_joined": "2025-01-20T15:30:00Z",
"last_login": "2025-01-21T10:15:00Z",
"groups": [
{
"id": 1,
"name": "Editors"
},
{
"id": 3,
"name": "Viewers"
}
],
"user_permissions": [
{
"id": 24,
"name": "Can view log entry",
"codename": "view_logentry",
"content_type": "admin.logentry"
},
{
"id": 32,
"name": "Can add user",
"codename": "add_user",
"content_type": "auth.user"
}
],
"attributes": {},
"missing_attributes": {}
}
}

Note: This endpoint includes user's groups and individual permissions, which are NOT included in the list view for performance reasons.


4. Update User

Update an existing user's information. Password updates are NOT allowed through this endpoint.

Endpoint: PUT /api/users/{username}/

Authentication: Required

Request Body: Any fields from Create User except password and confirm_password

Important Notes:

  • Password cannot be updated through this endpoint (use password reset flow)
  • is_deleted field cannot be updated (use DELETE endpoint for soft delete)
  • All fields are optional (partial updates supported)

Example 1: Update User Information

Request:

curl -X PUT "http://tenant1.127.0.0.1.nip.io:8000/api/users/john.doe/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Jonathan",
"last_name": "Doe",
"email": "jonathan.doe@example.com"
}'

Example 2: Deactivate User

Request:

curl -X PUT "http://tenant1.127.0.0.1.nip.io:8000/api/users/john.doe/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"is_active": false
}'

Example 3: Promote User to Staff

Request:

curl -X PUT "http://tenant1.127.0.0.1.nip.io:8000/api/users/john.doe/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"is_staff": true
}'

Response (200 OK):

{
"success": true,
"message": "User updated successfully",
"status_code": 200,
"data": {
"id": 5,
"username": "john.doe",
"email": "jonathan.doe@example.com",
"first_name": "Jonathan",
"last_name": "Doe",
"full_name": "Jonathan Doe",
"is_active": true,
"is_staff": true,
"is_superuser": false,
"is_deleted": false,
"date_joined": "2025-01-20T15:30:00Z",
"last_login": "2025-01-21T10:15:00Z",
"groups": [],
"user_permissions": [],
"attributes": {},
"missing_attributes": {}
}
}

Error: Attempting Password Update

Request:

curl -X PUT "http://tenant1.127.0.0.1.nip.io:8000/api/users/john.doe/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"password": "NewPassword123!"
}'

Response (400 Bad Request):

{
"success": false,
"message": "User validation failed",
"status_code": 400,
"data": {
"password": ["Password cannot be updated through this endpoint."]
},
"error_code": "VALIDATION_ERROR"
}

5. Delete User (Soft Delete)

Soft delete a user by setting their is_deleted flag to true. The user record is not removed from the database.

Endpoint: DELETE /api/users/{username}/

Authentication: Required

Restrictions:

  • Users cannot delete themselves
  • Non-superusers cannot delete superusers
  • This is a soft delete (user record remains in database)

Example Request:

curl -X DELETE "http://tenant1.127.0.0.1.nip.io:8000/api/users/john.doe/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Success Response (200 OK):

{
"success": true,
"message": "User deleted successfully.",
"status_code": 200
}

Error Responses

Attempting to Delete Self:

{
"success": false,
"message": "You cannot delete your own account.",
"status_code": 400
}

Non-Superuser Attempting to Delete Superuser:

{
"success": false,
"message": "You do not have permission to delete superusers.",
"status_code": 403
}

User Not Found (404):

{
"detail": "Not found."
}

6. Get Current User (Me)

Get details of the currently authenticated user.

Endpoint: GET /api/users/me/

Authentication: Required

Example Request:

curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/me/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Response (200 OK):

{
"success": true,
"message": "User retrieved successfully",
"status_code": 200,
"data": {
"id": 1,
"username": "current.user",
"email": "current@example.com",
"first_name": "Current",
"last_name": "User",
"full_name": "Current User",
"is_active": true,
"is_staff": true,
"is_superuser": false,
"is_deleted": false,
"date_joined": "2025-01-10T08:00:00Z",
"last_login": "2025-01-21T11:30:00Z",
"groups": [
{
"id": 2,
"name": "Administrators"
}
],
"user_permissions": [],
"attributes": {},
"missing_attributes": {}
}
}

Use Case: This endpoint is useful for:

  • Displaying current user information in UI
  • Checking current user's permissions
  • Profile pages
  • Navigation/header user info

7. User Attributes

User attributes are tenant-specific custom fields stored in each user's attributes JSONField. Unlike standard Django user fields (username, email, etc.), attributes are:

  • Dynamic: Defined per-tenant using JSON Schema
  • Flexible: Can have different attributes for different tenants
  • Validated: Automatically validated against the tenant's schema
  • Optional: Not all users need to have all attributes immediately

7.1. Schema Management API

Get Current Attributes Schema

Retrieve the current tenant's user attributes schema.

Endpoint: GET /api/users/attributes/

Authentication: Required

Request:

curl -X GET http://localhost:8000/api/users/attributes/ \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Response (200 OK):

{
"success": true,
"message": "User attributes schema retrieved successfully",
"status_code": 200,
"data": {
"type": "object",
"title": "User Attributes",
"properties": {
"department": {
"type": ["string", "null"],
"title": "Department",
"enum": ["HR", "DEV", "MANAGER", "SALES", "SUPPORT"]
},
"phone_number": {
"type": ["string", "null"],
"title": "Phone Number",
"minLength": 10,
"maxLength": 15
},
"emp_no": {
"type": ["string", "null"],
"title": "Employee Number",
"pattern": "^EMP[0-9]{5}$"
}
},
"required": ["department", "phone_number"]
}
}

Empty Schema Response: If no schema is configured, returns:

{
"success": true,
"message": "User attributes schema retrieved successfully",
"status_code": 200,
"data": {}
}

Update Attributes Schema

Update or create the user attributes schema for the current tenant.

Endpoint: POST /api/users/attributes/

Authentication: Required (Admin only - is_staff or is_superuser)

Request Body: Direct JSON Schema (not wrapped)

Request:

curl -X POST http://localhost:8000/api/users/attributes/ \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "object",
"title": "Employee Attributes",
"properties": {
"department": {
"type": ["string", "null"],
"title": "Department",
"enum": ["HR", "DEV", "MANAGER", "SALES", "SUPPORT"]
},
"phone_number": {
"type": ["string", "null"],
"title": "Phone Number",
"minLength": 10,
"maxLength": 15
},
"emp_no": {
"type": ["string", "null"],
"title": "Employee Number",
"minLength": 8,
"maxLength": 20
}
},
"required": ["department", "phone_number"]
}'

Response (200 OK):

{
"success": true,
"message": "User attributes schema updated successfully",
"status_code": 200,
"data": {
"type": "object",
"title": "Employee Attributes",
"properties": {
"department": {
"type": ["string", "null"],
"title": "Department",
"enum": ["HR", "DEV", "MANAGER", "SALES", "SUPPORT"]
},
"phone_number": {
"type": ["string", "null"],
"title": "Phone Number",
"minLength": 10,
"maxLength": 15
},
"emp_no": {
"type": ["string", "null"],
"title": "Employee Number",
"minLength": 8,
"maxLength": 20
}
},
"required": ["department", "phone_number"]
}
}

Error Response (403 Forbidden):

{
"success": false,
"message": "Only administrators can update attributes schema",
"status_code": 403
}
Full Replacement

The POST endpoint replaces the entire schema. To add a new property, you must send the complete schema including all existing properties.

Workflow: GET current schema → Modify → POST updated schema


7.2. JSON Schema Format

User attributes use JSON Schema Draft 2020-12 format (latest standard).

Required Structure

{
"type": "object", // REQUIRED: Must be "object"
"title": "...", // Optional: Display name
"properties": { // REQUIRED: Define your custom fields
"field_name": {
"type": ["string", "null"], // Type with null support
"title": "...", // Display name
// ... additional constraints
}
},
"required": ["field1", "field2"] // Optional: Required fields
}

Supported Field Types

{
"properties": {
"text_field": {
"type": ["string", "null"],
"minLength": 5,
"maxLength": 100,
"pattern": "^[A-Z]+$"
},
"number_field": {
"type": ["number", "null"],
"minimum": 0,
"maximum": 100
},
"integer_field": {
"type": ["integer", "null"],
"minimum": 1
},
"boolean_field": {
"type": ["boolean", "null"]
},
"enum_field": {
"type": ["string", "null"],
"enum": ["option1", "option2", "option3"]
}
}
}
Allowing Null Values

Always include "null" in the type array for optional flexibility:

"type": ["string", "null"]  // ✅ Recommended
"type": "string" // ❌ Too strict

This allows:

  1. Partial updates: Update only some attributes without providing all
  2. Removing attributes: Set a value to null to clear it
  3. Gradual completion: Users can fill required fields over time

7.3. Working with User Attributes

Creating a User with Attributes

Endpoint: POST /api/users/

Request:

curl -X POST http://localhost:8000/api/users/ \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "john_doe",
"email": "john@example.com",
"password": "SecurePass123!",
"confirm_password": "SecurePass123!",
"first_name": "John",
"last_name": "Doe",
"attributes": {
"department": "DEV",
"phone_number": "1234567890",
"emp_no": "EMP12345"
}
}'

Response (201 Created):

{
"id": 42,
"username": "john_doe",
"email": "john@example.com",
"first_name": "John",
"last_name": "Doe",
"attributes": {
"department": "DEV",
"phone_number": "1234567890",
"emp_no": "EMP12345"
},
"missing_attributes": {},
"groups": [],
"user_permissions": []
}

Updating User Attributes (Partial Update)

Endpoint: PUT /api/users/{username}/

The update endpoint supports partial updates - you only need to send the attributes you want to change.

Request (Update single attribute):

curl -X PUT http://localhost:8000/api/users/john_doe/ \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"attributes": {
"department": "MANAGER"
}
}'

Before:

{
"attributes": {
"department": "DEV",
"phone_number": "1234567890",
"emp_no": "EMP12345"
}
}

After:

{
"attributes": {
"department": "MANAGER", // ← Updated
"phone_number": "1234567890", // ← Preserved
"emp_no": "EMP12345" // ← Preserved
}
}
Merge Behavior

Attributes are merged, not replaced. Existing attributes are preserved unless explicitly updated or removed.


Removing Attributes

To remove an optional attribute from a user, send null as the value.

Request:

curl -X PUT http://localhost:8000/api/users/john_doe/ \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"attributes": {
"emp_no": null
}
}'

Before:

{
"attributes": {
"department": "HR",
"phone_number": "1234567890",
"emp_no": "EMP12345"
}
}

After:

{
"attributes": {
"department": "HR",
"phone_number": "1234567890",
"emp_no": null
}
}
Required Attributes Cannot Be Removed

You cannot set a required attribute to null. The validation will fail:

// Request
{"attributes": {"department": null}}

// Response (400 Bad Request)
{
"attributes.department": [
"None is not of type 'string'"
]
}

7.4. Missing Attributes Detection

When a user is missing required attributes, the API automatically detects and returns them in the missing_attributes field.

How It Works

  1. Schema defines: "required": ["department", "phone_number"]
  2. User has: {"department": "HR"}
  3. API returns: {"missing_attributes": {"phone_number": {...}}}

Response Format

The missing_attributes field returns a dictionary with missing field names as keys and their JSON Schema definitions as values:

{
"username": "john_doe",
"attributes": {
"department": "HR"
},
"missing_attributes": {
"phone_number": {
"type": ["string", "null"],
"title": "Phone Number",
"minLength": 10,
"maxLength": 15
}
}
}

Using Missing Attributes in Frontend

// Fetch user details
const response = await fetch('/api/users/john_doe/', {
headers: { 'Authorization': `Bearer ${token}` }
});
const user = await response.json();

// Check if user has missing required attributes
if (Object.keys(user.missing_attributes).length > 0) {
// Show form to complete profile
showProfileCompletionForm(user.missing_attributes);
}
List View

The missing_attributes field only appears in detail view (GET /api/users/{username}/), not in list view, for performance reasons.


7.5. Validation Rules

User attributes are validated at the model level (in the User model's save() method), ensuring validation occurs regardless of how the user is created - via API, Django admin, management commands, or any other method.

Reserved Field Names

The following field names are reserved and cannot be used as custom attribute names:

  • Identity: id, pk, uuid, username, email, password
  • Profile: first_name, last_name, full_name
  • Status: is_active, is_staff, is_superuser, is_deleted
  • Timestamps: date_joined, last_login, created_at, updated_at
  • Relations: groups, user_permissions
  • Custom: attributes

Error Example:

{
"error": "Attribute name 'email' is reserved and cannot be used (conflicts with User model field)"
}

Field Name Format

Attribute names must:

  • Start with a lowercase letter
  • Contain only lowercase letters, numbers, and underscores
  • Match regex: ^[a-z][a-z0-9_]*$

Valid: department, phone_number, emp_no, user_level_2

Invalid: Department, phone-number, 2nd_phone, _private


7.6. Example Use Cases

Example 1: Employee Management

{
"type": "object",
"title": "Employee Attributes",
"properties": {
"employee_id": {
"type": ["string", "null"],
"title": "Employee ID",
"pattern": "^EMP[0-9]{5}$"
},
"department": {
"type": ["string", "null"],
"title": "Department",
"enum": ["HR", "Engineering", "Sales", "Marketing", "Finance"]
},
"job_title": {
"type": ["string", "null"],
"title": "Job Title"
},
"manager_email": {
"type": ["string", "null"],
"title": "Manager Email",
"format": "email"
},
"hire_date": {
"type": ["string", "null"],
"title": "Hire Date",
"format": "date"
}
},
"required": ["employee_id", "department", "hire_date"]
}

Example 2: Customer Portal

{
"type": "object",
"title": "Customer Attributes",
"properties": {
"company_name": {
"type": ["string", "null"],
"title": "Company Name",
"minLength": 2,
"maxLength": 100
},
"industry": {
"type": ["string", "null"],
"title": "Industry",
"enum": ["Technology", "Healthcare", "Finance", "Retail", "Other"]
},
"customer_tier": {
"type": ["string", "null"],
"title": "Customer Tier",
"enum": ["Free", "Pro", "Enterprise"]
},
"annual_revenue": {
"type": ["number", "null"],
"title": "Annual Revenue (USD)",
"minimum": 0
}
},
"required": ["company_name", "customer_tier"]
}

7.7. Best Practices

1. Always Include null in Type Arrays

// ✅ Good: Allows flexibility
"type": ["string", "null"]

// ❌ Bad: Too restrictive
"type": "string"

2. Mark Only Essential Fields as Required

Only mark fields as required if they are absolutely necessary for your application to function. This allows users to gradually complete their profiles.

// ✅ Good: Only critical fields required
"required": ["department"]

// ❌ Bad: Too many required fields
"required": ["department", "phone", "address", "title", "manager"]

3. Use Descriptive Titles

Provide clear title fields for all properties to help frontend developers build better UIs:

{
"emp_no": {
"type": ["string", "null"],
"title": "Employee Number", // ✅ Clear
"description": "Format: EMP12345" // ✅ Helpful
}
}

4. Schema Update Workflow

When updating schemas:

  1. GET current schema first
  2. Extract the schema from the data field
  3. Modify locally
  4. POST complete updated schema
// Fetch current schema
const response = await fetch('/api/users/attributes/');
const schemaResponse = await response.json();
const currentSchema = schemaResponse.data;

// Add new field
currentSchema.properties.new_field = {
type: ["string", "null"],
title: "New Field"
};

// Update schema
await fetch('/api/users/attributes/', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(currentSchema)
});

5. Validate Client-Side Before Sending

Use the schema to validate on the frontend before making API calls:

import Ajv from 'ajv';

const ajv = new Ajv();
const schema = await fetch('/api/users/attributes/').then(r => r.json());
const validate = ajv.compile(schema);

const valid = validate(userAttributes);
if (!valid) {
console.log(validate.errors);
showValidationErrors(validate.errors);
return;
}

// Proceed with API call
await updateUser(username, { attributes: userAttributes });

8. API Token Management (Knox)

The API Token system provides Personal Access Tokens (PATs) for programmatic API access. Tokens act as an alternative to JWT authentication and inherit all permissions from the associated user account.

Base URL: /api/users/token/

Authentication for Token Management:

  • Creating tokens: JWT, Session, or XSessionToken (Knox tokens cannot create new tokens)
  • Using tokens: Any endpoint accepts Authorization: Api-Key <token>

Key Features:

  • Secure: Tokens hashed with SHA-512, full token shown only once
  • Flexible Expiry: Set custom expiration or create infinite tokens
  • Named Tokens: Assign human-readable names for easy identification
  • Permission Inheritance: Tokens have all permissions of the user
  • Unlimited: No limit on tokens per user
Security Notice

API tokens are equivalent to passwords. Store them securely and never commit them to version control. If a token is compromised, revoke it immediately using the DELETE endpoint.


8.1. Create API Token

Create a new authentication token for the authenticated user.

Endpoint: POST /api/users/token/

Authentication: JWT, Session, or XSessionToken (NOT Knox tokens)

Request Body:

FieldTypeRequiredDescription
namestringYesHuman-readable identifier (max 50 chars)
expirydatetime/nullYesISO 8601 datetime or null for infinite token
Infinite Tokens

Set expiry to null to create tokens that never expire. Use this for long-term integrations, CI/CD pipelines, or service accounts.

Example 1: Create Token with Expiry

Request:

curl -X POST "http://localhost:8000/api/users/token/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "30-Day Production Token",
"expiry": "2025-12-31T23:59:59Z"
}'

Response (201 Created):

{
"success": true,
"message": "Token created successfully. Please save this token securely as it cannot be retrieved again.",
"status_code": 201,
"data": {
"id": "7b887f2044dde644107cbbe9225f0ce7ba749d4605d76f99672982128484d5d1...",
"name": "30-Day Production Token",
"token": "abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
"created": "2025-01-19T10:30:00Z",
"expiry": "2025-12-31T23:59:59Z"
}
}

Example 2: Create Infinite Token

Request:

curl -X POST "http://localhost:8000/api/users/token/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "CI/CD Pipeline Token",
"expiry": null
}'

Response (201 Created):

{
"success": true,
"message": "Token created successfully. Please save this token securely as it cannot be retrieved again.",
"status_code": 201,
"data": {
"id": "ad968e2c99ce6b12e10d5aa3c4d115eedc6db2652a80a0efe7068fb13f14c41e...",
"name": "CI/CD Pipeline Token",
"token": "5935b23a252169a48d3fff3524aaee8f30a1e46630b94afe794da74cf6beb615",
"created": "2025-01-19T10:35:00Z",
"expiry": null
}
}
Save the Token Now!

The token field (inside data) contains the full plaintext token and is shown ONLY ONCE. Save it immediately in a secure location (password manager, secrets vault, environment variable). It cannot be retrieved again.

The id field is the token's digest (hash) and can be used to identify and revoke the token later.

Validation Errors

Missing Name:

{
"success": false,
"message": "Validation failed",
"status_code": 400,
"data": {
"name": ["This field is required."]
},
"error_code": "VALIDATION_ERROR"
}

Missing Expiry:

{
"success": false,
"message": "Validation failed",
"status_code": 400,
"data": {
"expiry": ["This field is required."]
},
"error_code": "VALIDATION_ERROR"
}

Past Expiry Date:

{
"success": false,
"message": "Validation failed",
"status_code": 400,
"data": {
"expiry": ["Expiry date must be in the future"]
},
"error_code": "VALIDATION_ERROR"
}

Invalid Expiry Format:

{
"success": false,
"message": "Validation failed",
"status_code": 400,
"data": {
"expiry": ["Datetime has wrong format. Use one of these formats instead: YYYY-MM-DDThh:mm[:ss[.uuuuuu]][+HH:MM|-HH:MM|Z]."]
},
"error_code": "VALIDATION_ERROR"
}

8.2. List API Tokens

Retrieve all API tokens for the authenticated user. Returns metadata only (not the full tokens).

Endpoint: GET /api/users/token/

Authentication: Any authentication method (including Knox tokens)

Example Request:

curl -X GET "http://localhost:8000/api/users/token/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Response (200 OK):

{
"success": true,
"message": "Tokens retrieved successfully",
"status_code": 200,
"data": [
{
"id": "7b887f2044dde644107cbbe9225f0ce7ba749d4605d76f99672982128484d5d1...",
"name": "30-Day Production Token",
"created": "2025-01-19T10:30:00Z",
"expiry": "2025-12-31T23:59:59Z"
},
{
"id": "ad968e2c99ce6b12e10d5aa3c4d115eedc6db2652a80a0efe7068fb13f14c41e...",
"name": "CI/CD Pipeline Token",
"created": "2025-01-19T10:35:00Z",
"expiry": null
},
{
"id": "5e91e13f250e058977dbd8724a95795bd776cfe88dd25604a57b5eac8ed33ae6...",
"name": "Development Token",
"created": "2025-01-18T14:20:00Z",
"expiry": "2025-02-18T14:20:00Z"
}
],
"total": 3
}

Empty Response (No Tokens):

{
"success": true,
"message": "Tokens retrieved successfully",
"status_code": 200,
"data": [],
"total": 0
}

Notes:

  • Tokens ordered by creation date (newest first)
  • Only returns tokens for authenticated user
  • Full token value is NEVER shown after creation
  • Use for auditing, token management UI, identifying which tokens to revoke

8.3. Revoke API Token

Permanently delete an API token. This immediately invalidates the token.

Endpoint: DELETE /api/users/token/{id}/

Authentication: JWT, Session, or XSessionToken recommended

Path Parameters:

ParameterTypeDescription
idstringToken digest (from create/list response)

Example Request:

curl -X DELETE "http://localhost:8000/api/users/token/7b887f2044dde644107cbbe9225f0ce7ba749d4605d76f99672982128484d5d1fcab8d5ecbe530f75dfafd415df5dff97b34e84b5086acb5303bb3f6cd100943/" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"

Success Response (204 No Content):

{
"success": true,
"message": "Token revoked successfully",
"status_code": 204
}

Error Response (404 Not Found):

{
"success": false,
"message": "Token not found",
"status_code": 404
}

Notes:

  • Token is permanently deleted from database
  • Cannot revoke tokens belonging to other users (404)
  • Revoked tokens immediately stop working
  • Cannot undo revocation - must create new token if needed

8.4. Using API Tokens for Authentication

Once created, use API tokens in the Authorization header to authenticate requests to any API endpoint.

Header Format

Authorization: Api-Key <your_token_here>
Correct Header Prefix

The header prefix is Api-Key (NOT Bearer, Token, or Api-Token). Using the wrong prefix will result in 401 Unauthorized errors.

Example Usage

Get User List:

curl -X GET "http://localhost:8000/api/users/" \
-H "Authorization: Api-Key 5935b23a252169a48d3fff3524aaee8f30a1e46630b94afe794da74cf6beb615"

Create New User:

curl -X POST "http://localhost:8000/api/users/" \
-H "Authorization: Api-Key 5935b23a252169a48d3fff3524aaee8f30a1e46630b94afe794da74cf6beb615" \
-H "Content-Type: application/json" \
-d '{
"username": "newuser",
"email": "newuser@example.com",
"password": "SecurePass123!",
"confirm_password": "SecurePass123!"
}'

Get Current User:

curl -X GET "http://localhost:8000/api/users/me/" \
-H "Authorization: Api-Key 5935b23a252169a48d3fff3524aaee8f30a1e46630b94afe794da74cf6beb615"

Permission Inheritance

API tokens inherit ALL permissions and roles from the associated user account:

  • Superuser token: Has all permissions across all resources
  • Staff token: Can access admin-restricted endpoints
  • Regular user token: Limited to user's assigned permissions
  • Group permissions: Token inherits user's group memberships
  • Object permissions: Works with Django Guardian object-level permissions
  • Tenant context: Automatically scoped to user's tenant

Example - Superuser Token:

# Token from superuser account can delete users
curl -X DELETE "http://localhost:8000/api/users/some-user/" \
-H "Authorization: Api-Key <superuser_token>"
# ✅ Success

Example - Regular User Token:

# Token from regular user cannot delete users
curl -X DELETE "http://localhost:8000/api/users/some-user/" \
-H "Authorization: Api-Key <regular_user_token>"
# ❌ 403 Forbidden

8.5. Token Expiry Behavior

Tokens support two expiry modes: time-based and infinite.

Time-Based Tokens

Tokens with a specific expiry datetime become invalid after that time.

Creation:

{
"name": "Temporary API Key",
"expiry": "2025-06-30T23:59:59Z"
}

Behavior:

  • ✅ Works before 2025-06-30T23:59:59Z
  • ❌ Returns 401 Unauthorized after expiry
  • 🔒 No auto-refresh - expiry never extends
  • 📅 Check expiry via list endpoint

Use Cases:

  • Temporary contractor access
  • Time-limited integrations
  • Demo/trial API keys
  • Compliance requirements (rotate every 90 days)

Infinite Tokens

Tokens with expiry: null never expire automatically.

Creation:

{
"name": "Permanent Service Account",
"expiry": null
}

Behavior:

  • ✅ Works indefinitely until manually revoked
  • 🔒 Must be explicitly deleted to invalidate
  • ⚠️ Higher security risk if compromised

Use Cases:

  • CI/CD pipelines
  • Long-running services
  • Server-to-server communication
  • Internal automation tools
Security Best Practice

Even for infinite tokens, implement regular rotation (e.g., every 6-12 months) by creating a new token and revoking the old one. Many organizations enforce token rotation policies for compliance.


8.6. Security Considerations

Server-Side Security

Token Hashing:

  • Tokens hashed with SHA-512 before storage
  • Only digest (hash) stored in database
  • Full token cannot be recovered from database
  • First 8 characters stored as token_key for identification

Storage:

Database Storage:
- digest: 7b887f204... (SHA-512 hash - PRIMARY KEY)
- token_key: 5935b23a (First 8 chars for ID)
- name: "My Token"
- expiry: "2025-12-31T23:59:59Z"
- user_id: 1

Authentication Flow:

  1. Client sends: Authorization: Api-Key 5935b23a252169a4...
  2. Server hashes received token with SHA-512
  3. Server looks up hash in database
  4. If match found and not expired → Authenticated
  5. Sets request.user to token owner

Client-Side Security

DO:

  • ✅ Store tokens in environment variables
  • ✅ Use secrets managers (AWS Secrets Manager, HashiCorp Vault, etc.)
  • ✅ Store in password managers for personal use
  • ✅ Limit token scope to minimum required permissions
  • ✅ Create separate tokens for different services/environments
  • ✅ Rotate tokens regularly
  • ✅ Revoke tokens immediately if compromised
  • ✅ Monitor token usage logs

DON'T:

  • ❌ Commit tokens to version control (.env.example OK, .env NOT OK)
  • ❌ Share tokens via email/Slack/chat
  • ❌ Store tokens in client-side JavaScript
  • ❌ Log full tokens in application logs
  • ❌ Use same token across multiple environments
  • ❌ Leave unused tokens active

Example - Environment Variables:

# .env (NEVER commit this file)
API_TOKEN=5935b23a252169a48d3fff3524aaee8f30a1e46630b94afe794da74cf6beb615

# .env.example (safe to commit)
API_TOKEN=your_token_here

Example - Docker Secrets:

# docker-compose.yml
services:
app:
environment:
- API_TOKEN_FILE=/run/secrets/api_token
secrets:
- api_token

secrets:
api_token:
external: true

Example - Python Usage:

import os
import requests

# ✅ Load from environment
API_TOKEN = os.environ['API_TOKEN']

# ❌ NEVER hardcode
# API_TOKEN = "5935b23a252169a4..." # DON'T DO THIS!

headers = {'Authorization': f'Api-Key {API_TOKEN}'}
response = requests.get('http://localhost:8000/api/users/', headers=headers)

8.7. Complete Workflow Example

This example demonstrates the full lifecycle of API token management:

# 1. Authenticate with JWT to get access token
JWT_TOKEN=$(curl -X POST "http://localhost:8000/api/auth/jwt/token/" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}' \
| jq -r '.access')

# 2. Create an infinite API token for CI/CD
TOKEN_RESPONSE=$(curl -X POST "http://localhost:8000/api/users/token/" \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "GitHub Actions CI",
"expiry": null
}')

# 3. Extract and save the token (ONLY SHOWN ONCE!)
API_TOKEN=$(echo $TOKEN_RESPONSE | jq -r '.data.token')
TOKEN_ID=$(echo $TOKEN_RESPONSE | jq -r '.data.id')

echo "Save this token securely: $API_TOKEN"
echo "Token ID for revocation: $TOKEN_ID"

# 4. Test the token by fetching users
curl -X GET "http://localhost:8000/api/users/" \
-H "Authorization: Api-Key $API_TOKEN"

# 5. Create another token with 90-day expiry
TEMP_TOKEN_RESPONSE=$(curl -X POST "http://localhost:8000/api/users/token/" \
-H "Authorization: Bearer $JWT_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"name\": \"Contractor Access\",
\"expiry\": \"$(date -d '+90 days' -u +%Y-%m-%dT%H:%M:%SZ)\"
}")

TEMP_TOKEN=$(echo $TEMP_TOKEN_RESPONSE | jq -r '.data.token')
TEMP_TOKEN_ID=$(echo $TEMP_TOKEN_RESPONSE | jq -r '.data.id')

# 6. List all active tokens
curl -X GET "http://localhost:8000/api/users/token/" \
-H "Authorization: Bearer $JWT_TOKEN"

# 7. Use token in automated script
curl -X POST "http://localhost:8000/api/users/" \
-H "Authorization: Api-Key $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "automated.user",
"email": "auto@example.com",
"password": "SecurePass123!",
"confirm_password": "SecurePass123!"
}'

# 8. Revoke the temporary token when contractor leaves
curl -X DELETE "http://localhost:8000/api/users/token/$TEMP_TOKEN_ID/" \
-H "Authorization: Bearer $JWT_TOKEN"

# 9. Verify token was revoked (should return 401)
curl -X GET "http://localhost:8000/api/users/" \
-H "Authorization: Api-Key $TEMP_TOKEN"

8.8. Integration Examples

CI/CD Pipeline (GitHub Actions)

# .github/workflows/deploy.yml
name: Deploy to Production

on:
push:
branches: [main]

jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

- name: Deploy via API
env:
API_TOKEN: ${{ secrets.PRODUCTION_API_TOKEN }}
run: |
curl -X POST "https://api.example.com/api/deployments/" \
-H "Authorization: Api-Key $API_TOKEN" \
-H "Content-Type: application/json" \
-d '{"version": "${{ github.sha }}"}'

Python Script

import os
import requests

class APIClient:
def __init__(self):
self.base_url = os.environ['API_BASE_URL']
self.token = os.environ['API_TOKEN']
self.headers = {
'Authorization': f'Api-Key {self.token}',
'Content-Type': 'application/json'
}

def get_users(self):
response = requests.get(
f'{self.base_url}/api/users/',
headers=self.headers
)
response.raise_for_status()
return response.json()

def create_user(self, user_data):
response = requests.post(
f'{self.base_url}/api/users/',
headers=self.headers,
json=user_data
)
response.raise_for_status()
return response.json()

# Usage
client = APIClient()
users_response = client.get_users()
print(f"Found {users_response['total']} users")

Node.js / JavaScript

// api-client.js
const axios = require('axios');

class APIClient {
constructor() {
this.baseURL = process.env.API_BASE_URL;
this.token = process.env.API_TOKEN;

this.client = axios.create({
baseURL: this.baseURL,
headers: {
'Authorization': `Api-Key ${this.token}`,
'Content-Type': 'application/json'
}
});
}

async getUsers() {
const response = await this.client.get('/api/users/');
return response.data;
}

async createUser(userData) {
const response = await this.client.post('/api/users/', userData);
return response.data;
}
}

// Usage
const client = new APIClient();
client.getUsers()
.then(data => console.log(`Found ${data.total} users`))
.catch(error => console.error('Error:', error.message));

cURL Automation Script

#!/bin/bash
# bulk-user-import.sh

set -e # Exit on error

API_BASE_URL="https://api.example.com"
API_TOKEN="${API_TOKEN:-}" # From environment

if [ -z "$API_TOKEN" ]; then
echo "Error: API_TOKEN environment variable not set"
exit 1
fi

# Function to create user
create_user() {
local username=$1
local email=$2

curl -X POST "$API_BASE_URL/api/users/" \
-H "Authorization: Api-Key $API_TOKEN" \
-H "Content-Type: application/json" \
-d "{
\"username\": \"$username\",
\"email\": \"$email\",
\"password\": \"TempPass123!\",
\"confirm_password\": \"TempPass123!\"
}" \
-w "\nHTTP Status: %{http_code}\n"
}

# Bulk create from CSV
while IFS=, read -r username email; do
echo "Creating user: $username"
create_user "$username" "$email"
sleep 0.5 # Rate limiting
done < users.csv

8.9. Troubleshooting

Common Issues

Problem: 401 "Authentication credentials were not provided"

Solution: Check header format - must be Authorization: Api-Key <token>, not Bearer or Token

# ❌ Wrong
Authorization: Bearer 5935b23a252169a4...
Authorization: Token 5935b23a252169a4...

# ✅ Correct
Authorization: Api-Key 5935b23a252169a4...

Problem: 401 "Invalid token"

Causes:

  • Token expired (check expiry date)
  • Token was revoked
  • Token string corrupted (missing characters, extra spaces)
  • Wrong token for this environment

Solution: Create new token or check token value is complete

Problem: Cannot create token with Knox authentication

Expected behavior: Knox tokens cannot create new tokens (prevents infinite token generation)

Solution: Use JWT, Session, or XSessionToken authentication to create tokens

Problem: Lost token value

Unfortunately, tokens cannot be retrieved after creation.

Solution: Revoke old token and create new one:

# 1. List tokens to find ID
curl -X GET "http://localhost:8000/api/users/token/" \
-H "Authorization: Bearer $JWT_TOKEN"

# 2. Revoke lost token
curl -X DELETE "http://localhost:8000/api/users/token/$TOKEN_ID/" \
-H "Authorization: Bearer $JWT_TOKEN"

# 3. Create replacement token
curl -X POST "http://localhost:8000/api/users/token/" \
-H "Authorization: Bearer $JWT_TOKEN" \
-d '{"name":"Replacement Token","expiry":null}'

9. Error Codes

HTTP CodeDescription
200Success
201User created successfully
400Bad request - validation error or operation not allowed
401Unauthorized - missing or invalid authentication
403Forbidden - insufficient permissions
404Not found - user doesn't exist
500Internal server error

Common Error Responses

Authentication Missing:

{
"detail": "Authentication credentials were not provided."
}

Invalid Token:

{
"detail": "Given token not valid for any token type",
"code": "token_not_valid",
"messages": [
{
"token_class": "AccessToken",
"token_type": "access",
"message": "Token is invalid or expired"
}
]
}

User Not Found:

{
"detail": "Not found."
}

9. Important Notes

Soft Delete vs Hard Delete

  • This API implements soft delete only
  • Deleted users have is_deleted=true but remain in the database
  • Deleted users are still visible in list view (use ?is_deleted=false to filter)
  • Non-superusers cannot see deleted users

Password Management

  • Creation: Password is required on user creation (unless creating OAuth-only user)
  • Updates: Password CANNOT be updated through the update endpoint
  • Password Reset: Use Django's built-in password reset flow or implement custom endpoint
  • Hashing: Passwords are automatically hashed using Django's password hashers

Multi-Tenancy

  • All operations are automatically scoped to the current tenant
  • Users are isolated per tenant schema
  • Tenant is determined by the domain in the request URL

Permissions and Groups

  • Groups and permissions are only shown in the detail view (GET /api/users/{slug}/)
  • NOT included in list view for performance optimization
  • Use prefetch_related for optimal query performance

Field Restrictions

  • id: Read-only, auto-generated
  • date_joined: Read-only, set on creation
  • last_login: Read-only, updated by Django auth
  • is_deleted: Can only be set via DELETE endpoint
  • is_superuser: Not exposed in create/update (set via Django admin)

Email and Username Uniqueness

  • Both email and username must be unique within the tenant
  • Validation errors returned if duplicates detected
  • Case-sensitive comparison

Non-Superuser Restrictions

  • Can only view active users
  • Cannot see deleted users
  • Cannot delete superusers
  • Cannot delete themselves

10. Complete Workflow Example

Here's a complete example of user management workflow:

# 1. Authenticate and get JWT token
TOKEN=$(curl -X POST "http://tenant1.127.0.0.1.nip.io:8000/api/auth/jwt/token/" \
-H "Content-Type: application/json" \
-d '{"username":"admin","password":"password"}' | jq -r '.access')

# 2. Get current user info
curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/me/" \
-H "Authorization: Bearer $TOKEN"

# 3. List all active users
curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/?is_active=true&is_deleted=false" \
-H "Authorization: Bearer $TOKEN"

# 4. Create a new user
USERNAME=$(curl -X POST "http://tenant1.127.0.0.1.nip.io:8000/api/users/" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"username": "new.user",
"email": "new.user@example.com",
"password": "SecurePass123!",
"confirm_password": "SecurePass123!",
"first_name": "New",
"last_name": "User",
"is_active": true
}' | jq -r '.data.username')

# 5. Get new user details
curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/$USERNAME/" \
-H "Authorization: Bearer $TOKEN"

# 6. Update user information
curl -X PUT "http://tenant1.127.0.0.1.nip.io:8000/api/users/$USERNAME/" \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"first_name": "Updated",
"is_staff": true
}'

# 7. Search for users
curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/?search=new" \
-H "Authorization: Bearer $TOKEN"

# 8. Soft delete user
curl -X DELETE "http://tenant1.127.0.0.1.nip.io:8000/api/users/$USERNAME/" \
-H "Authorization: Bearer $TOKEN"

# 9. Verify deletion (should show is_deleted=true)
curl -X GET "http://tenant1.127.0.0.1.nip.io:8000/api/users/?is_deleted=true" \
-H "Authorization: Bearer $TOKEN"

11. Integration with Social Authentication

When users log in via OAuth (Google, GitHub, Keycloak, etc.) with SOCIALACCOUNT_AUTO_CONNECT = True:

Auto-Connect Behavior

✅ If a user with the same email exists:

  • The OAuth account is linked to the existing user
  • No duplicate user is created
  • User logs in with their existing account

✅ If no user with that email exists:

  • A new user is created automatically
  • Username is generated from OAuth provider data
  • User has no password (OAuth-only authentication)

OAuth User Characteristics

  • password: Unusable (empty hash)
  • is_active: True (by default)
  • is_staff: False
  • email: From OAuth provider
  • first_name, last_name: From OAuth provider (if available)

You can manage OAuth users through this API like any other user (except password changes).


Support

For issues or questions:

  • Check logs: docker logs taruvi_web
  • Review admin interface: /admin/auth/user/
  • API documentation: /api/docs/ (Swagger UI)
  • ReDoc: /api/redoc/

Last Updated: January 2025 API Version: 1.0.0 Django Version: 5.2.6 DRF Version: 3.15+