Skip to main content

GraphQL API

Heimdall provides a flexible GraphQL API for querying GPS data and system information.

Endpoints

The GraphQL API is available at multiple endpoints:

EndpointMethodAuth RequiredAvailabilityDescription
/v1/gqlPOST/GETYesAll environmentsGraphQL queries and mutations
/v1/graphiqlGETNoDevelopment onlyInteractive GraphiQL IDE
/v1/schemaGETNoAll environmentsGraphQL schema in SDL format

GraphiQL Interactive IDE

Interactive GraphiQL IDE is available in development environments only:

Development: http://localhost:3000/v1/graphiql
Development Only

GraphiQL is only accessible when APP_ENV is not set to production. In production environments, accessing this endpoint returns a 404 error. This is a security measure to prevent exposing the interactive IDE in production.

The GraphiQL IDE provides:

  • Schema documentation browser
  • Auto-completion for queries and fields
  • Query validation and syntax highlighting
  • Real-time query execution
  • Query history
  • Schema introspection

Note: To execute queries in GraphiQL, you'll need to add your Bearer token in the HTTP HEADERS section since all GraphQL queries require authentication.

Authentication

The /v1/gql endpoint requires Bearer token authentication for all queries and mutations. Include the Authorization header with your requests:

curl -X POST https://api.elcto.com/v1/gql \
-H "Authorization: Bearer YOUR_TOKEN" \
-H "Content-Type: application/json" \
-d '{"query": "{ me { tokenId description role } }"}'

Authentication is required - Unlike some GraphQL APIs that allow unauthenticated access to certain queries, the Heimdall GraphQL endpoint requires authentication for all operations.

Queries

Get Current User Info

Retrieve information about the authenticated API token.

Authentication: Required

query {
me {
tokenId
description
role
isAdmin
}
}

Response:

{
"data": {
"me": {
"tokenId": "550e8400-e29b-41d4-a716-446655440000",
"description": "My API Token",
"role": "ADMIN",
"isAdmin": true
}
}
}

Get Current GPS Location

Retrieve the most recent GPS data point.

query {
currentGps {
id
latitude
longitude
altitude
timestamp
speed
createdAt
}
}

Response:

{
"data": {
"currentGps": {
"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"
}
}
}

List GPS Data

Retrieve a paginated list of GPS data points.

query {
gpsList(page: 1, limit: 10) {
data {
id
latitude
longitude
altitude
timestamp
speed
createdAt
}
pagination {
page
limit
total
totalPages
}
}
}

Response:

{
"data": {
"gpsList": {
"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,
"totalPages": 10
}
}
}
}

Get GPS by ID

Retrieve a specific GPS data point by ID.

query {
gpsById(id: "550e8400-e29b-41d4-a716-446655440000") {
id
latitude
longitude
altitude
timestamp
speed
createdAt
}
}

List Users

Retrieve a paginated list of all users.

Authentication: Required Permission: users:read

query {
users(page: 1, limit: 20, search: "john") {
users {
id
username
email
avatarUrl
platform
createdAt
updatedAt
lastLoginAt
privacyMode
}
totalCount
page
limit
totalPages
hasNextPage
hasPreviousPage
}
}

Response:

{
"data": {
"users": {
"users": [
{
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"username": "johndoe",
"email": "john@example.com",
"avatarUrl": "https://cdn.example.com/avatar.png",
"platform": "twitch",
"createdAt": "2025-01-15T10:00:00Z",
"updatedAt": "2025-01-20T15:30:00Z",
"lastLoginAt": "2025-01-25T09:00:00Z",
"privacyMode": false
}
],
"totalCount": 150,
"page": 1,
"limit": 20,
"totalPages": 8,
"hasNextPage": true,
"hasPreviousPage": false
}
}
}

Get User by ID

Retrieve a specific user by their ID.

Authentication: Required Permission: users:read (or self)

query {
user(id: "user-123") {
id
username
email
avatarUrl
platform
createdAt
updatedAt
lastLoginAt
privacyMode
}
}

Get User Profile

Retrieve a user's profile with login provider information.

Authentication: Required Permission: users:read (or self)

query {
userProfile(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
name
displayName
picture
provider
primaryProvider
lastLoginAt
createdAt
privacyMode
}
}

Get User Permissions

Retrieve a user's effective permissions.

Authentication: Required Permission: users:read (or self)

