Skip to main content

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:

ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger10Items 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:

ParameterTypeDescription
idUUIDGPS 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:

ParameterTypeDefaultDescription
pageinteger1Page number
limitinteger20Items 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:

ParameterTypeDescription
idstringUser 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"
}
FieldTypeRequiredDescription
localestring | nullNoPreferred 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:

ParameterTypeDescription
idstringUser ID
account_idstringPlatform 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"
}
}

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

Restrictions
  • 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)

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"
}
Password Requirements

The password must be at least 8 characters long.

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"
}

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 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"
}
Email Account Authentication

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:

FieldTypeDescription
idstringSession ID
ip_addressstringIP address used to create the session
user_agentstringFull user agent string
device_typestringParsed device type (desktop, mobile, tablet)
browser_namestringParsed browser name
os_namestringParsed operating system name
providerstringAuth provider slug used (twitch, discord, email, etc.)
provider_namestringAuth provider display name (Twitch, Discord, Email, etc.)
last_activity_atdatetimeLast activity timestamp
created_atdatetimeSession creation time
expires_atdatetimeSession expiration time
is_currentbooleanWhether 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:

ParameterTypeDescription
session_idstringSession ID to revoke

Response (200 OK):

{
"success": true,
"message": "Session revoked successfully"
}

Error Response (400 Bad Request):

{
"success": false,
"message": "Cannot revoke current session"
}
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)"
}
Use Case

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:

ParameterTypeRequiredDescription
user_idstringNoUser ID (required for system API keys)

Response:

{
"enabled": true,
"enabled_at": "2025-01-15T10:00:00Z",
"backup_codes_remaining": 8,
"platform_slug": "email"
}
FieldTypeDescription
enabledbooleanWhether 2FA is enabled
enabled_atstringWhen 2FA was enabled (ISO 8601)
backup_codes_remainingnumberNumber of unused backup codes
platform_slugstringPlatform 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:

ParameterTypeRequiredDescription
user_idstringNoUser ID (required for system API keys)

Response:

{
"required": true,
"enabled": false,
"reason": "Your role requires two-factor authentication"
}
FieldTypeDescription
requiredbooleanWhether 2FA is required
enabledbooleanWhether 2FA is enabled
reasonstringExplanation 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"
}
FieldTypeRequiredDescription
user_idstringNoUser ID (required for system API keys)
platform_idstringNoPlatform 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"
}
FieldTypeDescription
secretstringTOTP secret for manual entry
qr_code_urlstringOTPAuth URL for QR code
qr_code_imagestringBase64-encoded QR code PNG image
expires_atstringWhen the setup expires (10 minutes)
platform_slugstringPlatform used for 2FA
Setup Expiration

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"
}
FieldTypeRequiredDescription
user_idstringNoUser ID (required for system API keys)
codestringYes6-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"
}
FieldTypeDescription
successbooleanWhether verification succeeded
backup_codesstring[]One-time backup codes (shown once!)
messagestringSuccess message
Store Backup Codes Securely

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"
}
FieldTypeRequiredDescription
user_idstringNoUser ID (required for system API keys)
codestringYes6-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"
}
FieldTypeRequiredDescription
user_idstringNoUser ID (required for system API keys)
codestringYes6-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"
}
Previous Codes Invalidated

Regenerating backup codes invalidates all previous backup codes immediately. Make sure to store the new codes securely.

Permissions

ActionSelfOther Users
Get status✅ No special permissiontwo_factor:manage
Setup✅ No special permissiontwo_factor:manage
Verify✅ No special permissiontwo_factor:manage
Disable✅ No special permissiontwo_factor:manage
Regenerate codes✅ No special permissiontwo_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.

Internal Use Only

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:

ParameterTypeRequiredDescription
reasonstringNoReason for logout. Defaults to user_initiated.

Logout Reasons:

ReasonWhen Used
user_initiatedUser clicked sign out (default)
token_deletedAdmin deleted the session
session_expiredSession timed out
revokedSession was forcefully revoked
no_accessUser 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

  1. Login: User authenticates via credentials or OAuth
  2. Create: NextAuth calls POST /v1/sessions to create database session
  3. Use: Session token stored in session.accessToken for WebSocket auth
  4. Logout: NextAuth calls DELETE /v1/sessions/{token}?reason=user_initiated on sign out
  5. 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
}
}
warning

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
}
FieldTypeRequiredDescription
reasonstringNoReason for the ban
duration_secondsintegerNoBan 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"
}
Use with Caution

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..."
}
warning

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"
}
Important

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"
}
warning

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

