REST API Reference
Complete reference for all REST API endpoints.
Interactive Documentation
For an interactive experience, visit our Swagger UI where you can test endpoints directly.
Base URL
https://api.elcto.com
Health & Info
Get Health Status
Check the health status of the API and all services.
GET /health
GET /v1/health
Authentication: None required
Both endpoints return the same response. /health is available at the root level for load balancers and monitoring tools, while /v1/health is available under the versioned API for consistency.
Response:
{
"status": "healthy",
"version": "1.0.0",
"timestamp": "2025-11-24T10:30:00Z",
"uptime": {
"days": 5,
"hours": 12,
"minutes": 30,
"seconds": 45,
"total": 475845.0
},
"services": {
"database": {
"status": "up",
"responseTime": 15
},
"redis": {
"status": "up",
"responseTime": 3
},
"websocket": {
"status": "up",
"connections": 42,
"subscriptions": 128
}
},
"_links": {
"self": "https://api.elcto.com/health"
}
}
Get API Info
Get information about the API version and client details.
GET /v1
Authentication: None required
Response:
{
"version": "1.0.0",
"server": {
"uptime": 475845000
},
"client": {
"ip": "192.168.1.100",
"userAgent": "Mozilla/5.0...",
"browser": "Chrome",
"os": "Windows"
},
"timestamp": "2025-11-24T10:30:00Z",
"_links": {
"self": "https://api.elcto.com/v1"
}
}
GPS Data
List GPS Data
Retrieve a paginated list of GPS data points.
GET /v1/gps?page=1&limit=10
Authentication: Required (Viewer or Admin)
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 10 | Items per page (max: 100) |
Response:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"latitude": 51.5074,
"longitude": -0.1278,
"altitude": 11.0,
"timestamp": 1700000000,
"speed": 5.5,
"createdAt": "2025-11-24T10:00:00Z"
}
],
"pagination": {
"page": 1,
"limit": 10,
"total": 100,
"total_pages": 10
},
"_links": {
"self": "https://api.elcto.com/v1/gps?page=1&limit=10",
"first": "https://api.elcto.com/v1/gps?page=1&limit=10",
"next": "https://api.elcto.com/v1/gps?page=2&limit=10",
"last": "https://api.elcto.com/v1/gps?page=10&limit=10"
}
}
Get Current GPS
Get the most recent GPS data point.
GET /v1/gps/current
Authentication: Required (Viewer or Admin)
Response:
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"latitude": 51.5074,
"longitude": -0.1278,
"altitude": 11.0,
"timestamp": 1700000000,
"speed": 5.5,
"createdAt": "2025-11-24T10:00:00Z"
},
"_links": {
"self": "https://api.elcto.com/v1/gps/550e8400-e29b-41d4-a716-446655440000"
}
}
Get GPS by ID
Retrieve a specific GPS data point by ID.
GET /v1/gps/{id}
Authentication: Required (Viewer or Admin)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | UUID | GPS data ID |
Response:
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"latitude": 51.5074,
"longitude": -0.1278,
"altitude": 11.0,
"timestamp": 1700000000,
"speed": 5.5,
"createdAt": "2025-11-24T10:00:00Z"
},
"_links": {
"self": "https://api.elcto.com/v1/gps/550e8400-e29b-41d4-a716-446655440000"
}
}
Create GPS Data
Create a new GPS data point.
POST /v1/gps
Content-Type: application/json
Authentication: Required (Admin only)
Rate Limit: 30 requests per 60 seconds
Request Body:
{
"latitude": 51.5074,
"longitude": -0.1278,
"altitude": 11.0,
"timestamp": 1700000000,
"speed": 5.5
}
Response (201 Created):
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"latitude": 51.5074,
"longitude": -0.1278,
"altitude": 11.0,
"timestamp": 1700000000,
"speed": 5.5,
"createdAt": "2025-11-24T10:00:00Z"
},
"_links": {
"self": "https://api.elcto.com/v1/gps/550e8400-e29b-41d4-a716-446655440000"
}
}
System Settings
Get All Settings
Retrieve all system settings.
GET /v1/settings
Authentication: Required (Admin only)
Get Public Settings
Retrieve public-facing settings.
GET /v1/settings/public
Authentication: None required
Get Setting by Key
Retrieve a specific setting by key.
GET /v1/settings/{key}
Authentication: Required (Admin only)
Create Setting
Create a new system setting.
POST /v1/settings
Content-Type: application/json
Authentication: Required (Admin only)
Update Setting
Update an existing setting.
PUT /v1/settings/{key}
Content-Type: application/json
Authentication: Required (Admin only)
Delete Setting
Delete a system setting.
DELETE /v1/settings/{key}
Authentication: Required (Admin only)
Statistics
Get Public Statistics
Retrieve public platform statistics.
GET /v1/stats/public
Authentication: None required
Users
List Users
Retrieve a paginated list of all users.
GET /v1/users?page=1&limit=20
Authentication: Required
Permission: users:read
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
page | integer | 1 | Page number |
limit | integer | 20 | Items per page (max: 100) |
Response:
{
"data": [
{
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"username": "johndoe",
"primary_platform_account_id": "8f14e45f-ceea-467f-8a53-c9d5889d76b2",
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-20T15:30:00Z",
"last_login_at": "2025-01-25T09:00:00Z",
"deleted_at": null,
"privacy_mode": false
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 150,
"total_pages": 8
},
"_links": {
"self": "https://api.elcto.com/v1/users?page=1&limit=20",
"first": "https://api.elcto.com/v1/users?page=1&limit=20",
"next": "https://api.elcto.com/v1/users?page=2&limit=20",
"last": "https://api.elcto.com/v1/users?page=8&limit=20"
}
}
Get User by ID
Retrieve a specific user by their ID.
GET /v1/users/{id}
Authentication: Required
Permission: users:read (or self)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | User ID |
Response:
{
"data": {
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"username": "johndoe",
"primary_platform_account_id": "8f14e45f-ceea-467f-8a53-c9d5889d76b2",
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-20T15:30:00Z",
"last_login_at": "2025-01-25T09:00:00Z",
"deleted_at": null,
"privacy_mode": false
},
"_links": {
"self": "https://api.elcto.com/v1/users/6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
}
Get User Profile
Retrieve a user's profile with platform account details.
GET /v1/users/{id}/profile
Authentication: Required
Permission: users:read (or self)
Response:
{
"name": "John Doe",
"display_name": "johndoe",
"picture": "https://cdn.example.com/avatar.png",
"provider": "twitch",
"primary_provider": "twitch",
"last_login_at": "2025-01-25T09:00:00Z",
"created_at": "2025-01-15T10:00:00Z",
"privacy_mode": false
}
Get Current User
Retrieve the currently authenticated user.
GET /v1/users/me
Authentication: Required
Response: Same as Get User by ID
Update User
Update a user's information.
PATCH /v1/users/{id}
Content-Type: application/json
Authentication: Required
Permission: users:write (or self)
Request Body:
{
"username": "newusername",
"email": "newemail@example.com",
"avatar_url": "https://cdn.example.com/new-avatar.png",
"privacy_mode": true
}
All fields are optional. Only provided fields will be updated.
Response:
{
"data": {
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"username": "newusername",
"primary_platform_account_id": "8f14e45f-ceea-467f-8a53-c9d5889d76b2",
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-26T12:00:00Z",
"last_login_at": "2025-01-25T09:00:00Z",
"deleted_at": null,
"privacy_mode": true,
"preferred_locale": "en"
},
"_links": {
"self": "https://api.elcto.com/v1/users/6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
}
Update User Locale
Update a user's preferred locale for email communications.
PATCH /v1/users/{id}/locale
Content-Type: application/json
Authentication: Required
Permission: users:write (or self)
Request Body:
{
"locale": "de"
}
| Field | Type | Required | Description |
|---|---|---|---|
locale | string | null | No | Preferred locale (e.g., "en", "de"). Set to null to clear. |
Supported Locales: The list of supported locales is configurable via the email.supported_locales configuration. Default: ["en", "de"].
Response:
{
"data": {
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"username": "johndoe",
"primary_platform_account_id": "8f14e45f-ceea-467f-8a53-c9d5889d76b2",
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-26T12:00:00Z",
"last_login_at": "2025-01-25T09:00:00Z",
"deleted_at": null,
"privacy_mode": false,
"preferred_locale": "de"
},
"_links": {
"self": "https://api.elcto.com/v1/users/6ba7b810-9dad-11d1-80b4-00c04fd430c8"
}
}
Delete User
Soft delete a user (sets deleted_at timestamp and revokes all sessions/API keys).
DELETE /v1/users/{id}
Authentication: Required
Permission: users:delete
Response (204 No Content): Empty response on success
Get User Permissions
Retrieve a user's effective permissions (including role-based permissions).
GET /v1/users/{id}/permissions
Authentication: Required
Permission: users:read (or self)
Response:
{
"permissions": [
"users:read",
"users:write",
"gps:read",
"*:*"
]
}
Get User Roles
Retrieve a user's assigned roles.
GET /v1/users/{id}/roles
Authentication: Required
Permission: users:read (or self)
Response:
{
"roles": [
{
"id": "role_admin",
"name": "Admin",
"description": "Full administrative access to manage users, roles, and all resources"
}
]
}
Assign Role to User
Assign a role to a user.
POST /v1/users/{id}/roles
Content-Type: application/json
Authentication: Required
Permission: users:write
Request Body:
{
"role_id": "41eebc99-9c0b-4ef8-bb6d-6bb9bd380a21"
}
Remove Role from User
Remove a role from a user.
DELETE /v1/users/{id}/roles/{role_id}
Authentication: Required
Permission: users:write
Platform Accounts
List User Accounts
Retrieve all platform accounts linked to a user.
GET /v1/users/{id}/accounts
Authentication: Required
Permission: users:read (or self)
Response:
{
"data": [
{
"id": "8f14e45f-ceea-467f-8a53-c9d5889d76b2",
"platform_id": "platform-twitch",
"platform_name": "Twitch",
"platform_slug": "twitch",
"platform_user_id": "12345678",
"is_oauth": true,
"is_primary": true,
"username": "johndoe",
"email": "john@example.com",
"avatar_url": "https://cdn.example.com/avatar.png",
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-20T15:30:00Z"
}
],
"_links": {
"self": "https://api.elcto.com/v1/users/6ba7b810-9dad-11d1-80b4-00c04fd430c8/accounts"
}
}
Set Primary Platform Account
Change which platform account is used as the primary account for profile information.
PUT /v1/users/{id}/accounts/{account_id}/primary
Authentication: Required
Permission: users:write (or self)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | User ID |
account_id | string | Platform account ID to set as primary |
Response:
{
"data": {
"id": "8f14e45f-ceea-467f-8a53-c9d5889d76b2",
"platform_id": "platform-discord",
"platform_name": "Discord",
"platform_slug": "discord",
"platform_user_id": "98765432",
"is_oauth": true,
"is_primary": true,
"username": "johndoe#1234",
"email": "john@example.com",
"avatar_url": "https://cdn.example.com/avatar.png",
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-25T14:30:00Z"
},
"_links": {
"self": "https://api.elcto.com/v1/users/6ba7b810-9dad-11d1-80b4-00c04fd430c8/accounts/8f14e45f-ceea-467f-8a53-c9d5889d76b2"
}
}
Unlink Platform Account
Remove a platform account link from a user.
DELETE /v1/users/{id}/accounts/{account_id}
Authentication: Required
Permission: users:write (or self)
Response (204 No Content): Empty response on success
- Cannot unlink the email platform account (use account deletion instead)
- Cannot unlink the last platform account (user must have at least one login method)
- Cannot unlink the primary account (change primary first)
Request Link Email Account
Start the process of linking an email account to a user. Sends a verification email.
POST /v1/users/{id}/email-link/request
Authorization: Bearer {token}
Content-Type: application/json
Authentication: Required
Permission: users:write (or self)
Request Body:
{
"email": "newemail@example.com",
"password": "securepassword123"
}
Response (200 OK):
{
"message": "Verification email sent. Please check your inbox.",
"expires_at": "2025-01-25T15:30:00Z"
}
The password must be at least 8 characters long.
Verify Link Email Account
Complete the email account linking by verifying the token from the email.
POST /v1/users/email-link/verify
Authorization: Bearer {token}
Content-Type: application/json
Authentication: Required
Request Body:
{
"token": "abc123def456..."
}
Response (200 OK):
{
"message": "Email account linked successfully",
"platform_account_id": "c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a13"
}
Get Pending Email Link
Check if there's a pending email link verification for a user.
GET /v1/users/{id}/email-link/pending
Authorization: Bearer {token}
Authentication: Required
Permission: users:read (or self)
Response (200 OK) - With pending link:
{
"pending": true,
"email": "newemail@example.com",
"expires_at": "2025-01-25T15:30:00Z"
}
Response (200 OK) - No pending link:
{
"pending": false,
"email": null,
"expires_at": null
}
Cancel Pending Email Link
Cancel a pending email link request.
DELETE /v1/users/{id}/email-link/pending
Authorization: Bearer {token}
Authentication: Required
Permission: users:write (or self)
Response (200 OK):
{
"message": "Pending email link cancelled"
}
Once an email account is linked, users can authenticate using email/password on platforms that support it. This provides an alternative to OAuth authentication.
Active Sessions Management
Endpoints for users to view and manage their active login sessions across devices.
List Active Sessions
Get all active sessions for the authenticated user.
GET /v1/users/me/sessions
Authorization: Bearer {token}
Authentication: Required (Session token)
Response:
{
"sessions": [
{
"id": "d0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36...",
"device_type": "desktop",
"browser_name": "Chrome",
"os_name": "Windows",
"provider": "twitch",
"provider_name": "Twitch",
"last_activity_at": "2025-01-25T10:30:00Z",
"created_at": "2025-01-20T08:00:00Z",
"expires_at": "2025-02-20T08:00:00Z",
"is_current": true
},
{
"id": "e0eebc99-9c0b-4ef8-bb6d-6bb9bd380a15",
"ip_address": "10.0.0.50",
"user_agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)...",
"device_type": "mobile",
"browser_name": "Safari",
"os_name": "iOS",
"provider": "email",
"provider_name": "Email",
"last_activity_at": "2025-01-24T15:00:00Z",
"created_at": "2025-01-15T12:00:00Z",
"expires_at": "2025-02-15T12:00:00Z",
"is_current": false
}
],
"current_session_id": "d0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
"_links": {
"self": "https://api.elcto.com/v1/users/me/sessions"
}
}
Session Fields:
| Field | Type | Description |
|---|---|---|
id | string | Session ID |
ip_address | string | IP address used to create the session |
user_agent | string | Full user agent string |
device_type | string | Parsed device type (desktop, mobile, tablet) |
browser_name | string | Parsed browser name |
os_name | string | Parsed operating system name |
provider | string | Auth provider slug used (twitch, discord, email, etc.) |
provider_name | string | Auth provider display name (Twitch, Discord, Email, etc.) |
last_activity_at | datetime | Last activity timestamp |
created_at | datetime | Session creation time |
expires_at | datetime | Session expiration time |
is_current | boolean | Whether this is the current session |
Revoke Session
Revoke a specific session by ID.
DELETE /v1/users/me/sessions/{session_id}
Authorization: Bearer {token}
Authentication: Required (Session token)
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
session_id | string | Session ID to revoke |
Response (200 OK):
{
"success": true,
"message": "Session revoked successfully"
}
Error Response (400 Bad Request):
{
"success": false,
"message": "Cannot revoke current session"
}
You cannot revoke your current session. Use the logout functionality instead.
Revoke All Other Sessions
Revoke all sessions except the current one.
DELETE /v1/users/me/sessions
Authorization: Bearer {token}
Authentication: Required (Session token)
Response (200 OK):
{
"success": true,
"revoked_count": 3,
"message": "Revoked 3 session(s)"
}
This is useful when you suspect your account has been compromised or when you want to sign out from all other devices.
Two-Factor Authentication
Endpoints for managing two-factor authentication (2FA) using TOTP (Time-based One-Time Password).
Get 2FA Status
Check if 2FA is enabled for the authenticated user.
GET /v1/users/me/2fa/status
Authorization: Bearer {token}
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | string | No | User ID (required for system API keys) |
Response:
{
"enabled": true,
"enabled_at": "2025-01-15T10:00:00Z",
"backup_codes_remaining": 8,
"platform_slug": "email"
}
| Field | Type | Description |
|---|---|---|
enabled | boolean | Whether 2FA is enabled |
enabled_at | string | When 2FA was enabled (ISO 8601) |
backup_codes_remaining | number | Number of unused backup codes |
platform_slug | string | Platform used for 2FA |
Get 2FA Requirement
Check if 2FA is required for the authenticated user based on their roles.
GET /v1/users/me/2fa/requirement
Authorization: Bearer {token}
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
user_id | string | No | User ID (required for system API keys) |
Response:
{
"required": true,
"enabled": false,
"reason": "Your role requires two-factor authentication"
}
| Field | Type | Description |
|---|---|---|
required | boolean | Whether 2FA is required |
enabled | boolean | Whether 2FA is enabled |
reason | string | Explanation of why 2FA is required |
Setup 2FA
Initiate 2FA setup. Returns a TOTP secret and QR code for authenticator apps.
POST /v1/users/me/2fa/setup
Authorization: Bearer {token}
Content-Type: application/json
Request Body:
{
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"platform_id": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
}
| Field | Type | Required | Description |
|---|---|---|---|
user_id | string | No | User ID (required for system API keys) |
platform_id | string | No | Platform to use for 2FA (defaults to email) |
Response:
{
"secret": "JBSWY3DPEHPK3PXP",
"qr_code_url": "otpauth://totp/Heimdall:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Heimdall",
"qr_code_image": "data:image/png;base64,iVBORw0KGgo...",
"expires_at": "2025-01-25T10:10:00Z",
"platform_slug": "email"
}
| Field | Type | Description |
|---|---|---|
secret | string | TOTP secret for manual entry |
qr_code_url | string | OTPAuth URL for QR code |
qr_code_image | string | Base64-encoded QR code PNG image |
expires_at | string | When the setup expires (10 minutes) |
platform_slug | string | Platform used for 2FA |
The setup expires after 10 minutes. You must verify the code before then, or initiate setup again.
Verify 2FA
Verify a TOTP code to complete 2FA setup.
POST /v1/users/me/2fa/verify
Authorization: Bearer {token}
Content-Type: application/json
Request Body:
{
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"code": "123456"
}
| Field | Type | Required | Description |
|---|---|---|---|
user_id | string | No | User ID (required for system API keys) |
code | string | Yes | 6-digit TOTP code from authenticator app |
Response:
{
"success": true,
"backup_codes": [
"ABCD-EFGH-IJKL",
"MNOP-QRST-UVWX",
"1234-5678-9012",
"3456-7890-1234",
"5678-9012-3456",
"7890-1234-5678",
"9012-3456-7890",
"1234-5678-9012"
],
"message": "Two-factor authentication enabled successfully"
}
| Field | Type | Description |
|---|---|---|
success | boolean | Whether verification succeeded |
backup_codes | string[] | One-time backup codes (shown once!) |
message | string | Success message |
Backup codes are only shown once. Store them in a secure location - they cannot be retrieved later.
Disable 2FA
Disable two-factor authentication.
DELETE /v1/users/me/2fa
Authorization: Bearer {token}
Content-Type: application/json
Request Body:
{
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"code": "123456"
}
| Field | Type | Required | Description |
|---|---|---|---|
user_id | string | No | User ID (required for system API keys) |
code | string | Yes | 6-digit TOTP code or backup code |
Response:
{
"success": true,
"message": "Two-factor authentication disabled"
}
Regenerate Backup Codes
Generate new backup codes (invalidates all previous codes).
POST /v1/users/me/2fa/backup-codes/regenerate
Authorization: Bearer {token}
Content-Type: application/json
Request Body:
{
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"code": "123456"
}
| Field | Type | Required | Description |
|---|---|---|---|
user_id | string | No | User ID (required for system API keys) |
code | string | Yes | 6-digit TOTP code to confirm regeneration |
Response:
{
"success": true,
"backup_codes": [
"ABCD-EFGH-IJKL",
"MNOP-QRST-UVWX",
"1234-5678-9012",
"3456-7890-1234",
"5678-9012-3456",
"7890-1234-5678",
"9012-3456-7890",
"1234-5678-9012"
],
"message": "Backup codes regenerated successfully"
}
Regenerating backup codes invalidates all previous backup codes immediately. Make sure to store the new codes securely.
Permissions
| Action | Self | Other Users |
|---|---|---|
| Get status | ✅ No special permission | two_factor:manage |
| Setup | ✅ No special permission | two_factor:manage |
| Verify | ✅ No special permission | two_factor:manage |
| Disable | ✅ No special permission | two_factor:manage |
| Regenerate codes | ✅ No special permission | two_factor:manage |
Users can always manage their own 2FA without special permissions. Managing other users' 2FA requires the two_factor:manage permission or super admin role.
Sessions (Internal)
Session management endpoints for NextAuth integration. These endpoints require a system API key for authentication.
These endpoints are used internally by NextAuth for session management. They are not intended for direct client use.
Create Session
Create a new database session for a user.
POST /v1/sessions
Authorization: Bearer {SYSTEM_API_KEY}
Content-Type: application/json
Request Body:
{
"userId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"expires": "2025-02-25T10:00:00Z"
}
Response (201 Created):
{
"id": "sess-uuid",
"sessionToken": "sess_abc123...",
"userId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"expires": "2025-02-25T10:00:00Z",
"created_at": "2025-01-25T10:00:00Z"
}
Get Session
Retrieve a session by token.
GET /v1/sessions/{token}
Authorization: Bearer {SYSTEM_API_KEY}
Response:
{
"id": "sess-uuid",
"sessionToken": "sess_abc123...",
"userId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"expires": "2025-02-25T10:00:00Z",
"created_at": "2025-01-25T10:00:00Z"
}
Delete Session
Delete a session (sign out).
DELETE /v1/sessions/{token}?reason=user_initiated
Authorization: Bearer {SYSTEM_API_KEY}
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
reason | string | No | Reason for logout. Defaults to user_initiated. |
Logout Reasons:
| Reason | When Used |
|---|---|
user_initiated | User clicked sign out (default) |
token_deleted | Admin deleted the session |
session_expired | Session timed out |
revoked | Session was forcefully revoked |
no_access | User lost access (banned, deleted) |
Response (204 No Content): Empty response on success
Audit Event: Logs a logout event with metadata:
{
"reason": "user_initiated",
"session_id": "abc123...",
"app": "id"
}
The app field is populated from the X-Source-Service header if provided.
Session Flow with NextAuth
- Login: User authenticates via credentials or OAuth
- Create: NextAuth calls
POST /v1/sessionsto create database session - Use: Session token stored in
session.accessTokenfor WebSocket auth - Logout: NextAuth calls
DELETE /v1/sessions/{token}?reason=user_initiatedon sign out - Cleanup: Sessions are also deleted when user account is deleted
Platforms
List All Platforms
Get all authentication platforms with their status.
GET /v1/platforms
Authentication: None required
Response:
{
"data": [
{
"id": "platform-twitch",
"name": "Twitch",
"slug": "twitch",
"is_oauth": true,
"enabled": true,
"two_factor_supported": true,
"two_factor_required": false,
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-20T15:30:00Z"
}
]
}
List Enabled Platforms
Get only enabled platforms (for login/registration UI).
GET /v1/platforms/enabled
Authentication: None required
Get Platform by ID or Slug
Get a specific platform.
GET /v1/platforms/{id_or_slug}
Authentication: None required
Update Platform Enabled Status
Enable or disable a platform.
PATCH /v1/platforms/{id_or_slug}/enabled
Content-Type: application/json
Authentication: Required
Permission: settings:write
Request Body:
{
"enabled": true
}
Response:
{
"data": {
"id": "platform-discord",
"name": "Discord",
"slug": "discord",
"enabled": true
}
}
Disabling a platform prevents new logins/registrations via that provider. Existing users can still access their accounts via other linked platforms.
User Bans
Get User Ban Status
Check if a user is currently banned.
GET /v1/users/{id}/ban
Authentication: Required
Permission: users:read (or self)
Response:
{
"is_banned": true,
"active_ban": {
"id": "01eebc99-9c0b-4ef8-bb6d-6bb9bd380a17",
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"reason": "Violation of terms of service",
"banned_by": "51eebc99-9c0b-4ef8-bb6d-6bb9bd380a22",
"banned_at": "2025-01-25T10:00:00Z",
"expires_at": "2025-02-01T10:00:00Z",
"is_permanent": false
}
}
Ban User
Ban a user from the platform.
POST /v1/users/{id}/ban
Content-Type: application/json
Authentication: Required
Permission: users:ban
Request Body:
{
"reason": "Violation of terms of service",
"duration_seconds": 604800
}
| Field | Type | Required | Description |
|---|---|---|---|
reason | string | No | Reason for the ban |
duration_seconds | integer | No | Ban duration in seconds. null for permanent ban. |
Response:
{
"data": {
"id": "01eebc99-9c0b-4ef8-bb6d-6bb9bd380a17",
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"reason": "Violation of terms of service",
"banned_by": "51eebc99-9c0b-4ef8-bb6d-6bb9bd380a22",
"banned_at": "2025-01-25T10:00:00Z",
"expires_at": "2025-02-01T10:00:00Z",
"is_permanent": false
}
}
Unban User
Remove a ban from a user.
DELETE /v1/users/{id}/ban
Authentication: Required
Permission: users:ban
Response (200 OK):
{
"success": true,
"message": "User has been unbanned"
}
Get User Ban History
Get the complete ban history for a user.
GET /v1/users/{id}/bans
Authentication: Required
Permission: users:read
Response:
{
"data": [
{
"id": "01eebc99-9c0b-4ef8-bb6d-6bb9bd380a17",
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"reason": "Spam",
"banned_by": "51eebc99-9c0b-4ef8-bb6d-6bb9bd380a22",
"banned_at": "2025-01-20T10:00:00Z",
"expires_at": "2025-01-21T10:00:00Z",
"is_permanent": false,
"unbanned_at": "2025-01-20T15:00:00Z",
"unbanned_by": "61eebc99-9c0b-4ef8-bb6d-6bb9bd380a23"
}
]
}
Admin User Deletion
Get User Deletion Status
Get deletion status for a user (admin view with additional fields).
GET /v1/admin/users/{id}/deletion-status
Authentication: Required
Permission: users:read
Response:
{
"is_scheduled_for_deletion": true,
"scheduled_deletion_at": "2025-02-01T10:00:00Z",
"is_deleted": false,
"deleted_at": null
}
Force Delete User
Immediately delete a user (skip 7-day grace period).
POST /v1/admin/users/{id}/force-delete
Content-Type: application/json
Authentication: Required
Permission: users:delete
Request Body:
{
"reason": "GDPR urgent request"
}
Response:
{
"success": true,
"message": "User has been permanently deleted"
}
Force deletion is immediate and irreversible. Use only for:
- GDPR urgent requests
- Compromised accounts
- Legal requirements
Cancel User Deletion
Cancel scheduled deletion for a user.
POST /v1/admin/users/{id}/cancel-deletion
Authentication: Required
Permission: users:write
Response:
{
"success": true,
"message": "Account deletion has been cancelled"
}
OAuth Clients
List My OAuth Clients
Get OAuth clients owned by the current user.
GET /v1/oauth/clients
Authentication: Required
Response:
{
"data": [
{
"id": "11eebc99-9c0b-4ef8-bb6d-6bb9bd380a18",
"name": "My App",
"description": "My application",
"logo_url": "https://example.com/logo.png",
"homepage_url": "https://example.com",
"redirect_uris": ["https://example.com/callback"],
"allowed_scopes": ["openid", "profile", "email"],
"client_type": "confidential",
"is_active": true,
"is_first_party": false,
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-20T15:30:00Z"
}
]
}
List All OAuth Clients (Admin)
List all OAuth clients in the system.
GET /v1/oauth/clients/all
Authentication: Required Permission: Admin
Get OAuth Client
Get a specific OAuth client by ID.
GET /v1/oauth/clients/{id}
Authentication: Required (must be owner or admin)
Create OAuth Client
Create a new OAuth client application.
POST /v1/oauth/clients
Content-Type: application/json
Authentication: Required
Request Body:
{
"name": "My App",
"description": "My awesome application",
"logo_url": "https://example.com/logo.png",
"homepage_url": "https://example.com",
"privacy_policy_url": "https://example.com/privacy",
"terms_of_service_url": "https://example.com/terms",
"redirect_uris": ["https://example.com/callback"],
"allowed_scopes": ["openid", "profile", "email"],
"client_type": "confidential",
"is_first_party": false
}
Response (201 Created):
{
"client": {
"id": "11eebc99-9c0b-4ef8-bb6d-6bb9bd380a18",
"name": "My App",
"client_type": "confidential",
"is_active": true
},
"client_secret": "hm_secret_abc123..."
}
The client_secret is only returned once at creation time. Store it securely!
Update OAuth Client
Update an existing OAuth client.
PATCH /v1/oauth/clients/{id}
Content-Type: application/json
Authentication: Required (must be owner or admin)
Request Body: Same fields as create, all optional.
Regenerate Client Secret
Regenerate the client secret for a confidential client.
POST /v1/oauth/clients/{id}/regenerate-secret
Authentication: Required (must be owner or admin)
Response:
{
"client_id": "11eebc99-9c0b-4ef8-bb6d-6bb9bd380a18",
"client_secret": "hm_secret_new123...",
"message": "Secret regenerated successfully"
}
The new client secret is only shown once in this response. Store it securely before closing this page.
Delete OAuth Client
Delete an OAuth client.
DELETE /v1/oauth/clients/{id}
Authentication: Required (must be owner or admin)
Response (204 No Content): Empty response on success
API Keys
List API Keys
Get all API keys (without the actual key value).
GET /v1/api-keys
Authentication: Required
Permission: api_keys:read
Response:
{
"data": [
{
"id": "21eebc99-9c0b-4ef8-bb6d-6bb9bd380a19",
"key_prefix": "hm_abc12345...",
"name": "Production API Key",
"description": "Used for production services",
"scopes": ["gps:read", "gps:write"],
"is_active": true,
"is_system": false,
"expires_at": "2026-01-01T00:00:00Z",
"last_used_at": "2025-01-25T10:00:00Z",
"created_at": "2025-01-15T10:00:00Z",
"updated_at": "2025-01-20T15:30:00Z"
}
]
}
Get API Key
Get a specific API key by ID.
GET /v1/api-keys/{id}
Authentication: Required
Permission: api_keys:read
Create API Key
Create a new API key.
POST /v1/api-keys
Content-Type: application/json
Authentication: Required
Permission: api_keys:write
Request Body:
{
"name": "My API Key",
"description": "For my application",
"scopes": ["gps:read"],
"expires_at": "2026-01-01T00:00:00Z"
}
Response (201 Created):
{
"id": "21eebc99-9c0b-4ef8-bb6d-6bb9bd380a19",
"key": "hm_abc123def456...",
"name": "My API Key",
"scopes": ["gps:read"],
"expires_at": "2026-01-01T00:00:00Z"
}
The full API key is only returned once at creation time. Store it securely!
Update API Key
Update an existing API key.
PATCH /v1/api-keys/{id}
Content-Type: application/json
Authentication: Required
Permission: api_keys:write
Delete API Key
Delete an API key.
DELETE /v1/api-keys/{id}
Authentication: Required
Permission: api_keys:delete
Response (204 No Content): Empty response on success
System API keys cannot be deleted via the API.
Get API Key Roles
Get roles assigned to an API key.
GET /v1/api-keys/{id}/roles
Authentication: Required
Permission: api_keys:read
Assign Role to API Key
Assign a role to an API key.
POST /v1/api-keys/{id}/roles
Content-Type: application/json
Authentication: Required
Permission: api_keys:write
Request Body:
{
"role_id": "41eebc99-9c0b-4ef8-bb6d-6bb9bd380a21"
}
Remove Role from API Key
Remove a role from an API key.
DELETE /v1/api-keys/{id}/roles/{role_id}
Authentication: Required
Permission: api_keys:write
Roles
List Roles
Get all roles.
GET /v1/roles
Authentication: Required
Permission: roles:read
Response:
{
"data": [
{
"id": "role_admin",
"name": "Admin",
"description": "Full administrative access",
"is_system": true,
"requires_two_factor": true,
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
]
}
Get Role
Get a specific role by ID.
GET /v1/roles/{id}
Authentication: Required
Permission: roles:read
Create Role
Create a new role.
POST /v1/roles
Content-Type: application/json
Authentication: Required
Permission: roles:write
Request Body:
{
"name": "Custom Role",
"description": "A custom role",
"requires_two_factor": false
}
Update Role
Update an existing role.
PATCH /v1/roles/{id}
Content-Type: application/json
Authentication: Required
Permission: roles:write
Delete Role
Delete a role.
DELETE /v1/roles/{id}
Authentication: Required
Permission: roles:delete
System roles cannot be deleted.
Get Role Permissions
Get permissions assigned to a role.
GET /v1/roles/{id}/permissions
Authentication: Required
Permission: roles:read
Assign Permission to Role
Assign a permission to a role.
POST /v1/roles/{id}/permissions
Content-Type: application/json
Authentication: Required
Permission: roles:write
Request Body:
{
"permission_id": "31eebc99-9c0b-4ef8-bb6d-6bb9bd380a20"
}
Remove Permission from Role
Remove a permission from a role.
DELETE /v1/roles/{id}/permissions/{permission_id}
Authentication: Required
Permission: roles:write
User Data Export (GDPR/DSGVO)
Export User Data
Download a ZIP archive containing all personal data for the authenticated user. This endpoint complies with GDPR/DSGVO Article 15 (Right of Access) and Article 20 (Right to Data Portability).
GET /v1/user/export
Authentication: Required (Session or API Key)
Response: ZIP file download (application/zip)
Headers:
Content-Type: application/zipContent-Disposition: attachment; filename="heimdall-data-export-{date}.zip"
ZIP Archive Contents:
| File | Description |
|---|---|
data.json | Machine-readable export of all user data |
report.pdf | Human-readable PDF summary report |
README.txt | Explanation of the export files |
Data Included in Export:
- User Profile: ID, username, privacy mode, account creation date, last login, preferred locale
- Platform Accounts: Connected platforms (Twitch, Discord, GitHub, YouTube, Email), usernames, emails, avatars
- Connected Apps: OAuth applications the user has authorized, with scopes and consent dates
- Owned Apps: OAuth applications created by the user (if developer)
- API Keys: Key names, prefixes (not full keys), scopes, creation/usage dates
- Roles: Assigned role names
- Two-Factor Auth: 2FA enabled status, setup date, remaining backup codes count
- Deletion Status: Scheduled deletion date (if applicable)
- Export History: Previous data export timestamps
- Active Sessions: All active sessions with device info, IP addresses, authentication provider used, and activity timestamps
Example Response Headers:
HTTP/1.1 200 OK
Content-Type: application/zip
Content-Disposition: attachment; filename="heimdall-data-export-2025-01-03.zip"
Content-Length: 45678
data.json Structure:
{
"exportInfo": {
"exportedAt": "2025-01-03T12:00:00Z",
"format": "GDPR/DSGVO Data Export",
"version": "1.0"
},
"user": {
"id": "user_xxx",
"username": "johndoe",
"privacyMode": false,
"createdAt": "2024-01-01T00:00:00Z",
"lastLoginAt": "2025-01-03T10:00:00Z"
},
"platformAccounts": [
{
"platform": "Twitch",
"username": "johndoe",
"email": "john@example.com",
"isPrimary": true,
"connectedAt": "2024-01-01T00:00:00Z"
}
],
"connectedApps": [
{
"name": "Third Party App",
"scopes": ["openid", "profile"],
"isFirstParty": false,
"consentedAt": "2024-06-15T10:00:00Z"
}
],
"ownedApps": [],
"apiKeys": [],
"roles": ["user"],
"twoFactorAuth": {
"enabled": true,
"enabledAt": "2024-03-01T12:00:00Z",
"backupCodesRemaining": 8
},
"deletionStatus": null,
"exportLogs": [
{
"exportedAt": "2024-12-01T10:00:00Z",
"format": "zip"
}
],
"sessions": [
{
"id": "sess_xxx",
"ipAddress": "192.168.1.xxx",
"deviceType": "desktop",
"browserName": "Chrome",
"osName": "Windows",
"provider": "twitch",
"providerName": "Twitch",
"createdAt": "2025-01-01T10:00:00Z",
"expiresAt": "2025-02-01T10:00:00Z",
"lastActivityAt": "2025-01-03T09:00:00Z"
}
]
}
Error Responses:
| Code | Description |
|---|---|
| 401 | Unauthorized - Authentication required |
| 500 | Internal Server Error - Export generation failed |
This endpoint is designed to comply with:
- Article 15: Right of Access - Users can obtain a copy of their personal data
- Article 20: Right to Data Portability - Data is provided in a structured, machine-readable format (JSON)
The export includes all personal data stored in our system. Data is provided in both human-readable (PDF) and machine-readable (JSON) formats.
Permissions
List Permissions
Get all available permissions.
GET /v1/permissions
Authentication: Required
Permission: permissions:read
Response:
{
"data": [
{
"id": "31eebc99-9c0b-4ef8-bb6d-6bb9bd380a20",
"resource": "users",
"action": "read",
"description": "Read user information",
"created_at": "2025-01-01T00:00:00Z",
"updated_at": "2025-01-01T00:00:00Z"
}
]
}
Get Permission
Get a specific permission by ID.
GET /v1/permissions/{id}
Authentication: Required
Permission: permissions:read
Create Permission
Create a new permission.
POST /v1/permissions
Content-Type: application/json
Authentication: Required
Permission: permissions:write
Request Body:
{
"resource": "custom",
"action": "read",
"description": "Read custom resources"
}
Discord Bot Management
Endpoints for managing Discord guilds and channels synced from the Discord bot.
List Discord Guilds
Get all Discord guilds synced from the bot.
GET /v1/discord/guilds
Authentication: Required
Permission: discord:read
Response:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"guild_id": "123456789012345678",
"name": "My Server",
"icon": "a_1234567890abcdef",
"icon_url": "https://cdn.discordapp.com/icons/123456789012345678/a_1234567890abcdef.png",
"owner_id": "987654321098765432",
"member_count": 1500,
"boost_count": 14,
"premium_tier": 2,
"bot_joined_at": "2025-01-15T10:00:00Z",
"last_synced_at": "2025-01-25T14:30:00Z"
}
]
}
Guild Fields:
| Field | Type | Description |
|---|---|---|
id | string | Internal UUID primary key |
guild_id | string | Discord guild ID (snowflake) |
name | string | Guild name |
icon | string | Guild icon hash |
icon_url | string | Full URL to guild icon |
owner_id | string | Discord user ID of guild owner |
member_count | integer | Approximate member count |
boost_count | integer | Number of server boosts |
premium_tier | integer | Premium tier (0=none, 1=tier1, 2=tier2, 3=tier3) |
bot_joined_at | datetime | When the bot joined this guild |
last_synced_at | datetime | Last sync timestamp |
Get Discord Guild
Get a specific Discord guild by its Discord guild ID.
GET /v1/discord/guilds/{guild_id}
Authentication: Required
Permission: discord:read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
guild_id | string | Discord guild ID (snowflake) |
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"guild_id": "123456789012345678",
"name": "My Server",
"icon": "a_1234567890abcdef",
"icon_url": "https://cdn.discordapp.com/icons/123456789012345678/a_1234567890abcdef.png",
"owner_id": "987654321098765432",
"member_count": 1500,
"boost_count": 14,
"premium_tier": 2,
"bot_joined_at": "2025-01-15T10:00:00Z",
"last_synced_at": "2025-01-25T14:30:00Z"
}
List Discord Channels
Get all channels for a specific guild.
GET /v1/discord/guilds/{guild_id}/channels
Authentication: Required
Permission: discord:read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
guild_id | string | Discord guild ID (snowflake) |
Response:
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"channel_id": "111222333444555666",
"guild_id": "123456789012345678",
"name": "general",
"channel_type": "Text",
"channel_type_raw": 0,
"parent_id": "999888777666555444",
"position": 0,
"topic": "General chat",
"nsfw": false,
"is_text_based": true,
"is_voice": false
}
]
}
Channel Fields:
| Field | Type | Description |
|---|---|---|
id | string | Internal UUID primary key |
channel_id | string | Discord channel ID (snowflake) |
guild_id | string | Discord guild ID (snowflake) |
name | string | Channel name |
channel_type | string | Channel type name (Text, Voice, etc.) |
channel_type_raw | integer | Raw channel type value |
parent_id | string | Parent category channel ID (Discord snowflake) |
position | integer | Position in channel list |
topic | string | Channel topic/description |
nsfw | boolean | Whether channel is NSFW |
is_text_based | boolean | Whether channel supports text messages |
is_voice | boolean | Whether channel is a voice channel |
Channel Types:
| Type | Name | Description |
|---|---|---|
| 0 | Text | Text channel |
| 2 | Voice | Voice channel |
| 4 | Category | Channel category |
| 5 | Announcement | Announcement channel |
| 10 | Announcement Thread | Thread in announcement channel |
| 11 | Public Thread | Public thread |
| 12 | Private Thread | Private thread |
| 13 | Stage | Stage channel |
| 15 | Forum | Forum channel |
| 16 | Media | Media channel |
Get Discord Guild with Channels
Get a guild with all its channels in a single request.
GET /v1/discord/guilds/{guild_id}/full
Authentication: Required
Permission: discord:read
Response:
{
"guild": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"guild_id": "123456789012345678",
"name": "My Server",
"icon": "a_1234567890abcdef",
"icon_url": "https://cdn.discordapp.com/icons/123456789012345678/a_1234567890abcdef.png",
"owner_id": "987654321098765432",
"member_count": 1500,
"boost_count": 14,
"premium_tier": 2,
"bot_joined_at": "2025-01-15T10:00:00Z",
"last_synced_at": "2025-01-25T14:30:00Z"
},
"channels": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"channel_id": "111222333444555666",
"guild_id": "123456789012345678",
"name": "general",
"channel_type": "Text",
"channel_type_raw": 0,
"parent_id": "999888777666555444",
"position": 0,
"topic": "General chat",
"nsfw": false,
"is_text_based": true,
"is_voice": false
}
]
}
Sync Discord Guilds
Trigger a sync of all guilds and channels from the Discord bot. This sends a request to the connected Discord bot via WebSocket and waits for the response.
POST /v1/discord/sync
Authentication: Required
Permission: discord:sync
Request Body (optional):
{
"guild_id": "123456789012345678"
}
| Field | Type | Required | Description |
|---|---|---|---|
guild_id | string | No | Specific guild ID to sync. If omitted, syncs all guilds. |
Response:
{
"success": true,
"error": null,
"guilds_synced": 5,
"channels_synced": 47,
"roles_synced": 38
}
Response Fields:
| Field | Type | Description |
|---|---|---|
success | boolean | Whether the sync was successful |
error | string | Error message if failed |
guilds_synced | integer | Number of guilds synced |
channels_synced | integer | Number of channels synced |
roles_synced | integer | Number of roles synced |
The sync operation has a 30-second timeout. If the Discord bot doesn't respond within this time, the request will fail. Ensure the Discord bot is connected and running.
Delete Discord Guild
Remove a Discord guild from the database. This does not remove the bot from the Discord server - the guild data will be re-synced on the next sync operation if the bot is still in the server.
DELETE /v1/discord/guilds/{guild_id}
Authentication: Required
Permission: discord:delete
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
guild_id | string | Internal guild UUID (database ID) |
Response (204 No Content): Empty response on success
Get Discord Bot Settings
Get global Discord bot settings.
GET /v1/discord/settings
Authentication: Required
Permission: discord:read
Response:
{
"id": "00000000-0000-0000-0000-000000000001",
"auto_sync_enabled": true,
"sync_interval_seconds": 86400,
"initial_delay_seconds": 300,
"default_language": "en",
"notify_on_guild_join": true,
"notify_on_guild_leave": true,
"command_cooldown_seconds": 3,
"updated_at": "2025-01-15T10:30:00Z"
}
Update Discord Bot Settings
Update global Discord bot settings.
PUT /v1/discord/settings
Authentication: Required
Permission: discord:edit
Request Body:
{
"auto_sync_enabled": true,
"sync_interval_seconds": 86400,
"initial_delay_seconds": 300,
"default_language": "en",
"notify_on_guild_join": true,
"notify_on_guild_leave": true,
"command_cooldown_seconds": 3
}
All fields are optional. Only include fields you want to update.
Response:
{
"success": true
}
Get Guild Settings
Get per-guild settings for a specific Discord guild.
GET /v1/discord/guilds/{guild_id}/settings
Authentication: Required
Permission: discord:read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
guild_id | string | Discord guild snowflake ID |
Response:
{
"guild_id": "123456789012345678",
"default_language": "de"
}
The default_language field is null if the guild uses the global default.
Update Guild Settings
Update per-guild settings for a specific Discord guild.
PUT /v1/discord/guilds/{guild_id}/settings
Authentication: Required
Permission: discord:edit
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
guild_id | string | Discord guild snowflake ID |
Request Body:
{
"default_language": "de"
}
Set default_language to null to use the global default.
Response:
{
"success": true
}
List Discord Members
Get all synced members for a specific Discord guild.
GET /v1/discord/guilds/{guild_id}/members
Authentication: Required
Permission: discord:read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
guild_id | string | Internal guild UUID (database ID) |
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
search | string | - | Search by username, global_name, or nickname |
bot_only | boolean | false | Filter to only show bots |
humans_only | boolean | false | Filter to only show humans (non-bots) |
has_role | string | - | Filter by role ID |
limit | integer | 50 | Items per page (max: 100) |
offset | integer | 0 | Offset for pagination |
Response:
{
"members": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"guild_id": "550e8400-e29b-41d4-a716-446655440001",
"user_id": "123456789012345678",
"username": "johndoe",
"discriminator": "0",
"global_name": "John Doe",
"nickname": "Johnny",
"avatar": "a_1234567890abcdef",
"avatar_url": "https://cdn.discordapp.com/avatars/123456789012345678/a_1234567890abcdef.png",
"bot": false,
"system": false,
"joined_at": "2024-01-15T10:00:00Z",
"premium_since": "2024-06-01T12:00:00Z",
"deaf": false,
"mute": false,
"pending": false,
"communication_disabled_until": null,
"roles": ["111222333444555666", "222333444555666777"],
"display_name": "Johnny",
"formatted_username": "@johndoe",
"is_timed_out": false
}
],
"total_count": 1500,
"has_more": true
}
Member Fields:
| Field | Type | Description |
|---|---|---|
id | string | Internal UUID primary key |
guild_id | string | Internal guild UUID |
user_id | string | Discord user ID (snowflake) |
username | string | Discord username |
discriminator | string | Legacy discriminator (may be "0" for new usernames) |
global_name | string | Global display name |
nickname | string | Server-specific nickname |
avatar | string | Avatar hash |
avatar_url | string | Full URL to avatar image |
bot | boolean | Whether this is a bot account |
system | boolean | Whether this is a system account |
joined_at | datetime | When the member joined the guild |
premium_since | datetime | When the member started boosting |
deaf | boolean | Whether deafened in voice |
mute | boolean | Whether muted in voice |
pending | boolean | Whether passed membership screening |
communication_disabled_until | datetime | Timeout expiry timestamp |
roles | array | Role IDs the member has |
display_name | string | Computed display name (nickname > global_name > username) |
formatted_username | string | Formatted username (username#discriminator or @username) |
is_timed_out | boolean | Whether member is currently timed out |
Get Discord Member
Get a specific member from a Discord guild by user ID.
GET /v1/discord/guilds/{guild_id}/members/{user_id}
Authentication: Required
Permission: discord:read
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
guild_id | string | Internal guild UUID (database ID) |
user_id | string | Discord user ID (snowflake) |
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"guild_id": "550e8400-e29b-41d4-a716-446655440001",
"user_id": "123456789012345678",
"username": "johndoe",
"discriminator": "0",
"global_name": "John Doe",
"nickname": "Johnny",
"avatar": "a_1234567890abcdef",
"avatar_url": "https://cdn.discordapp.com/avatars/123456789012345678/a_1234567890abcdef.png",
"bot": false,
"system": false,
"joined_at": "2024-01-15T10:00:00Z",
"premium_since": "2024-06-01T12:00:00Z",
"deaf": false,
"mute": false,
"pending": false,
"communication_disabled_until": null,
"roles": ["111222333444555666", "222333444555666777"],
"display_name": "Johnny",
"formatted_username": "@johndoe",
"is_timed_out": false
}
Sync Discord Members
Trigger a sync of all members for a specific guild from the Discord bot. This sends a request to the connected Discord bot via WebSocket and waits for the response.
POST /v1/discord/guilds/{guild_id}/members/sync
Authentication: Required
Permission: discord:guild.sync
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
guild_id | string | Discord guild ID (snowflake) |
Response:
{
"success": true,
"error": null,
"members_synced": 1500
}
The member sync operation has a 60-second timeout. For large guilds with many members, this may take longer. Ensure the Discord bot is connected and running.
Activity Log (Audit Events)
Query and manage audit logs for account activity tracking and security.
Get My Activity Log
Get the activity log for the authenticated user.
Endpoint: GET /v1/users/me/audit
Authentication: Required (Bearer token)
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
page | Integer | No | Page number (default: 1) |
limit | Integer | No | Items per page (default: 20, max: 100) |
event_type | String | No | Filter by event type |
resource_type | String | No | Filter by resource type |
status | String | No | Filter by status (success, failure) |
Response:
{
"events": [
{
"id": "81eebc99-9c0b-4ef8-bb6d-6bb9bd380a25",
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"event_type": "login",
"resource_type": "session",
"resource_id": "e0eebc99-9c0b-4ef8-bb6d-6bb9bd380a15",
"actor_id": null,
"ip_address": "192.168.1.100",
"user_agent": "Mozilla/5.0...",
"description": "Signed in via Twitch",
"metadata": { "provider": "twitch" },
"status": "success",
"error_message": null,
"country_code": "DE",
"country_name": "Germany",
"city": "Berlin",
"region": "Berlin",
"source_service": "id",
"created_at": "2025-01-05T10:30:00Z",
"is_reported": false
}
],
"total_count": 150,
"page": 1,
"limit": 20,
"total_pages": 8,
"_links": {
"self": "/v1/users/me/audit?page=1&limit=20"
}
}
Event Fields:
| Field | Type | Description |
|---|---|---|
id | String | Unique audit event ID |
user_id | String | User ID associated with the event |
event_type | String | Type of event (see event types table below) |
resource_type | String | Type of resource affected |
resource_id | String | ID of the affected resource |
actor_id | String | ID of actor if different from user |
ip_address | String | IP address of the request |
user_agent | String | User agent string |
description | String | Human-readable description |
metadata | Object | Additional event-specific data (JSON) |
status | String | Event status (success or failure) |
error_message | String | Error message if status is failure |
country_code | String | ISO 3166-1 alpha-2 country code |
country_name | String | Full country name |
city | String | City name (from GeoIP) |
region | String | Region/state name (from GeoIP) |
source_service | String | Service that created the event |
created_at | String | ISO 8601 timestamp |
is_reported | Boolean | Whether user reported this event |
Source Services:
| Value | Description |
|---|---|
api | Direct API calls |
id | Heimdall ID webapp |
backend | Backend dashboard |
policies | Policies webapp |
discord_bot | Discord bot |
twitch_bot | Twitch bot |
Event Types:
| Event Type | Description |
|---|---|
login | Successful sign-in |
login_failed | Failed sign-in attempt |
logout | User signed out |
session_created | New session created |
session_revoked | Session revoked |
2fa_enabled | Two-factor authentication enabled |
2fa_disabled | Two-factor authentication disabled |
2fa_verified | Two-factor code verified |
2fa_backup_codes_regenerated | Backup codes regenerated |
password_changed | Password was changed |
password_reset_requested | Password reset requested |
user_created | Account created |
user_updated | Account details updated |
user_deleted | Account deleted |
account_linked | Platform account linked |
account_unlinked | Platform account unlinked |
data_exported | User data exported |
client_created | OAuth client application created |
client_updated | OAuth client application updated |
client_secret_regenerated | OAuth client secret regenerated |
client_deleted | OAuth client application deleted |
consent_granted | OAuth consent granted |
consent_revoked | OAuth consent revoked |
api_key_created | API key created |
api_key_revoked | API key revoked |
bot_command_executed | Bot command executed (Discord/Twitch) |
bot_guild_configured | Bot guild/server settings configured |
Report Suspicious Activity
Report an audit event as suspicious activity.
Endpoint: POST /v1/users/me/audit/{event_id}/report
Authentication: Required (Bearer token)
Request Body:
{
"reason": "not_me",
"description": "I didn't make this login"
}
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
reason | String | Yes | Reason for reporting (not_me, suspicious, unknown_device, unknown_location, other) |
description | String | No | Additional details |
Response (201 Created):
{
"success": true,
"report": {
"id": "91eebc99-9c0b-4ef8-bb6d-6bb9bd380a26",
"audit_event_id": "81eebc99-9c0b-4ef8-bb6d-6bb9bd380a25",
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"reason": "not_me",
"description": "I didn't make this login",
"status": "pending",
"reviewed_by": null,
"reviewed_at": null,
"resolution_notes": null,
"created_at": "2025-01-05T12:00:00Z",
"updated_at": "2025-01-05T12:00:00Z"
},
"error": null
}
Get My Audit Reports
Get all audit reports submitted by the current user.
Endpoint: GET /v1/users/me/audit/reports
Authentication: Required (Bearer token)
Response:
[
{
"id": "91eebc99-9c0b-4ef8-bb6d-6bb9bd380a26",
"audit_event_id": "81eebc99-9c0b-4ef8-bb6d-6bb9bd380a25",
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"reason": "not_me",
"description": "I didn't make this login",
"status": "resolved",
"reviewed_by": "admin_123",
"reviewed_at": "2025-01-05T14:00:00Z",
"resolution_notes": "Confirmed unauthorized access. Password reset initiated.",
"created_at": "2025-01-05T12:00:00Z",
"updated_at": "2025-01-05T14:00:00Z"
}
]
Admin: Get All Audit Reports
Get all audit reports across the system.
Endpoint: GET /v1/admin/audit/reports
Authentication: Required (Bearer token)
Permission: audit:read
Query Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
status | String | No | Filter by status (pending, reviewed, resolved, dismissed) |
Response:
[
{
"id": "91eebc99-9c0b-4ef8-bb6d-6bb9bd380a26",
"audit_event_id": "81eebc99-9c0b-4ef8-bb6d-6bb9bd380a25",
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"reason": "not_me",
"description": "I didn't make this login",
"status": "pending",
"reviewed_by": null,
"reviewed_at": null,
"resolution_notes": null,
"created_at": "2025-01-05T12:00:00Z",
"updated_at": "2025-01-05T12:00:00Z"
}
]
Admin: Update Audit Report
Update the status of an audit report and send a notification email to the user.
Endpoint: PATCH /v1/admin/audit/reports/{report_id}
Authentication: Required (Bearer token)
Permission: audit:write
Request Body:
{
"status": "resolved",
"resolution_notes": "Confirmed unauthorized access. Password reset initiated and all sessions revoked."
}
Request Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
status | String | No | New status (pending, reviewed, resolved, dismissed) |
resolution_notes | String | No | Notes about the resolution |
Response:
{
"id": "91eebc99-9c0b-4ef8-bb6d-6bb9bd380a26",
"audit_event_id": "81eebc99-9c0b-4ef8-bb6d-6bb9bd380a25",
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"reason": "not_me",
"description": "I didn't make this login",
"status": "resolved",
"reviewed_by": "admin_123",
"reviewed_at": "2025-01-05T14:00:00Z",
"resolution_notes": "Confirmed unauthorized access. Password reset initiated and all sessions revoked.",
"created_at": "2025-01-05T12:00:00Z",
"updated_at": "2025-01-05T14:00:00Z"
}
Platform Integrations
Endpoints for managing platform integrations (e.g., Twitch channel integration for bot features).
Platform integrations connect your Heimdall account to external platforms like Twitch, enabling features such as channel point rewards, bot commands, and stream statistics tracking.
List All Integrations
Get all integrations for the authenticated user.
GET /integrations
Authorization: Bearer {token}
Response:
[
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"user_id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"platform_slug": "twitch",
"platform_name": "Twitch",
"platform_user_id": "12345678",
"username": "streamer_name",
"display_name": "Streamer Name",
"profile_image_url": "https://static-cdn.jtvnw.net/jtv_user_pictures/...",
"scopes": ["channel:read:subscriptions", "channel:manage:redemptions"],
"is_active": true,
"bot_status": "active",
"connected_at": "2025-01-15T10:00:00Z",
"last_refreshed_at": "2025-01-25T14:30:00Z"
}
]
List Platform Integrations
Get integrations for a specific platform.
GET /integrations/platform/{slug}
Authorization: Bearer {token}
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
slug | string | Platform slug (e.g., twitch) |
Response: Same format as List All Integrations.
Get Integration
Get a specific integration by ID.
GET /integrations/{id}
Authorization: Bearer {token}
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Integration UUID |
Response: Single integration object.
Connect Integration
Initiate OAuth connection to a platform.
POST /integrations/platform/{slug}/connect
Authorization: Bearer {token}
Content-Type: application/json
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
slug | string | Platform slug (e.g., twitch) |
Request Body:
{
"redirect_uri": "https://your-app.com/integrations/callback",
"scopes": ["channel:read:subscriptions", "channel:manage:redemptions"]
}
| Field | Type | Required | Description |
|---|---|---|---|
redirect_uri | string | Yes | URL to redirect after OAuth |
scopes | string[] | No | Requested OAuth scopes |
Response:
{
"authorization_url": "https://id.twitch.tv/oauth2/authorize?...",
"state": "abc123"
}
Redirect the user to authorization_url to complete the OAuth flow.
Disconnect Integration
Disconnect a platform integration.
DELETE /integrations/{id}
Authorization: Bearer {token}
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Integration UUID |
Response:
{
"success": true,
"message": "Integration disconnected successfully"
}
Refresh Integration Token
Refresh the OAuth token for an integration.
POST /integrations/{id}/refresh
Authorization: Bearer {token}
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Integration UUID |
Response:
{
"success": true,
"message": "Token refreshed successfully",
"expires_at": "2025-02-25T10:00:00Z"
}
Refresh Channel Stats
Refresh statistics for a streaming channel integration (e.g., Twitch).
POST /integrations/{id}/stats
Authorization: Bearer {token}
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Integration UUID |
Response:
{
"success": true,
"stats": {
"followers": 15000,
"subscribers": 250,
"is_live": true,
"current_viewers": 1500,
"stream_title": "Playing some games!",
"game_name": "Minecraft",
"updated_at": "2025-01-25T15:00:00Z"
}
}
Update Twitch Bot Status
Update the bot status for a Twitch integration.
PATCH /integrations/{id}/twitch-status
Authorization: Bearer {token}
Content-Type: application/json
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
id | string | Integration UUID |
Request Body:
{
"bot_status": "active"
}
| Field | Type | Required | Description |
|---|---|---|---|
bot_status | string | Yes | Status: active, paused, disabled |
Response:
{
"success": true,
"bot_status": "active"
}
Get Platform Scopes
Get available OAuth scopes for a platform.
GET /integrations/platform/{slug}/scopes
Authorization: Bearer {token}
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
slug | string | Platform slug (e.g., twitch) |
Response:
{
"platform": "twitch",
"scopes": [
{
"name": "channel:read:subscriptions",
"description": "Read subscriber list",
"required": false
},
{
"name": "channel:manage:redemptions",
"description": "Manage channel point rewards",
"required": true
}
]
}
List Integration Platforms
Get all available integration platforms.
GET /integrations/platforms
Authorization: Bearer {token}
Response:
{
"platforms": [
{
"slug": "twitch",
"name": "Twitch",
"description": "Connect your Twitch channel",
"icon_url": "https://static.twitch.tv/assets/...",
"enabled": true,
"features": ["channel_stats", "bot_commands", "channel_points"]
}
]
}
OAuth Callback
OAuth callback endpoint (called by the platform after authorization).
GET /integrations/callback/{platform}
Path Parameters:
| Parameter | Type | Description |
|---|---|---|
platform | string | Platform slug (e.g., twitch) |
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
code | string | Authorization code from OAuth provider |
state | string | State parameter for CSRF protection |
This endpoint is called automatically by the OAuth provider after user authorization. It exchanges the code for tokens and creates the integration.
HATEOAS Links
All responses include hypermedia links for API navigation:
self: Link to the current resourcefirst: First page (pagination)prev: Previous page (pagination)next: Next page (pagination)last: Last page (pagination)
Error Responses
All errors follow a consistent format:
{
"error": "NotFound",
"message": "GPS data not found"
}
Common HTTP status codes:
| Code | Description |
|---|---|
| 200 | OK - Request succeeded |
| 201 | Created - Resource created |
| 400 | Bad Request - Invalid input |
| 401 | Unauthorized - Authentication required |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found - Resource doesn't exist |
| 429 | Too Many Requests - Rate limit exceeded |
| 500 | Internal Server Error - Server error |
| 503 | Service Unavailable - Service is down |