query {
userPermissions(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8")
}

Response:

{
"data": {
"userPermissions": ["users:read", "users:write", "gps:read", "*:*"]
}
}

Get User Accounts (Platform Accounts)

Retrieve all platform accounts linked to a user.

Authentication: Required Permission: users:read (or self)

query {
userAccounts(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
id
platformId
platformName
platformSlug
platformUserId
isOauth
isPrimary
username
email
avatarUrl
createdAt
updatedAt
}
}

Response:

{
"data": {
"userAccounts": [
{
"id": "7c9e6679-7425-40de-944b-e07fc1f90ae7",
"platformId": "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11",
"platformName": "Twitch",
"platformSlug": "twitch",
"platformUserId": "12345678",
"isOauth": true,
"isPrimary": true,
"username": "johndoe",
"email": "john@example.com",
"avatarUrl": "https://cdn.example.com/avatar.png",
"createdAt": "2025-01-15T10:00:00Z",
"updatedAt": "2025-01-20T15:30:00Z"
}
]
}
}

Get User Ban Status

Check if a user is currently banned.

Authentication: Required Permission: users:read (or self)

query {
userBanStatus(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
isBanned
activeBan {
id
reason
bannedAt
expiresAt
isPermanent
}
}
}

Mutations

Create GPS Data

Create a new GPS data point.

Authentication: Required (Admin only)

mutation {
createGps(input: {
latitude: 51.5074
longitude: -0.1278
altitude: 11.0
timestamp: 1700000000
speed: 5.5
}) {
id
latitude
longitude
altitude
timestamp
speed
createdAt
}
}

Response:

{
"data": {
"createGps": {
"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"
}
}
}

Update User

Update a user's information.

Authentication: Required Permission: users:write (or self)

mutation {
updateUser(
userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
username: "newusername"
email: "newemail@example.com"
avatarUrl: "https://cdn.example.com/new-avatar.png"
) {
id
username
email
avatarUrl
preferredLocale
updatedAt
}
}

All fields except userId are optional. Only provided fields will be updated.

Note: User objects include preferredLocale (e.g., "en", "de") for email communication preferences.

Update Display Name

Update only the user's display name (username).

Authentication: Required (self or system API key)

mutation {
updateDisplayName(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", newUsername: "mynewname") {
id
username
}
}

Update Privacy Mode

Toggle the user's privacy mode setting.

Authentication: Required (self or system API key)

mutation {
updatePrivacyMode(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", privacyMode: true) {
success
privacyMode
}
}

Update User Locale

Update the user's preferred locale for email communications.

Authentication: Required (self or system API key)

mutation {
updateUserLocale(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", locale: "de") {
success
locale
}
}

Response:

{
"data": {
"updateUserLocale": {
"success": true,
"locale": "de"
}
}
}

Supported locales: en, de

Delete User

Soft delete a user (sets deleted_at timestamp and revokes all sessions/API keys).

Authentication: Required Permission: users:delete

mutation {
deleteUser(id: "user-123")
}

Response:

{
"data": {
"deleteUser": true
}
}

Request Account Deletion

Schedule an account for deletion after a 7-day grace period.

Authentication: Required (self or system API key)

mutation {
requestAccountDeletion(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
isScheduledForDeletion
scheduledDeletionAt
}
}

Cancel Account Deletion

Cancel a scheduled account deletion.

Authentication: Required (self or system API key)

mutation {
cancelAccountDeletion(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8")
}

Request Email Change

Request an email change (sends verification to new email).

Authentication: Required (self or system API key)

mutation {
requestEmailChange(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", newEmail: "newemail@example.com") {
pending
newEmail
expiresAt
}
}

Verify Email Change

Complete the email change with verification token.

mutation {
verifyEmailChange(token: "verification-token-from-email")
}

Cancel Email Change

Cancel a pending email change request.

Authentication: Required (self or system API key)

mutation {
cancelEmailChange(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8")
}

Link an OAuth provider account to a user.

Authentication: Required Permission: users:write (or self)

mutation {
linkOauthAccount(
userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
input: {
oauthProvider: "twitch"
oauthProviderId: "12345678"
username: "johndoe"
email: "john@example.com"
avatarUrl: "https://cdn.example.com/avatar.png"
}
) {
id
platformId
platformName
platformSlug
platformUserId
isOauth
isPrimary
username
email
avatarUrl
createdAt
updatedAt
}
}

Unlink a platform account from a user.

Authentication: Required Permission: users:write (or self)

mutation {
unlinkPlatformAccount(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", accountId: "pa-456")
}

Response:

{
"data": {
"unlinkPlatformAccount": true
}
}
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)
  • If unlinking the primary account, another account will be automatically set as primary

Set Primary Platform Account

Change which platform account is used as the primary account for profile information.

Authentication: Required Permission: users:write (or self)

mutation {
setPrimaryPlatformAccount(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", accountId: "pa-456") {
id
platformId
platformName
platformSlug
platformUserId
isOauth
isPrimary
username
email
avatarUrl
createdAt
updatedAt
}
}

Response:

{
"data": {
"setPrimaryPlatformAccount": {
"id": "8f14e45f-ceea-467f-8a53-c9d5889d76b2",
"platformId": "b0eebc99-9c0b-4ef8-bb6d-6bb9bd380a12",
"platformName": "Discord",
"platformSlug": "discord",
"platformUserId": "98765432",
"isOauth": true,
"isPrimary": true,
"username": "johndoe#1234",
"email": "john@example.com",
"avatarUrl": "https://cdn.example.com/avatar.png",
"createdAt": "2025-01-15T10:00:00Z",
"updatedAt": "2025-01-25T14:30:00Z"
}
}
}
Primary Account

The primary account determines the user's default profile information (avatar, display name). When changed, the previous primary account's isPrimary flag is set to false.

Start the process of linking an email account to your user profile. This sends a verification email.

mutation RequestLinkEmailAccount($input: RequestEmailLinkInput!) {
requestLinkEmailAccount(input: $input) {
message
expiresAt
}
}

Variables:

{
"input": {
"email": "newemail@example.com",
"password": "securepassword123"
}
}

Response:

{
"data": {
"requestLinkEmailAccount": {
"message": "Verification email sent. Please check your inbox.",
"expiresAt": "2025-01-25T15:30:00Z"
}
}
}
Password Requirements

The password must be at least 8 characters long. This password will be used to authenticate with the email account after verification.

Complete the email account linking process by verifying the token sent to the email.

mutation VerifyLinkEmailAccount($input: VerifyEmailLinkInput!) {
verifyLinkEmailAccount(input: $input) {
message
platformAccountId
}
}

Variables:

{
"input": {
"token": "abc123def456..."
}
}

Response:

{
"data": {
"verifyLinkEmailAccount": {
"message": "Email account linked successfully",
"platformAccountId": "c0eebc99-9c0b-4ef8-bb6d-6bb9bd380a13"
}
}
}

Check if there's a pending email link verification for the current user.

mutation PendingEmailLinkStatus {
pendingEmailLinkStatus {
pending
email
expiresAt
}
}

Response (with pending link):

{
"data": {
"pendingEmailLinkStatus": {
"pending": true,
"email": "newemail@example.com",
"expiresAt": "2025-01-25T15:30:00Z"
}
}
}

Response (no pending link):

{
"data": {
"pendingEmailLinkStatus": {
"pending": false,
"email": null,
"expiresAt": null
}
}
}

Cancel a pending email link request.

mutation CancelLinkEmailAccount {
cancelLinkEmailAccount
}

Response:

{
"data": {
"cancelLinkEmailAccount": true
}
}
Email Account vs OAuth Account

Email accounts are a type of platform account that allows users to authenticate with email/password instead of OAuth. This is useful for users who don't want to link their social accounts or for platforms that don't support OAuth.

Schema Introspection

You can introspect the GraphQL schema to discover available types, queries, and mutations:

query {
__schema {
types {
name
kind
description
}
}
}

Get Type Details

query {
__type(name: "GpsData") {
name
kind
fields {
name
type {
name
kind
}
}
}
}

Variables

Use variables for dynamic queries:

query GetGpsById($id: String!) {
gpsById(id: $id) {
id
latitude
longitude
altitude
}
}

Variables:

{
"id": "550e8400-e29b-41d4-a716-446655440000"
}

Error Handling

GraphQL errors follow the standard format:

{
"errors": [
{
"message": "GPS data not found",
"locations": [
{
"line": 2,
"column": 3
}
],
"path": ["gpsById"]
}
],
"data": null
}

Best Practices

GraphQL Best Practices
  1. Request only needed fields - GraphQL allows you to select exactly what you need
  2. Use variables - For dynamic values, use variables instead of string interpolation
  3. Batch queries - Combine multiple queries in a single request
  4. Handle partial errors - GraphQL can return partial data with errors
  5. Use fragments - Reuse common field selections with fragments

Code Examples

JavaScript (Fetch)

const query = `
query {
currentGps {
latitude
longitude
altitude
}
}
`;

const response = await fetch('https://api.elcto.com/v1/gql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer YOUR_TOKEN'
},
body: JSON.stringify({ query })
});

const { data } = await response.json();
console.log(data.currentGps);

Python (requests)

import requests

query = """
query {
currentGps {
latitude
longitude
altitude
}
}
"""

response = requests.post(
'https://api.elcto.com/v1/gql',
json={'query': query},
headers={'Authorization': 'Bearer YOUR_TOKEN'}
)

data = response.json()['data']
print(data['currentGps'])

cURL

curl -X POST https://api.elcto.com/v1/gql \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{"query":"{ currentGps { latitude longitude altitude } }"}'

Active Sessions Management

Query and manage active login sessions across devices.

Queries

Get My Active Sessions

Get all active sessions for the authenticated user.

Authentication: Required

query {
myActiveSessions {
id
ipAddress
userAgent
deviceType
browserName
osName
provider
providerName
lastActivityAt
createdAt
expiresAt
isCurrent
}
}

Response:

{
"data": {
"myActiveSessions": [
{
"id": "d0eebc99-9c0b-4ef8-bb6d-6bb9bd380a14",
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"deviceType": "desktop",
"browserName": "Chrome",
"osName": "Windows",
"provider": "twitch",
"providerName": "Twitch",
"lastActivityAt": "2025-01-25T10:30:00Z",
"createdAt": "2025-01-20T08:00:00Z",
"expiresAt": "2025-02-20T08:00:00Z",
"isCurrent": true
},
{
"id": "e0eebc99-9c0b-4ef8-bb6d-6bb9bd380a15",
"ipAddress": "10.0.0.50",
"userAgent": "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0)...",
"deviceType": "mobile",
"browserName": "Safari",
"osName": "iOS",
"provider": "email",
"providerName": "Email",
"lastActivityAt": "2025-01-24T15:00:00Z",
"createdAt": "2025-01-15T12:00:00Z",
"expiresAt": "2025-02-15T12:00:00Z",
"isCurrent": false
}
]
}
}

Session Fields:

FieldTypeDescription
idString!Session ID
ipAddressStringIP address used to create the session
userAgentStringFull user agent string
deviceTypeStringParsed device type (desktop, mobile, tablet)
browserNameStringParsed browser name
osNameStringParsed operating system name
providerStringAuth provider slug (twitch, discord, email, etc.)
providerNameStringAuth provider display name (Twitch, Discord, Email)
lastActivityAtDateTimeLast activity timestamp
createdAtDateTime!Session creation time
expiresAtDateTime!Session expiration time
isCurrentBoolean!Whether this is the current session

Mutations

Revoke Session

Revoke a specific session by ID.

Authentication: Required

mutation {
revokeSession(sessionId: "sess-456")
}

Response:

{
"data": {
"revokeSession": true
}
}
Current Session

You cannot revoke your current session. The mutation will return an error if you try.

Revoke All Other Sessions

Revoke all sessions except the current one.

Authentication: Required

mutation {
revokeAllOtherSessions
}

Response:

{
"data": {
"revokeAllOtherSessions": 3
}
}

The returned number indicates how many sessions were revoked.

Security Feature

Use this when you suspect unauthorized access to your account. It will sign out all other devices while keeping you logged in.

Two-Factor Authentication

Manage two-factor authentication (2FA) using TOTP (Time-based One-Time Password).

Queries

Get 2FA Status

Check if 2FA is enabled for a user.

Authentication: Required

query {
twoFactorStatus(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
enabled
enabledAt
backupCodesRemaining
platformSlug
}
}

Parameters:

ParameterTypeRequiredDescription
userIdStringNoUser ID (defaults to current user)

Response:

{
"data": {
"twoFactorStatus": {
"enabled": true,
"enabledAt": "2025-01-15T10:00:00Z",
"backupCodesRemaining": 8,
"platformSlug": "email"
}
}
}

Check 2FA Requirement

Check if 2FA is required for a user based on their roles.

Authentication: Required

query {
userRequiresTwoFactor(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
required
enabled
reason
}
}

Response:

{
"data": {
"userRequiresTwoFactor": {
"required": true,
"enabled": false,
"reason": "Your role requires two-factor authentication"
}
}
}

Mutations

Setup 2FA

Initiate 2FA setup. Returns a TOTP secret and QR code for authenticator apps.

Authentication: Required

mutation {
setupTwoFactor(input: {
userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
platformId: "a0eebc99-9c0b-4ef8-bb6d-6bb9bd380a11"
}) {
secret
qrCodeUrl
qrCodeImage
expiresAt
platformSlug
}
}

Input:

FieldTypeRequiredDescription
userIdStringNoUser ID (defaults to current user)
platformIdStringNoPlatform for 2FA (defaults to email)

Response:

{
"data": {
"setupTwoFactor": {
"secret": "JBSWY3DPEHPK3PXP",
"qrCodeUrl": "otpauth://totp/Heimdall:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=Heimdall",
"qrCodeImage": "data:image/png;base64,iVBORw0KGgo...",
"expiresAt": "2025-01-25T10:10:00Z",
"platformSlug": "email"
}
}
}
Setup Expiration

The setup expires after 10 minutes. Verify the code before then, or initiate setup again.

Verify 2FA

Verify a TOTP code to complete 2FA setup.

Authentication: Required

mutation {
verifyTwoFactor(input: {
userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
code: "123456"
}) {
success
backupCodes
message
}
}

Input:

FieldTypeRequiredDescription
userIdStringNoUser ID (defaults to current user)
codeStringYes6-digit TOTP code

Response:

{
"data": {
"verifyTwoFactor": {
"success": true,
"backupCodes": [
"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"
}
}
}
Store Backup Codes Securely

Backup codes are only shown once. Store them in a secure location.

Disable 2FA

Disable two-factor authentication.

Authentication: Required

mutation {
disableTwoFactor(input: {
userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
code: "123456"
}) {
success
message
}
}

Input:

FieldTypeRequiredDescription
userIdStringNoUser ID (defaults to current user)
codeStringYesTOTP code or backup code

Response:

{
"data": {
"disableTwoFactor": {
"success": true,
"message": "Two-factor authentication disabled"
}
}
}

Regenerate Backup Codes

Generate new backup codes (invalidates all previous codes).

Authentication: Required

mutation {
regenerateBackupCodes(input: {
userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
code: "123456"
}) {
success
backupCodes
message
}
}

Input:

FieldTypeRequiredDescription
userIdStringNoUser ID (defaults to current user)
codeStringYesTOTP code to confirm

Response:

{
"data": {
"regenerateBackupCodes": {
"success": true,
"backupCodes": [
"ABCD-EFGH-IJKL",
"MNOP-QRST-UVWX",
"..."
],
"message": "Backup codes regenerated successfully"
}
}
}
Previous Codes Invalidated

Regenerating backup codes invalidates all previous backup codes immediately.

Verify 2FA During Login

Verify 2FA code during login flow (for auth systems).

Authentication: Required (System API key)

mutation {
verifyTwoFactorLogin(input: {
userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
code: "123456"
}) {
success
message
}
}

This mutation verifies a TOTP code during login without modifying 2FA settings.

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

OAuth Client Management

Manage OAuth client applications for third-party integrations.

Queries

List Available OAuth Scopes

Get all available OAuth scopes that can be requested by clients.

query {
oauthScopes {
name
description
}
}

Get My OAuth Clients

Get OAuth clients owned by the current user.

Authentication: Required Permission: OAuth client owner

query {
myOauthClients {
id
name
description
logoUrl
homepageUrl
privacyPolicyUrl
termsOfServiceUrl
redirectUris
allowedScopes
clientType
isActive
isFirstParty
createdAt
updatedAt
}
}

Get OAuth Client by ID

Get a specific OAuth client (must be owner or admin).

query {
oauthClient(id: "client-123") {
id
name
description
redirectUris
allowedScopes
clientType
isActive
isFirstParty
ownerUserId
createdAt
}
}

List All OAuth Clients (Admin)

List all OAuth clients in the system.

Authentication: Required Permission: Admin

query {
allOauthClients {
id
name
ownerUserId
isActive
isFirstParty
createdAt
}
}

Get My OAuth Consents

Get consents the current user has granted to OAuth clients.

query {
myOauthConsents {
id
userId
clientId
clientName
clientLogo
scopes
isFirstParty
createdAt
updatedAt
}
}

Get My OAuth Tokens

Get active OAuth access tokens for the current user.

query {
myOauthTokens {
id
clientId
clientName
scopes
expiresAt
createdAt
}
}

Mutations

Create OAuth Client

Create a new OAuth client application.

Authentication: Required

mutation {
createOauthClient(input: {
name: "My App"
description: "My awesome application"
logoUrl: "https://example.com/logo.png"
homepageUrl: "https://example.com"
privacyPolicyUrl: "https://example.com/privacy"
termsOfServiceUrl: "https://example.com/terms"
redirectUris: ["https://example.com/callback"]
allowedScopes: ["openid", "profile", "email"]
clientType: "confidential"
isFirstParty: false
}) {
client {
id
name
clientType
}
clientSecret
}
}
Client Secret

The clientSecret is only returned once at creation time. Store it securely!

Input Fields:

FieldTypeRequiredDescription
nameStringYesClient application name
descriptionStringNoApplication description
logoUrlStringNoURL to client logo
homepageUrlStringNoClient homepage URL
privacyPolicyUrlStringNoPrivacy policy URL
termsOfServiceUrlStringNoTerms of service URL
redirectUris[String!]!YesAllowed redirect URIs
allowedScopes[String!]!YesScopes client can request
clientTypeStringYes"confidential" or "public"
isFirstPartyBooleanNoFirst-party apps skip consent (default: false)

Update OAuth Client

Update an existing OAuth client.

mutation {
updateOauthClient(
id: "client-123"
input: {
name: "Updated Name"
description: "Updated description"
redirectUris: ["https://example.com/new-callback"]
}
) {
id
name
description
updatedAt
}
}

Regenerate Client Secret

Regenerate the client secret for a confidential client.

mutation {
regenerateOauthClientSecret(id: "client-123") {
success
clientSecret
error
}
}

Response:

FieldTypeDescription
successBoolean!Whether the operation succeeded
clientSecretStringThe new client secret (only shown once!)
errorStringError message if operation failed

Delete OAuth Client

Delete an OAuth client.

mutation {
deleteOauthClient(id: "client-123") {
success
error
}
}

Response:

FieldTypeDescription
successBoolean!Whether the deletion succeeded
errorStringError message if deletion failed

Revoke user consent for an OAuth client (disconnect the app).

mutation {
revokeOauthConsent(clientId: "client-123")
}

Revoke OAuth Token

Revoke a specific OAuth access token.

mutation {
revokeOauthToken(tokenId: "token-123")
}

API Key Management

Manage API keys for programmatic access to the Heimdall API.

Queries

List API Keys

Get all API keys for the authenticated user.

Authentication: Required Permission: api_keys:read

query {
apiKeys {
id
keyPrefix
name
description
scopes
isActive
isSystem
expiresAt
lastUsedAt
createdAt
updatedAt
}
}

Response:

{
"data": {
"apiKeys": [
{
"id": "ak_550e8400-e29b-41d4-a716-446655440000",
"keyPrefix": "hm_abc12345...",
"name": "My API Key",
"description": "Key for CI/CD pipeline",
"scopes": ["gps:read", "gps:write"],
"isActive": true,
"isSystem": false,
"expiresAt": "2025-12-31T23:59:59Z",
"lastUsedAt": "2025-01-25T10:30:00Z",
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-15T12:00:00Z"
}
]
}
}

API Key Fields:

FieldTypeDescription
idString!Unique API key identifier
keyPrefixString!Visible prefix of the key (e.g., hm_abc12345...)
nameString!Name/label for the key
descriptionStringOptional description
scopes[String!]!Permission scopes granted to this key
isActiveBoolean!Whether the key is currently active
isSystemBoolean!Whether this is a system-managed key
expiresAtDateTimeOptional expiration date
lastUsedAtDateTimeLast time the key was used
createdAtDateTime!Creation timestamp
updatedAtDateTime!Last update timestamp

Get API Key by ID

Get a specific API key by its ID.

Authentication: Required Permission: api_keys:read

query {
apiKey(id: "ak_550e8400-e29b-41d4-a716-446655440000") {
id
keyPrefix
name
description
scopes
isActive
isSystem
expiresAt
lastUsedAt
createdAt
updatedAt
}
}

Mutations

Create API Key

Create a new API key. The full key is only returned once at creation time.

Authentication: Required Permission: api_keys:write

mutation {
createApiKey(input: {
name: "CI/CD Pipeline Key"
description: "Key for automated deployments"
scopes: ["gps:read", "gps:write"]
expiresAt: "2025-12-31T23:59:59Z"
}) {
id
key
keyPrefix
name
description
scopes
isActive
isSystem
expiresAt
createdAt
updatedAt
}
}

Input Fields:

FieldTypeRequiredDescription
nameStringYesName/label for the key
descriptionStringNoOptional description
scopes[String!]NoPermission scopes (defaults to empty)
expiresAtDateTimeNoOptional expiration date

Response:

{
"data": {
"createApiKey": {
"id": "ak_550e8400-e29b-41d4-a716-446655440000",
"key": "hm_abcdefghijklmnopqrstuvwxyz123456",
"keyPrefix": "hm_abc12345...",
"name": "CI/CD Pipeline Key",
"description": "Key for automated deployments",
"scopes": ["gps:read", "gps:write"],
"isActive": true,
"isSystem": false,
"expiresAt": "2025-12-31T23:59:59Z",
"createdAt": "2025-01-25T14:00:00Z",
"updatedAt": "2025-01-25T14:00:00Z"
}
}
}
Store Your Key Securely

The key field contains the full API key and is only returned once at creation time. Store it securely immediately - you won't be able to retrieve it again.

Update API Key

Update an existing API key's properties.

Authentication: Required Permission: api_keys:write

mutation {
updateApiKey(
id: "ak_550e8400-e29b-41d4-a716-446655440000"
input: {
name: "Updated Key Name"
description: "Updated description"
scopes: ["gps:read"]
isActive: false
expiresAt: "2026-06-30T23:59:59Z"
}
) {
id
keyPrefix
name
description
scopes
isActive
expiresAt
updatedAt
}
}

Input Fields:

FieldTypeRequiredDescription
nameStringNoNew name for the key
descriptionStringNoNew description
scopes[String!]NoNew permission scopes
isActiveBooleanNoEnable or disable the key
expiresAtDateTimeNoNew expiration date

All fields are optional - only include fields you want to update.

System Keys

System API keys cannot be modified except by super admins. Attempting to update a system key will return an error.

Delete API Key

Delete an API key permanently.

Authentication: Required Permission: api_keys:delete

mutation {
deleteApiKey(id: "ak_550e8400-e29b-41d4-a716-446655440000") {
success
error
}
}

Response:

{
"data": {
"deleteApiKey": {
"success": true,
"error": null
}
}
}
System Keys

System API keys cannot be deleted. Attempting to delete a system key will return an error.

Ownership

Users can only manage their own API keys. Super admins can manage all API keys.

Platform Management

Manage authentication platform providers.

Queries

List All Platforms

Get all authentication platforms with their status.

query {
platforms {
id
name
slug
isOauth
enabled
twoFactorSupported
twoFactorRequired
createdAt
updatedAt
}
}

List Enabled Platforms

Get only enabled platforms (for login/registration UI).

query {
enabledPlatforms {
id
name
slug
isOauth
}
}

Get Platform by ID or Slug

Get a specific platform.

query {
platform(idOrSlug: "twitch") {
id
name
slug
enabled
twoFactorSupported
twoFactorRequired
}
}

Mutations

Enable Platform

Enable a platform for authentication.

Authentication: Required Permission: settings:write

mutation {
enablePlatform(idOrSlug: "discord") {
success
message
}
}

Disable Platform

Disable a platform for authentication.

Authentication: Required Permission: settings:write

mutation {
disablePlatform(idOrSlug: "discord") {
success
message
}
}
warning

Disabling a platform prevents new logins/registrations via that provider. Existing users can still access their accounts via other linked platforms.

Set Platform Enabled Status

Set platform enabled status directly.

mutation {
setPlatformEnabled(idOrSlug: "discord", enabled: true) {
id
name
enabled
}
}

Roles

Query and manage roles in the system.

Queries

List All Roles

Get all roles in the system.

Authentication: Required Permission: roles:read

query {
roles {
id
name
description
isSystem
requiresTwoFactor
permissions {
id
name
description
}
createdAt
updatedAt
}
}

Response:

{
"data": {
"roles": [
{
"id": "role_admin",
"name": "Admin",
"description": "Full administrative access",
"isSystem": true,
"requiresTwoFactor": true,
"permissions": [
{
"id": "perm_users_write",
"name": "users:write",
"description": "Write user data"
}
],
"createdAt": "2025-01-01T00:00:00Z",
"updatedAt": "2025-01-01T00:00:00Z"
}
]
}
}

Get Role by ID

Get a specific role by its ID.

Authentication: Required Permission: roles:read

query {
role(id: "role_admin") {
id
name
description
isSystem
requiresTwoFactor
permissions {
id
name
}
createdAt
updatedAt
}
}

Role Fields:

FieldTypeDescription
idString!Unique role identifier
nameString!Role display name
descriptionStringRole description
isSystemBoolean!Whether this is a system-managed role
requiresTwoFactorBoolean!Whether users with this role must have 2FA enabled
permissions[Permission!]!Permissions granted by this role
createdAtDateTime!Creation timestamp
updatedAtDateTime!Last update timestamp

Permissions

Query available permissions in the system.

Queries

List All Permissions

Get all available permissions.

Authentication: Required Permission: permissions:read

query {
permissions {
id
name
description
resource
action
createdAt
}
}

Response:

{
"data": {
"permissions": [
{
"id": "perm_users_read",
"name": "users:read",
"description": "Read user data",
"resource": "users",
"action": "read",
"createdAt": "2025-01-01T00:00:00Z"
},
{
"id": "perm_users_write",
"name": "users:write",
"description": "Write user data",
"resource": "users",
"action": "write",
"createdAt": "2025-01-01T00:00:00Z"
}
]
}
}

Get Permission by ID

Get a specific permission by its ID.

Authentication: Required Permission: permissions:read

query {
permission(id: "perm_users_read") {
id
name
description
resource
action
createdAt
}
}

Permission Fields:

FieldTypeDescription
idString!Unique permission identifier
nameString!Permission name in resource:action format
descriptionStringPermission description
resourceString!Resource this permission applies to
actionString!Action this permission allows
createdAtDateTime!Creation timestamp

Common Permissions:

PermissionDescription
users:readRead user profiles
users:writeUpdate user data
users:deleteDelete users
users:banBan/unban users
roles:readView roles
roles:writeCreate/update roles
permissions:readView permissions
audit:readView audit logs
audit:writeManage audit reports
discord:readView Discord data
discord:syncSync Discord data
api_keys:readView API keys
api_keys:writeCreate/update API keys
api_keys:deleteDelete API keys
two_factor:manageManage other users' 2FA
settings:readView system settings
settings:writeUpdate system settings

Platform Integrations

Query and manage platform integrations (e.g., Twitch channel integration).

Queries

List Platform Integrations

Get integrations for a specific platform.

Authentication: Required

query {
platformIntegrations(platformSlug: "twitch") {
id
userId
platformSlug
platformName
platformUserId
username
displayName
profileImageUrl
scopes
isActive
botStatus
connectedAt
lastRefreshedAt
}
}

Response:

{
"data": {
"platformIntegrations": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"userId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"platformSlug": "twitch",
"platformName": "Twitch",
"platformUserId": "12345678",
"username": "streamer_name",
"displayName": "Streamer Name",
"profileImageUrl": "https://static-cdn.jtvnw.net/...",
"scopes": ["channel:read:subscriptions"],
"isActive": true,
"botStatus": "active",
"connectedAt": "2025-01-15T10:00:00Z",
"lastRefreshedAt": "2025-01-25T14:30:00Z"
}
]
}
}