warning

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

warning

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/zip
  • Content-Disposition: attachment; filename="heimdall-data-export-{date}.zip"

ZIP Archive Contents:

FileDescription
data.jsonMachine-readable export of all user data
report.pdfHuman-readable PDF summary report
README.txtExplanation 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:

CodeDescription
401Unauthorized - Authentication required
500Internal Server Error - Export generation failed
GDPR Compliance

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:

FieldTypeDescription
idstringInternal UUID primary key
guild_idstringDiscord guild ID (snowflake)
namestringGuild name
iconstringGuild icon hash
icon_urlstringFull URL to guild icon
owner_idstringDiscord user ID of guild owner
member_countintegerApproximate member count
boost_countintegerNumber of server boosts
premium_tierintegerPremium tier (0=none, 1=tier1, 2=tier2, 3=tier3)
bot_joined_atdatetimeWhen the bot joined this guild
last_synced_atdatetimeLast 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:

ParameterTypeDescription
guild_idstringDiscord 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:

ParameterTypeDescription
guild_idstringDiscord 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:

FieldTypeDescription
idstringInternal UUID primary key
channel_idstringDiscord channel ID (snowflake)
guild_idstringDiscord guild ID (snowflake)
namestringChannel name
channel_typestringChannel type name (Text, Voice, etc.)
channel_type_rawintegerRaw channel type value
parent_idstringParent category channel ID (Discord snowflake)
positionintegerPosition in channel list
topicstringChannel topic/description
nsfwbooleanWhether channel is NSFW
is_text_basedbooleanWhether channel supports text messages
is_voicebooleanWhether channel is a voice channel

Channel Types:

TypeNameDescription
0TextText channel
2VoiceVoice channel
4CategoryChannel category
5AnnouncementAnnouncement channel
10Announcement ThreadThread in announcement channel
11Public ThreadPublic thread
12Private ThreadPrivate thread
13StageStage channel
15ForumForum channel
16MediaMedia 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"
}
FieldTypeRequiredDescription
guild_idstringNoSpecific 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:

FieldTypeDescription
successbooleanWhether the sync was successful
errorstringError message if failed
guilds_syncedintegerNumber of guilds synced
channels_syncedintegerNumber of channels synced
roles_syncedintegerNumber of roles synced
Timeout

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:

ParameterTypeDescription
guild_idstringInternal 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:

ParameterTypeDescription
guild_idstringDiscord 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:

ParameterTypeDescription
guild_idstringDiscord 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:

ParameterTypeDescription
guild_idstringInternal guild UUID (database ID)

Query Parameters:

ParameterTypeDefaultDescription
searchstring-Search by username, global_name, or nickname
bot_onlybooleanfalseFilter to only show bots
humans_onlybooleanfalseFilter to only show humans (non-bots)
has_rolestring-Filter by role ID
limitinteger50Items per page (max: 100)
offsetinteger0Offset 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:

