GraphQL
Lumio provides a GraphQL endpoint for flexible data querying.
Endpoint
POST /v1/gql
Base URLs
| Environment | URL |
|---|---|
| Production | https://api.lumio.vision/v1/gql |
| Production Preview | https://lumio.api.prod.zaflun.dev/v1/gql |
| Staging | https://lumio.api.staging.zaflun.dev/v1/gql |
Interactive Explorer
GraphiQL is available at /v1/graphiql when the API is running (gated behind graphql.playground = true in config). Use it to explore the schema, build queries, and test mutations.
Schema Download
Download the GraphQL SDL schema:
curl -o schema.graphql https://api.lumio.vision/v1/schema
Example Query
query {
overlays {
id
name
key
width
height
}
}
Feature Flags & Status
Queries
me.featureStatuses: [FeatureStatus!]!
Returns merged feature-flag statuses for the current user. Includes account-scope flags (all non-system:* categories) and the user-scope system:account_creation flag. Available on the Me type returned by me { ... }.
accountFeatures { enabledFeatures featureStatuses }
Returns feature flags scoped to the caller's active account. Requires authentication (no account:read permission needed — works with popout-token auth).
enabledFeatures: [String!]!— list of flag keys that are enabled for this account (excludessystem:*flags).featureStatuses: [FeatureStatus!]!— full status list withkey,enabled, andreasonfor each flag (excludessystem:*flags).
query {
accountFeatures {
enabledFeatures
featureStatuses {
key
enabled
reason
}
}
}
me { ownedAccountCount } and me { maxAccounts } — multi-account limits
The Me type exposes two fields for multi-account management:
ownedAccountCount: Int!— the number of Lumio accounts the authenticated user owns (i.e. where they hold the Owner role).maxAccounts: Int!— the maximum number of accounts this user may own, derived from the user's plan and any admin overrides.
These fields are used by the dashboard onboarding flow and account-creation gate to surface upgrade prompts when the user is at their account limit.
me { loginConnections } / account { channelConnections } / account { botConnections } — platform filtering
Login connections, channel connections, and bot connections filter by the corresponding platform flag (platform:{x}:login, platform:{x}:channel, platform:{x}:bot). A platform whose flag is disabled globally is excluded from these lists. This filtering also applies to the REST equivalents (GET /users/me/login-connections, GET /connections/channel, GET /bot-connections).
me { activeAccountId } — stale-membership filtering
If the JWT carries an accountId the user is no longer a member of (e.g. the
account owner removed them after the token was issued), the resolver returns
activeAccountId: null rather than the stale claim. Permissions are computed
against the corrected scope. The dashboard shell reads this signal and routes
the user to onboarding instead of rendering an account context they can no
longer access.
The REST DELETE /v1/accounts/{id}/members/{membership_id} (admin kick) and POST /v1/accounts/{id}/leave (self-leave) endpoints invalidate the Redis permission cache for the removed user, matching the existing GraphQL removeMember mutation behaviour — two-protocol parity for cache invalidation.
Mutations
adminUpdateUserAccountCreationOverride(userId: UUID!, override: AccountCreationOverride!): Boolean!
Set or clear the per-user system:account_creation override. Requires users:edit admin permission. Invalidates the cached override for the user immediately. Returns true on success.
Types
FeatureStatus
| Field | Type | Description |
|---|---|---|
key | String! | Feature flag key (e.g., feature:bots) |
enabled | Boolean! | Whether the feature is enabled for this caller |
reason | FeatureDisabledReason | Why the feature is disabled; null when enabled |
FeatureDisabledReason (enum)
| Value | Meaning |
|---|---|
GLOBAL_OFF | The flag is turned off globally by an admin kill-switch |
PLAN_LOCKED | The account's plan does not include this feature |
ACCOUNT_OVERRIDE | An explicit per-account override blocks this feature |
USER_OVERRIDE | An explicit per-user override blocks this feature (system:account_creation only) |
AccountCreationOverride (enum)
| Value | Description |
|---|---|
DEFAULT | Inherit the global system:account_creation flag setting |
ALLOW | User is always allowed to create accounts (overrides global OFF) |
DENY | User is always blocked from creating accounts (overrides global ON) |
AdminUser.accountCreationOverride: AccountCreationOverride!
Available on the AdminUser type returned by adminUser(id) and the user-list query. Reflects the stored per-user override value.
Protocol parity: All queries and mutations above have matching REST endpoints — see REST API.
Admin Role Management
The following queries and mutations are admin-scope: they check the caller's admin permissions (not account permissions) and return errors for unauthenticated or unauthorized requests.
Queries
adminRoles(): [AdminRole!]!
List all admin roles with their permissions and member counts. Requires admin-roles:read.
adminRole(id: UUID!): AdminRole!
Get a single admin role by ID. Requires admin-roles:read. Returns "Admin role not found" if the ID does not exist.
adminRoleMembers(roleId: UUID!): [AdminRoleMember!]!
List all users assigned to an admin role. Requires admin-roles:read.
allAdminPermissions(): [AdminPermissionInfo!]!
Return the full catalog of admin-scope permissions with their category labels. Requires admin-roles:read. Source: lo_auth::rbac::all_admin_permissions(). Used by the permission picker UI.
Mutations
adminCreateRole(input: CreateAdminRoleInput!): AdminRole!
Create a new admin role. Requires admin-roles:create.
- Auto-injects
admin:accessinto the permissions list if not present - Validates all permissions against
all_admin_permissions()— rejects unknown permissions with"Invalid permission: <perm>" - Returns
"Role name already in use"on duplicate name
adminUpdateRole(id: UUID!, input: UpdateAdminRoleInput!): AdminRole!
Update an existing admin role. Requires admin-roles:edit.
- Fields are optional: omit to leave unchanged
description: nullclears the description; omitting it leaves it unchanged- Permissions are applied as a diff against known permissions (unknown/legacy permissions are preserved)
- Same validation as create;
is_system = trueroles are fully editable (not protected from edits)
adminDeleteRole(id: UUID!): Boolean!
Delete an admin role. Requires admin-roles:delete.
- Returns
trueif deleted - Rejects with
"Cannot delete system admin role"ifis_system = true - Cascades to
admin_role_permissionsanduser_admin_roles
adminAssignUserRole(userId: UUID!, roleId: UUID!): Boolean!
Assign a user to an admin role. Requires admin-roles:edit. Idempotent — returns true if a new assignment was created, false if already assigned.
adminUnassignUserRole(userId: UUID!, roleId: UUID!): Boolean!
Remove a user's admin role assignment. Requires admin-roles:edit. Returns true if removed.
Types
AdminRole
| Field | Type | Description |
|---|---|---|
id | UUID! | Role ID |
name | String! | Display name |
description | String | Optional description |
isSystem | Boolean! | System roles cannot be deleted |
permissions | [String!]! | Sorted permission strings |
memberCount | Int! | Number of assigned users |
createdAt | String! | ISO-8601 timestamp |
updatedAt | String! | ISO-8601 timestamp |
AdminRoleMember
| Field | Type | Description |
|---|---|---|
userId | UUID! | User ID |
displayName | String! | User display name |
email | String | User email |
avatarUrl | String | User avatar URL |
assignedAt | String! | ISO-8601 timestamp |
AdminPermissionInfo
| Field | Type | Description |
|---|---|---|
permission | String! | Permission string (e.g., admin-roles:read) |
category | String! | Category label (e.g., Admin Role Management) |
CreateAdminRoleInput
| Field | Type | Required |
|---|---|---|
name | String! | Yes |
description | String | No |
permissions | [String!]! | Yes (may be empty) |
UpdateAdminRoleInput
| Field | Type | Notes |
|---|---|---|
name | String | Optional; trimmed |
description | String | null = clear, omit = leave unchanged |
permissions | [String!] | Optional; replaces via diff |
Protocol parity: All queries and mutations above have matching REST endpoints under
/v1/admin/admin-rolesand/v1/admin/admin-permissions— see REST API.
Plan Management
Admin-scope queries and mutations for managing subscription plans. All check plans:* admin permissions.
Queries
adminPlans: [AdminPlan!]!
List all plans with their feature assignments and account counts. Requires plans:read.
Mutations
adminCreatePlan(input: AdminCreatePlanInput!): AdminPlan!
Create a new plan. Requires plans:create.
- Validates
slugagainst regex^[a-z0-9]+(?:-[a-z0-9]+)*$, length 2–40 chars. Returns"Invalid slug format"on failure. - Rejects duplicate slugs with
"Plan slug already in use". - Rejects negative prices / limits with
"Price cannot be negative"/"Limit cannot be negative". currencydefaults to"USD"if omitted.- Stripe IDs are optional free-text strings. Lumio does not call Stripe — paste IDs from the Stripe dashboard.
adminUpdatePlan(id: UUID!, input: AdminUpdatePlanInput!): AdminPlan!
Update an existing plan. Requires plans:edit.
slugis immutable and is not part ofAdminUpdatePlanInput. To change a slug, create a new plan, migrate accounts, then delete the old one.- All input fields rewrite the plan's editable state.
- On success, invalidates the feature cache for every account currently on the plan.
- Returns
"Plan not found"if no plan has the given ID.
adminDeletePlan(id: UUID!): Boolean!
Delete a plan. Requires plans:delete. Returns true on success.
- Returns
"Plan not found"if no plan has the given ID. - Returns
"Cannot delete plan: N account(s) still reference it. Migrate them to a different plan first."if any accounts are still on the plan. - Cascades to
plan_featuresvia the FK constraint.
Types
AdminPlan
type AdminPlan {
id: UUID!
slug: String!
name: String!
description: String
priceMonthly: Int!
priceYearly: Int!
currency: String!
isPublic: Boolean!
sortOrder: Int!
maxOverlays: Int!
maxStorageBytes: Int!
maxUploadSizeBytes: Int!
maxIntegrations: Int!
chatRetentionDays: Int!
stripeProductId: String
stripeMonthlyPriceId: String
stripeYearlyPriceId: String
features: [AdminPlanFeature!]!
accountsUsing: Int!
}
AdminPlanFeature
type AdminPlanFeature {
featureId: UUID!
featureKey: String!
label: String!
enabled: Boolean!
}
AdminCreatePlanInput
input AdminCreatePlanInput {
slug: String!
name: String!
description: String
priceMonthly: Int!
priceYearly: Int!
currency: String
isPublic: Boolean!
sortOrder: Int!
maxOverlays: Int!
maxStorageBytes: Int!
maxUploadSizeBytes: Int!
maxIntegrations: Int!
chatRetentionDays: Int!
stripeProductId: String
stripeMonthlyPriceId: String
stripeYearlyPriceId: String
}
AdminUpdatePlanInput
input AdminUpdatePlanInput {
name: String!
description: String
priceMonthly: Int!
priceYearly: Int!
currency: String!
isPublic: Boolean!
sortOrder: Int!
maxOverlays: Int!
maxStorageBytes: Int!
maxUploadSizeBytes: Int!
maxIntegrations: Int!
chatRetentionDays: Int!
stripeProductId: String
stripeMonthlyPriceId: String
stripeYearlyPriceId: String
}
AdminUpdatePlanInput intentionally has no slug field — slugs are immutable after creation.
Protocol parity: All queries and mutations above have matching REST endpoints under
/v1/admin/plans— see REST API.
Public Pricing
Public, auth-optional queries used by the marketing pricing surfaces (/pricing, landing page, dashboard onboarding, /account/subscription).
plans
Returns every plan where is_public = true, ordered by sort_order. Admin-only is_public = false plans are filtered out.
type BillingPlan {
id: UUID!
slug: String!
name: String!
description: String
priceMonthly: String!
priceYearly: String!
currency: String!
isPublic: Boolean!
sortOrder: Int!
maxOverlays: Int!
maxStorageBytes: String!
maxUploadSizeBytes: String!
maxIntegrations: Int!
chatRetentionDays: Int!
features: [PlanFeature!]!
}
type PlanFeature {
featureId: UUID!
featureKey: String!
label: String!
enabled: Boolean!
"True when the underlying feature_flag kill-switch is on. Effective availability = enabled && globallyEnabled."
globallyEnabled: Boolean!
}
features[] semantics: Only flags whose category is in ('feature', 'widget', 'integration', 'bot_module') are returned — platform:*, system:*, automation:*, copyright_provider:*, and event:* are admin/infrastructure concerns and stay off the pricing card. The query uses a LEFT JOIN on plan_features with COALESCE(pf.enabled, false) — a flag without an explicit plan_features row renders as struck-through on every plan card (fail-closed). To include a flag in a plan, add a matching plan_features row via migration (see apps/api/migrations/20260415000008_backfill_plan_features_matrix.up.sql for the canonical pattern).
enabledPlatforms
extend type Query {
enabledPlatforms: [String!]!
}
Returns the list of streaming platform slugs whose kill-switch is globally enabled AND which have an enabled :login or :bot sub-flag. Used by the "Supported Platforms" badge row on every pricing card.
Integration-only platforms (currently Spotify — channel-OAuth only, no login and no chat bot) are intentionally excluded so they don't appear as streaming destinations on pricing cards, even though their kill-switch is on.
The companion resolver enabledProviders(connectionType: "login" | "channel" | "bot") returns the full per-subtype list and is what the ID app's login page and the /account/profile login-connections section use.
Login Assignments
Login assignments link a user's login connection to a specific Lumio account, enabling multi-account ownership from a single user identity.
Own assignments are always allowed without permissions. The
login-assignments:*permissions only apply when managing assignments on behalf of another user.
Queries
accountLoginAssignments(userId: UUID): [LoginAssignment!]!
List all login assignments for the caller's active account. When userId is supplied, returns the assignments for that specific user (requires login-assignments:read). When omitted, returns the caller's own assignments.
query {
accountLoginAssignments {
provider
loginConnectionId
userId
assignedAt
}
}
Mutations
assignLoginConnection(loginConnectionId: UUID!, provider: String!, userId: UUID): LoginAssignment!
Assign a login connection to the caller's active account. userId defaults to the authenticated user when omitted. Requires login-assignments:create to assign on behalf of another user; own assignments are always allowed.
removeLoginAssignment(provider: String!, userId: UUID): Boolean!
Remove the login assignment for the given provider from the active account. userId defaults to the authenticated user. Requires login-assignments:delete to remove another user's assignment; own assignments are always allowed. Returns true on success.
disconnectLoginConnection(loginConnectionId: UUID!): Boolean!
Delete a login connection by UUID. The connection must belong to the authenticated user. No special permission required beyond ownership. Returns true on success.
Note: This mutation supersedes the previous provider-based disconnect operation. The old form accepted a
providerstring; the new form accepts aloginConnectionIdUUID to uniquely identify the connection even when a user has multiple connections to the same platform.
Types
LoginAssignment
| Field | Type | Description |
|---|---|---|
provider | String! | Platform slug (e.g. "twitch", "google") |
loginConnectionId | UUID! | ID of the linked login connection |
userId | UUID! | ID of the owning user |
assignedAt | String! | ISO-8601 timestamp |
Protocol parity: All operations above have matching REST endpoints — see REST API.
Notifications
User-scoped notifications with read/unread tracking and actionable items. See Notifications for the full feature documentation, queries, and mutations.
The invite notification type carries data.inviteId (UUID of the account_invites row) and exposes two actions: accept_invite (adds the invitee as a member with the invite's role) and decline_invite (deletes the invite row).
Notification preferences
| Operation | Args | Returns | Permission |
|---|---|---|---|
notificationPreferences | — | [NotificationPreference!]! | Auth only |
updateNotificationPreference(notificationType: String!, channel: String!) | — | NotificationPreference! | Auth only |
channel accepts "off", "in_app", or "in_app_email". Updating a locked type (e.g. invite) returns an error.
Idea participant autocomplete
| Query | Args | Returns | Permission |
|---|---|---|---|
ideaParticipants(ideaId: UUID!, search: String) | — | [IdeaParticipant!]! | Auth only |
Returns the union of the idea author, voters, and commenters, filtered by the optional search string. Used to populate the @mention autocomplete dropdown in idea comments.
Protocol parity: Both operations have matching REST endpoints —
GET /v1/notifications/preferences,PATCH /v1/notifications/preferences/{type}, andGET /v1/ideas/{id}/participants— see REST API.
YouTube Member Badges
Account-scoped read query and a system-admin-scoped erasure mutation. See Member Badges for the underlying architecture.
Queries
youtubeMembershipTiers(): [YoutubeMembershipTier!]!
List YouTube member-tier badges observed for the caller's account, sorted by first-observation order. Returns [] when the cache has not been populated yet (e.g. before the first member message of the very first stream). Requires chat:read.
Fields on YoutubeMembershipTier: tooltip (raw English InnerTube tooltip — uniquely identifies the badge artwork at this loyalty milestone), tierName, badgeUrl, durationValue, durationUnit (MONTH/MONTHS/YEAR/YEARS), memberMonthsMin, sortOrder, firstSeenAt, lastSeenAt.
Mutations
eraseYoutubeMemberData(input: EraseYoutubeMemberDataInput!): EraseYoutubeMemberResult!
GDPR Art. 17 — erase all cached references to one YouTube member channel across the entire Lumio Redis namespace. Audit-logged (one global youtube_member_erasure row plus one per affected account). Requires admin:privacy-erase.
Result fields: erasedKeyCount, affectedAccountIds. The mutation deletes all lumio:yt:member:*:{memberChannelId} cache rows, the matching lumio:yt:refresh_lock:*:{memberChannelId} debounce locks, and the channel-emote cache lumio:yt:channel_emotes:{memberChannelId} (a no-op for pure viewers; only populated when the data subject is a broadcaster who hosts custom emotes). PII inside historical platform_chat_messages.badges is not addressed by this mutation — the limitation is disclosed in the Auskunftsbescheid; backfill is tracked as a follow-up.
Protocol parity: mirror at
GET /v1/youtube/memberships/tiersandDELETE /v1/admin/privacy/youtube/member/{id}— see REST API.
Chat Moderation
Account-scoped moderation across all four chat platforms. Queries for chat history / user info live in the chat module — see Chat for the full surface.
moderateChat(input: ModerationInput!): GqlModerationResult!
Perform a moderation action on twitch, youtube, kick, or trovo. Permission depends on the action: chat:ban for BAN, chat:timeout for TIMEOUT, chat:delete for DELETE. The mutation never returns a partial success — failures bubble up via result.success = false and result.details = "<message>" so the frontend can show them in the Failed-Sends banner.
ModerationInput fields:
action: ModerationActionGql!—BAN,TIMEOUT, orDELETEplatform: String!—"twitch","youtube","kick", or"trovo"userId: String— platform user ID (required forBAN/TIMEOUT)messageId: String— required forDELETEdurationSecs: Int— timeout length (defaults to300when omitted; YouTube accepts1–86400)reason: String— optional moderator reason (logged tomoderation_log)liveChatId: String— only used by YouTube. Optional: when omitted the server resolves the active broadcast'sliveChatIdfrom the polling worker's Redis cache (lumio:youtube:active_streams:{account_id}). Pass it explicitly when the broadcaster runs multiple concurrent broadcasts and you want to target a specific one.
Per-platform behaviour:
| Platform | Supported actions | Notes |
|---|---|---|
twitch | BAN, TIMEOUT, DELETE | Calls Helix; needs moderator:manage:banned_users / moderator:manage:chat_messages scope on the moderator's login token |
youtube | BAN, TIMEOUT, DELETE | BAN/TIMEOUT use liveChatBans.insert (type=permanent vs type=temporary + banDurationSeconds); DELETE uses liveChatMessages.delete. Returns 403 if the target is the broadcaster or another moderator — Lumios UI hides the buttons for those targets to surface the limitation as missing UI rather than a failed request. |
kick | DELETE only | Kick's public mod API only exposes message deletion; ban/timeout return BadRequest. |
trovo | BAN only | Trovo's public mod API has no timeout/delete endpoints. |
After a successful BAN or TIMEOUT the server soft-deletes every message from the affected user and broadcasts a chat:clear_user event to the chat WebSocket channel — see the WebSocket reference for the payload.
Protocol parity: mirror at
POST /v1/chat/moderate— see REST API.
refreshPlatformUserProfile(platform: String!, platformUserId: String!): GqlUnifiedProfile!
Force a fresh enrichment of a platform user's profile, bypassing the normal 24h/14-day staleness interval. Returns the same GqlUnifiedProfile type as the platformUserProfile query.
Permission: chat:refresh_user
Rate limit: One refresh per (account, platform, user) triple every 10 minutes. When the cooldown is active the mutation returns an error with extension { "code": "REFRESH_COOLDOWN", "retry_after_seconds": N }.
Platforms: "twitch", "youtube", "kick", "trovo".
Protocol parity: mirror at
POST /v1/chat/users/\{platform\}/\{platform_user_id\}/refresh— see REST API.
Ideas Hub
Community idea board with voting, comments, moderation, categories, and tags. All GET queries use OptionalAuth — they are public and return data for unauthenticated callers too. Mutations require the system:ideas_hub feature flag to be enabled on the account.
Queries
| Query | Arguments | Returns | Permission |
|---|---|---|---|
ideas | filter: IdeaFilterInput, sort: IdeaSortInput, limit: Int, offset: Int | IdeaConnection | Public |
idea | id: UUID! | Idea | Public |
ideaComments | ideaId: UUID! | [IdeaComment] | Public |
ideaCategories | — | [IdeaCategory] | Public |
ideaTags | search: String | [IdeaTag] | Public |
ideaVoters | ideaId: UUID! | [IdeaVoter] | Public |
ideaTimeline | ideaId: UUID! | [IdeaTimelineEntry] | Public |
ideaParticipants | ideaId: UUID!, search: String | [IdeaParticipant] | Auth only |
ideaParticipants returns the union of the idea author, voters, and commenters filtered by the optional search string. Used to populate the @mention autocomplete dropdown in idea comments.
Mutations
| Mutation | Arguments | Returns | Permission |
|---|---|---|---|
createIdea | input: CreateIdeaInput! | Idea | ideas:create |
updateIdea | id: UUID!, input: UpdateIdeaInput! | Idea | ideas:edit or ideas:moderate_edit |
deleteIdea | id: UUID! | Boolean | ideas:delete or ideas:moderate_delete |
voteIdea | id: UUID!, voteType: String! | Idea | ideas:vote |
removeVote | id: UUID! | Idea | ideas:vote |
createIdeaComment | input: CreateIdeaCommentInput! | IdeaComment | ideas:comment_create |
updateIdeaComment | id: UUID!, body: String! | IdeaComment | ideas:comment_edit |
deleteIdeaComment | id: UUID! | Boolean | ideas:comment_delete or ideas:moderate_comment |
updateIdeaStatus | id: UUID!, status: String! | Idea | ideas:moderate_status |
createIdeaCategory | input: CreateIdeaCategoryInput! | IdeaCategory | Admin ideas:edit |
updateIdeaCategory | id: UUID!, input: UpdateIdeaCategoryInput! | IdeaCategory | Admin ideas:edit |
deleteIdeaCategory | id: UUID! | Boolean | Admin ideas:delete |
createIdeaTag | name: String! | IdeaTag | ideas:create |
deleteIdeaTag | id: UUID! | Boolean | Admin ideas:delete |
Types
Idea
| Field | Type | Description |
|---|---|---|
id | UUID! | Idea ID |
author | IdeaAuthor! | Author info |
category | IdeaCategory | Category (nullable) |
tags | [IdeaTag!]! | Assigned tags |
title | String! | Idea title |
description | String! | Idea description (plain text or HTML) |
status | String! | Status slug (e.g. open, in_progress, done, declined) |
voteCountUp | Int! | Number of upvotes |
voteCountDown | Int! | Number of downvotes |
commentCount | Int! | Total comment count |
myVote | String | Authenticated caller's vote ("up", "down", or null) |
createdAt | String! | ISO-8601 timestamp |
updatedAt | String! | ISO-8601 timestamp |
IdeaComment
| Field | Type | Description |
|---|---|---|
id | UUID! | Comment ID |
ideaId | UUID! | Parent idea ID |
author | IdeaAuthor! | Comment author |
parentId | UUID | Parent comment ID for nested replies |
body | String! | Sanitized HTML from rich text editor |
createdAt | String! | ISO-8601 timestamp |
updatedAt | String! | ISO-8601 timestamp |
replies | [IdeaComment!]! | Nested replies (one level deep) |
Comment bodies contain sanitized HTML. @mentions appear as <span data-mention-id="UUID" class="mention">@Name</span>.
IdeaCategory
| Field | Type | Description |
|---|---|---|
id | UUID! | Category ID |
name | String! | Machine-readable slug |
label | String! | Display label |
color | String! | Hex color for UI display |
sortOrder | Int! | Sort position |
IdeaTag
| Field | Type | Description |
|---|---|---|
id | UUID! | Tag ID |
name | String! | Tag name |
IdeaTimelineEntry
| Field | Type | Description |
|---|---|---|
id | UUID! | Entry ID |
actor | IdeaAuthor! | Who performed the action |
action | String! | Action type (e.g. status_changed, edited) |
oldValue | String | Previous value |
newValue | String | New value |
createdAt | String! | ISO-8601 timestamp |
IdeaParticipant
| Field | Type | Description |
|---|---|---|
id | UUID! | User ID |
displayName | String! | Display name |
avatarUrl | String | Avatar URL |
IdeaFilterInput
| Field | Type | Description |
|---|---|---|
status | String | Filter by status slug |
categoryId | UUID | Filter by category |
tagIds | [UUID!] | Filter by one or more tags |
authorId | UUID | Filter by author |
search | String | Full-text search on title and description |
IdeaSortInput (enum)
| Value | Description |
|---|---|
NEWEST | Most recently created first |
MOST_VOTED | Highest net vote count first |
MOST_COMMENTED | Most comments first |
RECENTLY_UPDATED | Most recently updated first |
Protocol parity: All queries and mutations above have matching REST endpoints under
/v1/ideas— see REST API.
Real-time Updates
Lumio does not expose a GraphQL Subscription type. For real-time updates (events, chat, overlay changes), use the channel-based WebSocket at /v1/ws — see WebSocket.