Get Integration

Get a specific integration by ID.

Authentication: Required

query {
integration(id: "550e8400-e29b-41d4-a716-446655440000") {
id
platformSlug
username
isActive
botStatus
}
}

List All Integrations

Get all integrations for the current user.

Authentication: Required

query {
allIntegrations {
id
platformSlug
platformName
username
isActive
}
}

Get Integration Scopes

Get available OAuth scopes for a platform integration.

Authentication: Required

query {
integrationScopes(platformSlug: "twitch") {
name
description
required
}
}

List Integration Platforms

Get all available integration platforms.

Authentication: Required

query {
integrationPlatforms {
slug
name
description
iconUrl
enabled
features
}
}

Mutations

Connect Integration

Initiate OAuth connection to a platform.

Authentication: Required

mutation {
connectIntegration(input: {
platformSlug: "twitch"
redirectUri: "https://your-app.com/integrations/callback"
scopes: ["channel:read:subscriptions"]
}) {
authorizationUrl
state
}
}

Redirect the user to authorizationUrl to complete the OAuth flow.

Disconnect Integration

Disconnect a platform integration.

Authentication: Required

mutation {
disconnectIntegration(id: "550e8400-e29b-41d4-a716-446655440000") {
success
message
}
}

Refresh Integration Token

Refresh the OAuth token for an integration.

