Multi-User API Guide
Overview
Section titled “Overview”This guide explains how to interact with InvokeAI’s API in both single-user and multi-user modes. The API behavior depends on the multiuser configuration setting.
Single-User vs Multi-User Mode
Section titled “Single-User vs Multi-User Mode”Single-User Mode (multiuser: false or option absent):
- No authentication required
- All API endpoints accessible without tokens
- Direct API access like previous InvokeAI versions
- All content visible in unified view
Multi-User Mode (multiuser: true):
- JWT token authentication required
- User-scoped access to resources
- Role-based authorization (admin vs regular user)
- Data isolation between users
Authentication (Multi-User Mode Only)
Section titled “Authentication (Multi-User Mode Only)”Authentication Flow
Section titled “Authentication Flow”When multi-user mode is enabled, most API endpoints require authentication using JWT bearer tokens. The unauthenticated authentication endpoints are GET /api/v1/auth/status, POST /api/v1/auth/setup, and POST /api/v1/auth/login.
Authentication Process:
- Obtain Token: POST credentials to
/api/v1/auth/login - Store Token: Save the JWT token securely
- Use Token: Include token in
Authorizationheader for all requests - Refresh: Re-authenticate when token expires
Login Endpoint
Section titled “Login Endpoint”Endpoint: POST /api/v1/auth/login
Request:
{ "password": "SecurePassword123", "remember_me": false}Response (Success):
{ "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "user": { "user_id": "abc123", "display_name": "John Doe", "is_admin": false, "is_active": true, "created_at": "2024-01-15T10:00:00Z", "updated_at": "2024-01-15T10:00:00Z", "last_login_at": "2024-01-15T15:30:00Z" }, "expires_in": 86400}Response (Error):
{ "detail": "Incorrect email or password"}Status Codes:
200 OK— Authentication successful401 Unauthorized— Invalid credentials403 Forbidden— Account disabled422 Unprocessable Entity— Invalid request format
Using the Token
Section titled “Using the Token”Include the JWT token in the Authorization header with the Bearer scheme:
HTTP Header:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Example HTTP Request:
GET /api/v1/boards HTTP/1.1Host: localhost:9090Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Content-Type: application/jsonToken Expiration
Section titled “Token Expiration”Tokens have a limited lifetime:
- Default: 24 hours (86400 seconds)
- Remember Me: 7 days (604800 seconds)
Handling Expiration:
import requestsimport time
def api_request(url, token, max_retries=1): headers = {"Authorization": f"Bearer {token}"} response = requests.get(url, headers=headers)
if response.status_code == 401: # Token expired # Re-authenticate and retry new_token = login() headers = {"Authorization": f"Bearer {new_token}"} response = requests.get(url, headers=headers)
return responseLogout Endpoint
Section titled “Logout Endpoint”Endpoint: POST /api/v1/auth/logout
Request:
POST /api/v1/auth/logout HTTP/1.1Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...Response:
{ "success": true}Note: With JWT tokens, logout is primarily client-side (delete token). Server-side session invalidation may be added in future releases.
Code Examples
Section titled “Code Examples”Python
Section titled “Python”Using requests library:
import requestsimport json
class InvokeAIClient: def __init__(self, base_url="http://localhost:9090"): self.base_url = base_url self.token = None
def login(self, email, password, remember_me=False): """Authenticate and store token.""" url = f"{self.base_url}/api/v1/auth/login" payload = { "email": email, "password": password, "remember_me": remember_me }
response = requests.post(url, json=payload) response.raise_for_status()
data = response.json() self.token = data["token"] return data["user"]
def _get_headers(self): """Get headers with authentication token.""" if not self.token: raise Exception("Not authenticated. Call login() first.")
return { "Authorization": f"Bearer {self.token}", "Content-Type": "application/json" }
def get_boards(self): """Get user's boards.""" url = f"{self.base_url}/api/v1/boards/" response = requests.get(url, headers=self._get_headers()) response.raise_for_status() return response.json()
def create_board(self, board_name): """Create a new board.""" url = f"{self.base_url}/api/v1/boards/" response = requests.post( url, params={"board_name": board_name}, headers=self._get_headers() ) response.raise_for_status() return response.json()
def logout(self): """Logout and clear token.""" url = f"{self.base_url}/api/v1/auth/logout" response = requests.post(url, headers=self._get_headers()) self.token = None return response.json()
# Usageclient = InvokeAIClient()print(f"Logged in as: {user['display_name']}")
boards = client.get_boards()print(f"User has {len(boards['items'])} boards")
new_board = client.create_board("My New Board")print(f"Created board: {new_board['board_name']}")
client.logout()Error Handling:
import requestsfrom requests.exceptions import HTTPError
def safe_api_call(client, method, *args, **kwargs): """Make API call with error handling.""" try: func = getattr(client, method) return func(*args, **kwargs)
except HTTPError as e: if e.response.status_code == 401: print("Authentication failed or token expired") # Re-authenticate client.login(email, password) # Retry return func(*args, **kwargs) elif e.response.status_code == 403: print("Permission denied") elif e.response.status_code == 404: print("Resource not found") else: print(f"API error: {e.response.status_code}") print(e.response.text)
raise
# Usagetry: boards = safe_api_call(client, "get_boards")except Exception as e: print(f"Failed to get boards: {e}")JavaScript/TypeScript
Section titled “JavaScript/TypeScript”Using fetch API:
class InvokeAIClient { constructor(baseUrl = 'http://localhost:9090') { this.baseUrl = baseUrl; this.token = null; }
async login(email, password, rememberMe = false) { const response = await fetch(`${this.baseUrl}/api/v1/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ email, password, remember_me: rememberMe, }), });
if (!response.ok) { throw new Error(`Login failed: ${response.statusText}`); }
const data = await response.json(); this.token = data.token;
// Store token in localStorage localStorage.setItem('invokeai_token', data.token);
return data.user; }
getHeaders() { if (!this.token) { throw new Error('Not authenticated. Call login() first.'); }
return { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json', }; }
async getBoards() { const response = await fetch(`${this.baseUrl}/api/v1/boards/`, { headers: this.getHeaders(), });
if (!response.ok) { throw new Error(`Failed to get boards: ${response.statusText}`); }
return response.json(); }
async createBoard(boardName) { const url = new URL(`${this.baseUrl}/api/v1/boards/`); url.searchParams.set('board_name', boardName); const response = await fetch(url, { method: 'POST', headers: this.getHeaders(), });
if (!response.ok) { throw new Error(`Failed to create board: ${response.statusText}`); }
return response.json(); }
async logout() { const response = await fetch(`${this.baseUrl}/api/v1/auth/logout`, { method: 'POST', headers: this.getHeaders(), });
this.token = null; localStorage.removeItem('invokeai_token');
return response.json(); }}
// Usage(async () => { const client = new InvokeAIClient();
try { console.log(`Logged in as: ${user.display_name}`);
const boards = await client.getBoards(); console.log(`User has ${boards.items.length} boards`);
const newBoard = await client.createBoard('My New Board'); console.log(`Created board: ${newBoard.board_name}`);
await client.logout(); } catch (error) { console.error('Error:', error.message); }})();TypeScript with Types:
interface LoginRequest { email: string; password: string; remember_me?: boolean;}
interface User { user_id: string; email: string; display_name: string; is_admin: boolean; is_active: boolean; created_at: string;}
interface LoginResponse { token: string; user: User; expires_in: number;}
interface Board { board_id: string; board_name: string; created_at: string; updated_at: string; deleted_at?: string; cover_image_name?: string;}
class InvokeAIClient { private baseUrl: string; private token: string | null = null;
constructor(baseUrl: string = 'http://localhost:9090') { this.baseUrl = baseUrl; }
async login( email: string, password: string, rememberMe: boolean = false ): Promise<User> { const response = await fetch(`${this.baseUrl}/api/v1/auth/login`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password, remember_me: rememberMe }), });
if (!response.ok) { const error = await response.json(); throw new Error(error.detail || 'Login failed'); }
const data: LoginResponse = await response.json(); this.token = data.token; return data.user; }
private getHeaders(): HeadersInit { if (!this.token) { throw new Error('Not authenticated'); } return { 'Authorization': `Bearer ${this.token}`, 'Content-Type': 'application/json', }; }
async getBoards(): Promise<{ items: Board[] }> { const response = await fetch(`${this.baseUrl}/api/v1/boards/`, { headers: this.getHeaders(), });
if (!response.ok) { throw new Error('Failed to get boards'); }
return response.json(); }}Login:
# Login and extract tokenTOKEN=$(curl -X POST http://localhost:9090/api/v1/auth/login \ -H "Content-Type: application/json" \ -d '{ "email": "[email protected]", "password": "SecurePassword123", "remember_me": false }' | jq -r '.token')
echo "Token: $TOKEN"Get Boards:
curl -X GET http://localhost:9090/api/v1/boards/ \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json"Create Board:
curl -X POST "http://localhost:9090/api/v1/boards/?board_name=My%20API%20Board" \ -H "Authorization: Bearer $TOKEN"API Endpoint Changes
Section titled “API Endpoint Changes”Authentication Required
Section titled “Authentication Required”All endpoints now require authentication except:
GET /api/v1/auth/status— Check whether multi-user setup is requiredPOST /api/v1/auth/setup— Initial admin setupPOST /api/v1/auth/login— User loginGET /api/v1/images/i/{image_name}/full— Full-resolution image fileGET /api/v1/images/i/{image_name}/thumbnail— Image thumbnailGET /api/v1/workflows/i/{workflow_id}/thumbnail— Workflow thumbnail
The image and thumbnail endpoints are intentionally unauthenticated because browsers load these resources via <img src> tags, which cannot send Authorization headers. Security relies on the fact that image and workflow IDs are UUIDs and therefore unguessable.
User-Scoped Resources
Section titled “User-Scoped Resources”Resources are now filtered by the authenticated user:
Boards:
# Before (single-user)GET /api/v1/boards/?all=true # Returns all boards
# After (multi-user)GET /api/v1/boards/?all=true # Returns boards the current user can access, including their own boards plus shared/public boards; admins can see all boardsImages:
# Images are filtered by board ownershipGET /api/v1/images/ # Only shows images on user's boardsWorkflows:
# Returns user's workflows + public workflowsGET /api/v1/workflows/Queue:
# Regular users see their own queue items in full and may see redacted details for other users' items on queue-status endpointsGET /api/v1/queue/... # Queue data, sanitized for non-admin viewers
# Administrators see all queue items in fullGET /api/v1/queue/... # Full queue dataAdministrator Endpoints
Section titled “Administrator Endpoints”Some endpoints require administrator privileges:
User Management:
GET /api/v1/auth/users # List users (admin only)POST /api/v1/auth/users # Create user (admin only)GET /api/v1/auth/users/{id} # Get user (admin only)PATCH /api/v1/auth/users/{id} # Update user (admin only)DELETE /api/v1/auth/users/{id} # Delete user (admin only)Model Management (Write Operations):
POST /api/v2/models/install # Install model (admin only)DELETE /api/v2/models/i/{key} # Delete model (admin only)PATCH /api/v2/models/i/{key} # Update model (admin only)PUT /api/v2/models/convert/{key} # Convert model (admin only)Model Management (Read Operations):
GET /api/v2/models/ # List models (all users)GET /api/v2/models/i/{key} # Get model details (all users)Error Responses
Section titled “Error Responses”401 Unauthorized:
{ "detail": "Invalid authentication credentials"}Occurs when:
- Token is missing
- Token is invalid
- Token is expired
- Token signature is invalid
403 Forbidden:
{ "detail": "Admin privileges required"}Occurs when:
- User attempts admin-only operation
- Account is disabled
- Insufficient permissions
404 Not Found:
{ "detail": "Resource not found"}Occurs when:
- Resource doesn’t exist
- User doesn’t have access to resource
Multiuser API Endpoints
Section titled “Multiuser API Endpoints”Authentication Endpoints
Section titled “Authentication Endpoints”Check if initial administrator setup is required
Section titled “Check if initial administrator setup is required”Endpoint: GET /api/v1/auth/status
Description: Returns a SetupStatusResponse indicating whether setup is needed and multiuser mode status.
Request: No parameters
Response (initial setup not yet complete):
{ "setup_required": true, "multiuser_enabled": true, "strict_password_checking": true,}Response (setup already complete, or multiuser disabled):
{ "setup_required": false, "multiuser_enabled": true, "strict_password_checking": true, "admin_email": null}Setup Administrator
Section titled “Setup Administrator”Endpoint: POST /api/v1/auth/setup
Description: Create initial administrator account (only works if no admin exists)
Request:
{ "display_name": "Administrator", "password": "SecureAdminPass123"}Response:
{ "success": true, "user": { "user_id": "abc123", "display_name": "Administrator", "is_admin": true, "is_active": true }}Get Current User
Section titled “Get Current User”Endpoint: GET /api/v1/auth/me
Description: Get currently authenticated user’s information
Request:
GET /api/v1/auth/meAuthorization: Bearer <token>Response:
{ "user_id": "abc123", "display_name": "John Doe", "is_admin": false, "is_active": true, "created_at": "2024-01-15T10:00:00Z", "updated_at": "2024-01-15T10:00:00Z", "last_login_at": "2024-01-15T15:30:00Z"}User Management Endpoints (Admin Only)
Section titled “User Management Endpoints (Admin Only)”List Users
Section titled “List Users”Endpoint: GET /api/v1/auth/users
Request:
GET /api/v1/auth/usersAuthorization: Bearer <admin_token>Response:
[ { "user_id": "abc123", "display_name": "John Doe", "is_admin": false, "is_active": true, "created_at": "2024-01-15T10:00:00Z", "updated_at": "2024-04-25T17:23:00Z", "last_login_at": "2024-01-15T15:30:00Z" }]Create User
Section titled “Create User”Endpoint: POST /api/v1/auth/users
Request:
{ "display_name": "New User", "password": "TempPassword123", "is_admin": false}Response:
{ "user_id": "xyz789", "display_name": "New User", "is_admin": false, "is_active": true, "created_at": "2024-01-15T16:00:00Z"}Update User
Section titled “Update User”Endpoint: PATCH /api/v1/auth/users/{user_id}
Request:
{ "display_name": "Updated Name", "is_active": true, "is_admin": false}Response:
{ "user_id": "xyz789", "display_name": "Updated Name", "is_admin": false, "is_active": true}Delete User
Section titled “Delete User”Endpoint: DELETE /api/v1/auth/users/{user_id}
Response:
Returns 204 No Content on success.
On an error, it returns 422 Unprocessable Content and the following JSON:
{ "detail": [ { "loc": [ "string", 0 ], "msg": "string", "type": "string" } ]}List Image Boards
Section titled “List Image Boards”Endpoint: GET /api/v1/boards/
Response:
{ "limit": 0, "offset": 0, "total": 0, "items": [ { "board_id": "8b31a33d-0acb-46fe-8612-83601481cf2c", "board_name": "Testing Board", "user_id": "string", "created_at": "2026-05-07T03:04:00.738Z", "updated_at": "2026-05-07T03:04:00.738Z", "deleted_at": "2026-05-07T03:04:00.738Z", "cover_image_name": "string", "archived": false, "board_visibility": "private", "image_count": 0, "asset_count": 0, "owner_username": "string" } ]}This returns a paged response. See the swagger page (http://localhost:9090/docs#/boards/list_boards) for details.
The board_visibility field will be one of:
private— private to the owner and administratorshared— read/write to the owner and administrator, read-only to everyone elsepublic— read/write by everyone
Get One Board
Section titled “Get One Board”Endpoint: GET /api/v1/boards/{board_id}
Response:
{ "board_id": "8b31a33d-0acb-46fe-8612-83601481cf2c", "board_name": "Testing Board", "user_id": "3c59a0ba-f4c7-4275-b96f-82179e8aaff8", "created_at": "2026-03-09 16:10:47.095", "updated_at": "2026-03-09 16:10:55", "deleted_at": null, "cover_image_name": "08689e4b-f084-4c49-83a8-4fc1edb167c4.png", "archived": false, "board_visibility": "shared", "image_count": 55, "asset_count": 0, "owner_username": null}Best Practices
Section titled “Best Practices”Token Storage
Section titled “Token Storage”Do:
- Store tokens securely (keychain, secure storage)
- Use HTTPS to transmit tokens
- Clear tokens on logout
- Handle token expiration gracefully
Don’t:
- Store tokens in URL parameters
- Log tokens in plain text
- Share tokens between users
- Store tokens in version control
Error Handling
Section titled “Error Handling”Always handle authentication errors:
def make_request(client, func, *args, **kwargs): max_retries = 3 retry_count = 0
while retry_count < max_retries: try: return func(*args, **kwargs) except AuthenticationError: if retry_count >= max_retries - 1: raise # Re-authenticate client.login(email, password) retry_count += 1 except Exception as e: logger.error(f"Request failed: {e}") raiseRate Limiting
Section titled “Rate Limiting”Be mindful of API rate limits:
- Implement exponential backoff for retries
- Cache frequently accessed data
- Batch requests when possible
- Don’t hammer the login endpoint
Connection Management
Section titled “Connection Management”import requestsfrom requests.adapters import HTTPAdapterfrom urllib3.util.retry import Retry
def create_session(): """Create session with retry logic.""" session = requests.Session()
retry = Retry( total=3, backoff_factor=0.3, status_forcelist=[500, 502, 503, 504], )
adapter = HTTPAdapter(max_retries=retry) session.mount('http://', adapter) session.mount('https://', adapter)
return sessionMigration Guide
Section titled “Migration Guide”Updating Existing Code
Section titled “Updating Existing Code”Before (single-user mode):
import requests
def get_boards(): response = requests.get("http://localhost:9090/api/v1/boards/") return response.json()After (multi-user mode):
import requests
class APIClient: def __init__(self): self.token = None
def login(self, email, password): response = requests.post( "http://localhost:9090/api/v1/auth/login", json={"email": email, "password": password} ) self.token = response.json()["token"]
def get_boards(self): headers = {"Authorization": f"Bearer {self.token}"} response = requests.get( "http://localhost:9090/api/v1/boards/", headers=headers ) return response.json()
# Usageclient = APIClient()boards = client.get_boards()Backward Compatibility
Section titled “Backward Compatibility”InvokeAI supports both single-user and multi-user modes via the multiuser configuration option.
Configuration:
# Single-user mode (no authentication)multiuser: false # or omit the option entirely
# Multi-user mode (authentication required)multiuser: trueChecking Mode Programmatically:
def is_multiuser_enabled(base_url): response = requests.get(f"{base_url}/api/v1/auth/status") response.raise_for_status() return response.json()["multiuser_enabled"]
# Example usagebase_url = "http://localhost:9090"if is_multiuser_enabled(base_url): print("Multi-user mode: authentication required") # Use authenticated API callselse: print("Single-user mode: no authentication needed") # Use direct API callsAdaptive Client:
class AdaptiveInvokeAIClient: def __init__(self, base_url="http://localhost:9090"): self.base_url = base_url self.token = None self.multiuser_mode = self._check_multiuser_mode()
def _check_multiuser_mode(self): """Detect if multi-user mode is enabled.""" try: response = requests.get(f"{self.base_url}/api/v1/boards/") return response.status_code == 401 except: return False
def login(self, email, password): """Login (only needed in multi-user mode).""" if not self.multiuser_mode: print("Single-user mode: login not required") return
response = requests.post( f"{self.base_url}/api/v1/auth/login", json={"email": email, "password": password} ) self.token = response.json()["token"]
def _get_headers(self): """Get headers (with auth token if in multi-user mode).""" if self.multiuser_mode and self.token: return {"Authorization": f"Bearer {self.token}"} return {}
def get_boards(self): """Get boards (works in both modes).""" response = requests.get( f"{self.base_url}/api/v1/boards/", headers=self._get_headers() ) return response.json()OpenAPI/Swagger Documentation
Section titled “OpenAPI/Swagger Documentation”InvokeAI provides OpenAPI documentation for all endpoints.
Access Swagger UI:
http://localhost:9090/docsDownload OpenAPI Schema:
curl http://localhost:9090/openapi.json > invokeai_openapi.jsonGenerate Client Code:
Use tools like openapi-generator to generate client libraries:
# Generate Python clientopenapi-generator generate \ -i http://localhost:9090/openapi.json \ -g python \ -o ./invokeai-client
# Generate TypeScript clientopenapi-generator generate \ -i http://localhost:9090/openapi.json \ -g typescript-fetch \ -o ./invokeai-client-tsSecurity Considerations
Section titled “Security Considerations”Always use HTTPS in production:
# Developmentclient = InvokeAIClient("http://localhost:9090")
# Productionclient = InvokeAIClient("https://invoke.example.com")Token Security
Section titled “Token Security”Protect JWT tokens:
# Never log tokenslogger.info(f"User logged in") # Goodlogger.info(f"Token: {token}") # Bad!
# Use environment variables for credentialsimport osemail = os.environ.get("INVOKEAI_EMAIL")password = os.environ.get("INVOKEAI_PASSWORD")Input Validation
Section titled “Input Validation”Always validate user input:
import re
def validate_email(email): pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' return re.match(pattern, email) is not None
def validate_password(password): """Check password meets requirements.""" if len(password) < 8: return False, "Password must be at least 8 characters" if not any(c.isupper() for c in password): return False, "Password must contain uppercase letters" if not any(c.islower() for c in password): return False, "Password must contain lowercase letters" if not any(c.isdigit() for c in password): return False, "Password must contain numbers" return True, ""Troubleshooting
Section titled “Troubleshooting”Common Issues
Section titled “Common Issues”Issue: “Invalid authentication credentials”
- Token expired — re-authenticate
- Token malformed — check token string
- Token signature invalid — check secret key hasn’t changed
Issue: “Admin privileges required”
- User is not an administrator
- Use admin account for this operation
Issue: Token not being sent
- Check
Authorizationheader is present - Verify
Bearerprefix is included - Check token isn’t truncated
Issue: CORS errors
Configure CORS in InvokeAI:
allow_origins: - "http://localhost:3000" - "https://myapp.example.com"allow_credentials: trueallow_methods: - "*"allow_headers: - "*"Additional Resources
Section titled “Additional Resources”- User Guide — For end users
- Administrator Guide — For administrators
- GitHub Repository — Source code
Questions? Visit the InvokeAI Discord or check the FAQ.