FieldTypeDescription
idstringInternal UUID primary key
guild_idstringInternal guild UUID
user_idstringDiscord user ID (snowflake)
usernamestringDiscord username
discriminatorstringLegacy discriminator (may be "0" for new usernames)
global_namestringGlobal display name
nicknamestringServer-specific nickname
avatarstringAvatar hash
avatar_urlstringFull URL to avatar image
botbooleanWhether this is a bot account
systembooleanWhether this is a system account
joined_atdatetimeWhen the member joined the guild
premium_sincedatetimeWhen the member started boosting
deafbooleanWhether deafened in voice
mutebooleanWhether muted in voice
pendingbooleanWhether passed membership screening
communication_disabled_untildatetimeTimeout expiry timestamp
rolesarrayRole IDs the member has
display_namestringComputed display name (nickname > global_name > username)
formatted_usernamestringFormatted username (username#discriminator or @username)
is_timed_outbooleanWhether 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:

ParameterTypeDescription
guild_idstringInternal guild UUID (database ID)
user_idstringDiscord 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:

ParameterTypeDescription
guild_idstringDiscord guild ID (snowflake)

Response:

{
"success": true,
"error": null,
"members_synced": 1500
}
Timeout

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:

ParameterTypeRequiredDescription
pageIntegerNoPage number (default: 1)
limitIntegerNoItems per page (default: 20, max: 100)
event_typeStringNoFilter by event type
resource_typeStringNoFilter by resource type
statusStringNoFilter 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:

FieldTypeDescription
idStringUnique audit event ID
user_idStringUser ID associated with the event
event_typeStringType of event (see event types table below)
resource_typeStringType of resource affected
resource_idStringID of the affected resource
actor_idStringID of actor if different from user
ip_addressStringIP address of the request
user_agentStringUser agent string
descriptionStringHuman-readable description
metadataObjectAdditional event-specific data (JSON)
statusStringEvent status (success or failure)
error_messageStringError message if status is failure
country_codeStringISO 3166-1 alpha-2 country code
country_nameStringFull country name
cityStringCity name (from GeoIP)
regionStringRegion/state name (from GeoIP)
source_serviceStringService that created the event
created_atStringISO 8601 timestamp
is_reportedBooleanWhether user reported this event

Source Services:

ValueDescription
apiDirect API calls
idHeimdall ID webapp
backendBackend dashboard
policiesPolicies webapp
discord_botDiscord bot
twitch_botTwitch bot

Event Types:

Event TypeDescription
loginSuccessful sign-in
login_failedFailed sign-in attempt
logoutUser signed out
session_createdNew session created
session_revokedSession revoked
2fa_enabledTwo-factor authentication enabled
2fa_disabledTwo-factor authentication disabled
2fa_verifiedTwo-factor code verified
2fa_backup_codes_regeneratedBackup codes regenerated
password_changedPassword was changed
password_reset_requestedPassword reset requested
user_createdAccount created
user_updatedAccount details updated
user_deletedAccount deleted
account_linkedPlatform account linked
account_unlinkedPlatform account unlinked
data_exportedUser data exported
client_createdOAuth client application created
client_updatedOAuth client application updated
client_secret_regeneratedOAuth client secret regenerated
client_deletedOAuth client application deleted
consent_grantedOAuth consent granted
consent_revokedOAuth consent revoked
api_key_createdAPI key created
api_key_revokedAPI key revoked
bot_command_executedBot command executed (Discord/Twitch)
bot_guild_configuredBot 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:

ParameterTypeRequiredDescription
reasonStringYesReason for reporting (not_me, suspicious, unknown_device, unknown_location, other)
descriptionStringNoAdditional 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:

ParameterTypeRequiredDescription
statusStringNoFilter 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:

ParameterTypeRequiredDescription
statusStringNoNew status (pending, reviewed, resolved, dismissed)
resolution_notesStringNoNotes 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).

About Integrations

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:

ParameterTypeDescription
slugstringPlatform 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:

ParameterTypeDescription
idstringIntegration 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:

ParameterTypeDescription
slugstringPlatform slug (e.g., twitch)

Request Body:

{
"redirect_uri": "https://your-app.com/integrations/callback",
"scopes": ["channel:read:subscriptions", "channel:manage:redemptions"]
}
FieldTypeRequiredDescription
redirect_uristringYesURL to redirect after OAuth
scopesstring[]NoRequested 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:

ParameterTypeDescription
idstringIntegration 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:

ParameterTypeDescription
idstringIntegration 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:

ParameterTypeDescription
idstringIntegration 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:

ParameterTypeDescription
idstringIntegration UUID

Request Body:

{
"bot_status": "active"
}
FieldTypeRequiredDescription
bot_statusstringYesStatus: 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:

ParameterTypeDescription
slugstringPlatform 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:

ParameterTypeDescription
platformstringPlatform slug (e.g., twitch)

Query Parameters:

ParameterTypeDescription
codestringAuthorization code from OAuth provider
statestringState 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.

All responses include hypermedia links for API navigation:

  • self: Link to the current resource
  • first: 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:

CodeDescription
200OK - Request succeeded
201Created - Resource created
400Bad Request - Invalid input
401Unauthorized - Authentication required
403Forbidden - Insufficient permissions
404Not Found - Resource doesn't exist
429Too Many Requests - Rate limit exceeded
500Internal Server Error - Server error
503Service Unavailable - Service is down

Next Steps