Authentication: Required

mutation {
refreshIntegrationToken(id: "550e8400-e29b-41d4-a716-446655440000") {
success
expiresAt
}
}

Update Twitch Bot Status

Update the bot status for a Twitch integration.

Authentication: Required

mutation {
updateTwitchBotStatus(id: "550e8400-e29b-41d4-a716-446655440000", status: "active") {
success
botStatus
}
}

Status Values: active, paused, disabled

Refresh Channel Stats

Refresh statistics for a streaming channel integration.

Authentication: Required

mutation {
refreshChannelStats(id: "550e8400-e29b-41d4-a716-446655440000") {
success
stats {
followers
subscribers
isLive
currentViewers
streamTitle
gameName
updatedAt
}
}
}

Discord Bot Management

Query and manage Discord guilds and channels synced from the Discord bot.

Queries

List Discord Guilds

Get all Discord guilds synced from the bot.

Authentication: Required Permission: discord:read

query {
discordGuilds {
id
guildId
name
icon
iconUrl
ownerId
memberCount
boostCount
premiumTier
botJoinedAt
lastSyncedAt
}
}

Response:

{
"data": {
"discordGuilds": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"guildId": "123456789012345678",
"name": "My Server",
"icon": "a_1234567890abcdef",
"iconUrl": "https://cdn.discordapp.com/icons/123456789012345678/a_1234567890abcdef.png",
"ownerId": "987654321098765432",
"memberCount": 1500,
"boostCount": 14,
"premiumTier": 2,
"botJoinedAt": "2025-01-15T10:00:00Z",
"lastSyncedAt": "2025-01-25T14:30:00Z"
}
]
}
}

Guild Fields:

FieldTypeDescription
idString!Internal UUID primary key
guildIdString!Discord guild ID (snowflake)
nameString!Guild name
iconStringGuild icon hash
iconUrlStringFull URL to guild icon
ownerIdString!Discord user ID of guild owner
memberCountIntApproximate member count
boostCountIntNumber of server boosts
premiumTierIntPremium tier (0=none, 1=tier1, 2=tier2, 3=tier3)
botJoinedAtDateTimeWhen the bot joined this guild
lastSyncedAtDateTime!Last sync timestamp

Get Discord Guild

Get a specific Discord guild by its Discord guild ID.

Authentication: Required Permission: discord:read

query {
discordGuild(id: "123456789012345678") {
id
guildId
name
icon
iconUrl
ownerId
memberCount
boostCount
premiumTier
botJoinedAt
lastSyncedAt
}
}

Note: The id parameter requires the internal UUID (database ID), not the Discord snowflake.

List Discord Channels

Get all channels for a specific guild.

Authentication: Required Permission: discord:read

query {
discordChannels(guildId: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe") {
id
channelId
guildId
name
channelType
channelTypeRaw
parentId
position
topic
nsfw
isTextBased
isVoice
}
}

Response:

{
"data": {
"discordChannels": [
{
"id": "550e8400-e29b-41d4-a716-446655440001",
"channelId": "111222333444555666",
"guildId": "123456789012345678",
"name": "general",
"channelType": "Text",
"channelTypeRaw": 0,
"parentId": "999888777666555444",
"position": 0,
"topic": "General chat",
"nsfw": false,
"isTextBased": true,
"isVoice": false
}
]
}
}

Channel Fields:

FieldTypeDescription
idString!Internal UUID primary key
channelIdString!Discord channel ID (snowflake)
guildIdString!Discord guild ID (snowflake)
nameString!Channel name
channelTypeEnumChannel type (Text, Voice, Category, etc.)
channelTypeRawInt!Raw channel type value
parentIdStringParent category channel ID
positionInt!Position in channel list
topicStringChannel topic/description
nsfwBoolean!Whether channel is NSFW
isTextBasedBoolean!Whether channel supports text messages
isVoiceBoolean!Whether 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 Channel

Get a specific Discord channel by its Discord channel ID.

Authentication: Required Permission: discord:read

query {
discordChannel(channelId: "111222333444555666") {
id
channelId
guildId
name
channelType
channelTypeRaw
parentId
position
topic
nsfw
isTextBased
isVoice
}
}

Note: The channelId parameter requires the internal UUID (database ID), not the Discord snowflake.

Get Discord Guild with Channels

Get a guild with all its channels in a single request.

Authentication: Required Permission: discord:read

query {
discordGuildWithChannels(guildId: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe") {
guild {
id
guildId
name
icon
iconUrl
ownerId
memberCount
boostCount
premiumTier
botJoinedAt
lastSyncedAt
}
channels {
id
channelId
name
channelType
channelTypeRaw
parentId
position
topic
nsfw
isTextBased
isVoice
}
}
}

List Discord Members

Get paginated members for a specific guild with optional filtering.

Authentication: Required Permission: discord:read

query {
discordMembers(
guildId: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe"
page: 1
limit: 50
filter: { search: "john", humansOnly: true }
) {
members {
id
userId
username
globalName
nickname
displayName
formattedUsername
avatarUrl
bot
joinedAt
premiumSince
roles
isTimedOut
communicationDisabledUntil
}
totalCount
hasMore
}
}

Parameters:

ParameterTypeRequiredDescription
guildIdStringYesInternal guild UUID (database ID)
pageIntNoPage number (default: 1)
limitIntNoItems per page (default: 50, max: 100)
filterObjectNoFilter options

Filter Options:

FieldTypeDescription
searchStringSearch by username, global name, or nickname
botOnlyBooleanOnly show bots
humansOnlyBooleanOnly show humans (non-bots)
hasRoleStringFilter by role ID

Response:

{
"data": {
"discordMembers": {
"members": [
{
"id": "550e8400-e29b-41d4-a716-446655440002",
"userId": "111222333444555666",
"username": "johndoe",
"globalName": "John Doe",
"nickname": "Johnny",
"displayName": "Johnny",
"formattedUsername": "@johndoe",
"avatarUrl": "https://cdn.discordapp.com/avatars/111222333444555666/abc123.png",
"bot": false,
"joinedAt": "2024-06-15T10:00:00Z",
"premiumSince": "2024-12-01T00:00:00Z",
"roles": ["role-id-1", "role-id-2"],
"isTimedOut": false,
"communicationDisabledUntil": null
}
],
"totalCount": 1500,
"hasMore": true
}
}
}

Member Fields:

FieldTypeDescription
idString!Internal UUID primary key
userIdString!Discord user ID (snowflake)
usernameString!Discord username
discriminatorStringLegacy discriminator (may be "0")
globalNameStringGlobal display name
nicknameStringServer-specific nickname
displayNameString!Computed: nickname > globalName > username
formattedUsernameString!Formatted: @username or username#discriminator
avatarStringAvatar hash
avatarUrlStringFull URL to avatar
botBoolean!Whether this is a bot account
systemBoolean!Whether this is a system account
joinedAtDateTimeWhen the member joined
premiumSinceDateTimeWhen the member started boosting
roles[String!]!List of role IDs
isTimedOutBoolean!Whether member is currently timed out
communicationDisabledUntilDateTimeTimeout expiry

Get Discord Member

Get a specific member by their Discord user ID.

Authentication: Required Permission: discord:read

query {
discordMember(guildId: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe", userId: "111222333444555666") {
id
userId
username
globalName
nickname
displayName
avatarUrl
bot
joinedAt
roles
isTimedOut
}
}

Mutations

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.

Authentication: Required Permission: discord:sync

mutation {
syncDiscordGuilds {
success
error
guildsSynced
channelsSynced
rolesSynced
}
}

With specific guild ID:

mutation {
syncDiscordGuilds(guildId: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe") {
success
error
guildsSynced
channelsSynced
rolesSynced
}
}
Guild ID Format

The guildId parameter requires the internal UUID (database ID), not the Discord snowflake. Example: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe"

Response:

{
"data": {
"syncDiscordGuilds": {
"success": true,
"error": null,
"guildsSynced": 5,
"channelsSynced": 47,
"rolesSynced": 38
}
}
}

Response Fields:

FieldTypeDescription
successBooleanWhether the sync was successful
errorStringError message if failed
guildsSyncedIntNumber of guilds synced
channelsSyncedIntNumber of channels synced
rolesSyncedIntNumber 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.

Sync Discord Guilds Only

Sync only guild metadata from Discord (without channels or roles). Use this for a lightweight sync when you only need to update guild information.

Authentication: Required Permission: discord:sync

mutation {
syncDiscordGuildsOnly {
success
error
guildsSynced
}
}

Response:

{
"data": {
"syncDiscordGuildsOnly": {
"success": true,
"error": null,
"guildsSynced": 5
}
}
}

Sync Discord Channels Only

Sync only channels for a specific guild from Discord. Use this when you only need to update channel information.

Authentication: Required Permission: discord:guild.sync

mutation {
syncDiscordChannelsOnly(guildId: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe") {
success
error
channelsSynced
}
}

The guildId must be the internal UUID (database ID).

Response:

{
"data": {
"syncDiscordChannelsOnly": {
"success": true,
"error": null,
"channelsSynced": 30
}
}
}

Sync Discord Roles Only

Sync only roles for a specific guild from Discord. Use this when you only need to update role information.

Authentication: Required Permission: discord:guild.sync

mutation {
syncDiscordRolesOnly(guildId: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe") {
success
error
rolesSynced
}
}

The guildId must be the internal UUID (database ID).

Response:

{
"data": {
"syncDiscordRolesOnly": {
"success": true,
"error": null,
"rolesSynced": 16
}
}
}

Sync Discord Members

Sync all members for a specific guild from Discord. This can take longer for large guilds (60 second timeout).

Authentication: Required Permission: discord:guild.sync

mutation {
syncDiscordMembers(guildId: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe") {
success
error
membersSynced
}
}

The guildId must be the internal UUID (database ID).

Response:

{
"data": {
"syncDiscordMembers": {
"success": true,
"error": null,
"membersSynced": 1500
}
}
}
Large Guilds

Member sync can be slow for large guilds. The operation has a 60-second timeout.

Moderate Discord Member

Execute a moderation action on a Discord member (warn, kick, ban, timeout).

Authentication: Required Permission: Action-specific (discord:guild.warn, discord:guild.kick, discord:guild.ban, discord:guild.timeout)

mutation {
moderateDiscordMember(input: {
guildId: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe"
userId: "987654321098765432"
action: TIMEOUT
reason: "Spamming in general chat"
durationSeconds: 3600 # 1 hour timeout
}) {
success
error
action
}
}

Input Fields:

FieldTypeRequiredDescription
guildIdStringYesInternal guild UUID (database ID)
userIdStringYesTarget Discord user ID (snowflake)
actionEnumYesModeration action to perform
reasonStringNoReason for the action
durationSecondsIntNoDuration for timeout (required for TIMEOUT action)
deleteMessageDaysIntNoDays of messages to delete for ban (0-7)

Action Types:

ActionDescriptionRequired Permission
WARNSend a warning DM to the userdiscord:guild.warn
KICKKick the user from the guilddiscord:guild.kick
BANBan the user from the guilddiscord:guild.ban
TIMEOUTTimeout the user (requires durationSeconds)discord:guild.timeout
REMOVE_TIMEOUTRemove an existing timeoutdiscord:guild.timeout

Response:

{
"data": {
"moderateDiscordMember": {
"success": true,
"error": null,
"action": "TIMEOUT"
}
}
}

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.

Authentication: Required Permission: discord:delete

mutation {
deleteDiscordGuild(guildId: "5543dd84-9fe3-4331-b071-f2e7e39c8bbe")
}

Response:

{
"data": {
"deleteDiscordGuild": true
}
}

Get Discord Bot Settings

Get global Discord bot settings.

Authentication: Required Permission: discord:read

query {
discordBotSettings {
id
autoSyncEnabled
syncIntervalSeconds
initialDelaySeconds
defaultLanguage
notifyOnGuildJoin
notifyOnGuildLeave
commandCooldownSeconds
updatedAt
}
}

Response:

{
"data": {
"discordBotSettings": {
"id": "00000000-0000-0000-0000-000000000001",
"autoSyncEnabled": true,
"syncIntervalSeconds": 86400,
"initialDelaySeconds": 300,
"defaultLanguage": "en",
"notifyOnGuildJoin": true,
"notifyOnGuildLeave": true,
"commandCooldownSeconds": 3,
"updatedAt": "2025-01-15T10:30:00Z"
}
}
}

Update Discord Bot Settings

Update global Discord bot settings.

Authentication: Required Permission: discord:edit

mutation UpdateDiscordBotSettings($input: UpdateDiscordBotSettingsInput!) {
updateDiscordBotSettings(input: $input) {
success
error
}
}

Variables:

{
"input": {
"autoSyncEnabled": true,
"syncIntervalSeconds": 86400,
"defaultLanguage": "en"
}
}

All input fields are optional. Only include fields you want to update.

Update Discord Guild Settings

Update per-guild settings for a specific Discord guild.

Authentication: Required Permission: discord:edit

mutation UpdateDiscordGuildSettings($guildId: String!, $input: UpdateDiscordGuildSettingsInput!) {
updateDiscordGuildSettings(guildId: $guildId, input: $input) {
success
error
}
}

Variables:

{
"guildId": "123456789012345678",
"input": {
"defaultLanguage": "de"
}
}

Set defaultLanguage to null to use the global default.

Admin Actions

Administrative actions for user management.

User Bans

Get User Ban Status

Check if a user is currently banned.

query {
userBanStatus(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
isBanned
activeBan {
id
reason
bannedAt
expiresAt
isPermanent
}
}
}

Get User Ban History

Get the complete ban history for a user.

Authentication: Required Permission: users:read

query {
userBanHistory(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
id
reason
bannedBy
bannedAt
expiresAt
isPermanent
unbannedAt
unbannedBy
}
}

Ban User

Ban a user from the platform.

Authentication: Required Permission: users:ban

mutation {
banUser(input: {
userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
reason: "Violation of terms of service"
durationSeconds: 604800 # 7 days, null for permanent
}) {
id
userId
reason
bannedAt
expiresAt
isPermanent
}
}

Unban User

Remove a ban from a user.

Authentication: Required Permission: users:ban

mutation {
unbanUser(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8")
}

User Deletion

Get User Deletion Status (Admin)

Get deletion status for any user including deleted status.

Authentication: Required Permission: users:read

query {
userDeletionStatus(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
isScheduledForDeletion
scheduledDeletionAt
isDeleted
deletedAt
}
}

Force Delete User

Immediately delete a user (skip 7-day grace period).

Authentication: Required Permission: users:delete

mutation {
adminForceDeleteUser(
userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
reason: "GDPR urgent request"
) {
success
message
}
}
Use with Caution

Force deletion is immediate and irreversible. Use only for:

  • GDPR urgent requests
  • Compromised accounts
  • Legal requirements

Schedule User Deletion

Schedule a user for deletion with 7-day grace period.

Authentication: Required Permission: users:delete

mutation {
adminScheduleUserDeletion(
userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
reason: "Policy violation"
) {
success
message
}
}

Cancel User Deletion (Admin)

Cancel scheduled deletion for a user.

Authentication: Required Permission: users:write

mutation {
adminCancelUserDeletion(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
success
message
}
}

User Data Export (GDPR/DSGVO)

Export user data for GDPR/DSGVO compliance. The GraphQL API provides access to export logs, while the actual data export is available via the REST API endpoint GET /v1/user/export.

Queries

Get Data Export Logs

Retrieve the history of data exports for the current user.

Authentication: Required

query {
dataExportLogs {
id
exportedAt
format
fileSize
}
}

Response:

{
"data": {
"dataExportLogs": [
{
"id": "f0eebc99-9c0b-4ef8-bb6d-6bb9bd380a16",
"exportedAt": "2025-01-03T12:00:00Z",
"format": "zip",
"fileSize": 45678
}
]
}
}
Export via REST API

To download the actual data export (ZIP file with JSON + PDF), use the REST API endpoint:

GET /v1/user/export
Authorization: Bearer YOUR_TOKEN

This returns a ZIP archive containing:

  • data.json - Machine-readable export
  • report.pdf - Human-readable PDF report
  • README.txt - Explanation file

See the REST API documentation for full details.

Data Included in Export:

  • User profile (ID, username, privacy mode, preferred locale, dates)
  • Platform accounts (connected providers)
  • Connected apps (OAuth consents)
  • Owned apps (developer applications)
  • API keys (names, prefixes, scopes)
  • Roles and permissions
  • Two-factor authentication status
  • Account deletion status
  • Export history
  • Active sessions (device info, IP addresses, authentication provider used)
GDPR Compliance

This feature complies 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 structured, machine-readable format

Activity Log (Audit Events)

Query and manage audit logs for account activity tracking and security.

Queries

Get My Activity Log

Get the activity log for the authenticated user.

Authentication: Required

query {
myAuditLog(page: 1, limit: 20, eventType: "login") {
events {
id
userId
eventType
resourceType
resourceId
actorId
ipAddress
userAgent
description
metadata
status
errorMessage
countryCode
countryName
city
region
sourceService
createdAt
}
totalCount
page
limit
totalPages
}
}

Parameters:

ParameterTypeRequiredDescription
pageIntNoPage number (default: 1)
limitIntNoItems per page (default: 20, max: 100)
eventTypeStringNoFilter by event type

Response:

{
"data": {
"myAuditLog": {
"events": [
{
"id": "81eebc99-9c0b-4ef8-bb6d-6bb9bd380a25",
"userId": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
"eventType": "login",
"resourceType": "session",
"resourceId": "e0eebc99-9c0b-4ef8-bb6d-6bb9bd380a15",
"actorId": null,
"ipAddress": "192.168.1.100",
"userAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)...",
"description": "Signed in via Twitch",
"metadata": { "provider": "twitch" },
"status": "success",
"errorMessage": null,
"countryCode": "DE",
"countryName": "Germany",
"city": "Berlin",
"region": "Berlin",
"sourceService": "id",
"createdAt": "2025-01-05T10:30:00Z"
}
],
"totalCount": 150,
"page": 1,
"limit": 20,
"totalPages": 8
}
}
}
Location Data

Location fields (countryCode, countryName, city, region) are populated automatically from the IP address using the GeoIP database. If GeoIP is not configured, these fields will be null.

Event Types:

Event TypeDescription
loginSuccessful sign-in
login_failedFailed sign-in attempt
logoutUser signed out
session_createdNew session created
session_revokedSession revoked
all_sessions_revokedAll other sessions 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
deletion_scheduledAccount deletion scheduled
deletion_cancelledScheduled deletion cancelled
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
token_createdOAuth token created
token_revokedOAuth token revoked
account_linkedPlatform account linked
account_unlinkedPlatform account unlinked
primary_account_changedPrimary account changed
data_exportedUser data exported
api_key_createdAPI key created
api_key_revokedAPI key revoked
user_bannedUser banned (admin action)
user_unbannedUser unbanned (admin action)
bot_command_executedBot command executed (Discord/Twitch)
bot_guild_configuredBot guild/server settings configured

Source Services:

The sourceService field identifies which application or service created the audit event:

ValueDescription
apiDirect API calls
idHeimdall ID webapp (account management)
backendBackend dashboard (admin panel)
policiesPolicies webapp
discord_botDiscord bot
twitch_botTwitch bot

Get User Audit Log (Admin)

Get the activity log for a specific user.

Authentication: Required Permission: audit:read

query {
userAuditLog(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8", page: 1, limit: 20) {
events {
id
eventType
description
status
createdAt
}
totalCount
page
limit
totalPages
}
}

Get All Audit Events (Admin)

Get all audit events across the system.

Authentication: Required Permission: audit:read

query {
allAuditEvents(page: 1, limit: 50, eventType: "login", userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
events {
id
userId
eventType
description
metadata
ipAddress
countryCode
countryName
city
status
sourceService
createdAt
}
totalCount
page
limit
totalPages
}
}

Parameters:

ParameterTypeRequiredDescription
pageIntNoPage number (default: 1)
limitIntNoItems per page (default: 50, max: 100)
eventTypeStringNoFilter by event type
userIdStringNoFilter by user ID
Activity Log in Data Export

Audit events are also included in the GDPR data export. When users download their data export, their complete activity log is included with anonymized IP addresses.

Report Suspicious Activity

Users can report suspicious activity in their audit log if they notice something they don't recognize.

Report an Audit Event

Report an audit event as suspicious activity.

Authentication: Required

mutation {
reportAuditEvent(input: {
auditEventId: "81eebc99-9c0b-4ef8-bb6d-6bb9bd380a25"
reason: "not_me"
description: "I didn't make this login"
}) {
success
report {
id
auditEventId
reason
description
status
createdAt
}
error
}
}

Input Parameters:

ParameterTypeRequiredDescription
auditEventIdStringYesThe audit event ID to report
reasonStringYesReason for reporting (see table below)
descriptionStringNoAdditional details

Report Reasons:

ReasonDescription
not_meThe activity wasn't performed by the user
suspiciousGeneral suspicious activity
unknown_deviceActivity from an unknown device
unknown_locationActivity from an unknown location
otherOther concern

Response:

{
"data": {
"reportAuditEvent": {
"success": true,
"report": {
"id": "91eebc99-9c0b-4ef8-bb6d-6bb9bd380a26",
"auditEventId": "81eebc99-9c0b-4ef8-bb6d-6bb9bd380a25",
"reason": "not_me",
"description": "I didn't make this login",
"status": "pending",
"createdAt": "2025-01-05T12:00:00Z"
},
"error": null
}
}
}
Email Confirmation

Users receive a confirmation email when they submit a report, and another email when an admin updates the report status.

Get My Audit Reports

Get all audit reports submitted by the current user.

Authentication: Required

mutation {
myAuditReports {
id
auditEventId
reason
description
status
reviewedBy
reviewedAt
resolutionNotes
createdAt
updatedAt
}
}

Response:

{
"data": {
"myAuditReports": [
{
"id": "91eebc99-9c0b-4ef8-bb6d-6bb9bd380a26",
"auditEventId": "81eebc99-9c0b-4ef8-bb6d-6bb9bd380a25",
"reason": "not_me",
"description": "I didn't make this login",
"status": "resolved",
"reviewedBy": "admin_123",
"reviewedAt": "2025-01-05T14:00:00Z",
"resolutionNotes": "Confirmed unauthorized access. Password reset initiated.",
"createdAt": "2025-01-05T12:00:00Z",
"updatedAt": "2025-01-05T14:00:00Z"
}
]
}
}

Admin: Manage Audit Reports

Administrators can view and manage audit reports submitted by users.

Get All Audit Reports (Admin)

Get all audit reports across the system.

Authentication: Required Permission: audit:read

mutation {
allAuditReports(page: 1, limit: 20, status: "pending") {
reports {
id
auditEventId
userId
reason
description
status
reviewedBy
reviewedAt
resolutionNotes
createdAt
updatedAt
}
totalCount
page
limit
totalPages
}
}

Parameters:

ParameterTypeRequiredDescription
pageIntNoPage number (default: 1)
limitIntNoItems per page (default: 20, max: 100)
statusStringNoFilter by status (pending, reviewed, resolved, dismissed)

Update Audit Report (Admin)

Update the status of an audit report and send a notification email to the user.

Authentication: Required Permission: audit:write

mutation {
updateAuditReport(input: {
reportId: "91eebc99-9c0b-4ef8-bb6d-6bb9bd380a26"
status: "resolved"
resolutionNotes: "Confirmed unauthorized access. Password reset initiated and all sessions revoked."
}) {
success
report {
id
status
reviewedBy
reviewedAt
resolutionNotes
updatedAt
}
error
}
}

Input Parameters:

ParameterTypeRequiredDescription
reportIdStringYesThe report ID to update
statusStringYesNew status (reviewed, resolved, dismissed)
resolutionNotesStringNoNotes about the resolution

Report Statuses:

StatusDescription
pendingReport submitted, awaiting review
reviewedReport is being investigated
resolvedInvestigation complete, action taken
dismissedReport determined to be legitimate activity

Response:

{
"data": {
"updateAuditReport": {
"success": true,
"report": {
"id": "91eebc99-9c0b-4ef8-bb6d-6bb9bd380a26",
"status": "resolved",
"reviewedBy": "admin_123",
"reviewedAt": "2025-01-05T14:00:00Z",
"resolutionNotes": "Confirmed unauthorized access. Password reset initiated and all sessions revoked.",
"updatedAt": "2025-01-05T14:00:00Z"
},
"error": null
}
}
}
Email Notifications

When an admin updates a report status, the user automatically receives an email notification with the new status and any resolution notes.

Additional Mutations

Resend the verification email for a pending email account link.

mutation {
resendLinkEmailVerification(userId: "6ba7b810-9dad-11d1-80b4-00c04fd430c8") {
message
expiresAt
}
}

Generated Schema

The GraphQL schema is automatically generated using the generate-schema binary:

cargo run --bin generate-schema

This generates schema.graphql in the project root, which can be used for:

  • Client-side code generation
  • Schema validation
  • Documentation
  • IDE auto-completion

Next Steps