GraphQL
GraphQL requests are sent to POST https://api.lumio.vision/v1/gql. The schema uses camelCase field and input names.
curl https://api.lumio.vision/v1/gql \
-H "Authorization: Bearer lm_usr_your_key" \
-H "Content-Type: application/json" \
-d '{"query":"query { userApiKeys { id label keyPrefix permissions } }"}'
Download the schema from GET /v1/schema. GraphiQL is available at GET /v1/graphiql where enabled.
Errors
GraphQL transport responses are HTTP 200 for executed GraphQL requests. Resolver failures are returned in errors[]. Authorization and shared API errors use extensions.code:
| Code | Meaning |
|---|---|
UNAUTHENTICATED | Missing authentication or anonymous credential on a guarded field. |
FORBIDDEN | Authenticated but blocked by permission, feature, account access, ownership, first-party, or admin guard. |
| Shared REST error codes | Resolvers that return shared ApiError values expose the same code family used by REST, such as VALIDATION_ERROR, BAD_REQUEST, NOT_FOUND, FEATURE_DISABLED, and PLAN_LIMIT_REACHED. |
GraphQL and REST use the same authorization model: account permission guards, user permission guards, admin permission guards, first-party user checks, owner/team checks, feature gates, and developer ownership checks have protocol twins.
Public music reads
publicSafeSongs, publicBlockedSongs, publicSong(id: UUID!), publicSongSitemap(after: UUID, first: Int), publicSystemPlaylists, publicSystemPlaylist(id: UUID!) and publicSongStatus are unauthenticated reads behind the fail-closed feature:copyright_detection gate. The gate resolves for the caller's account when one is signed in, otherwise globally. publicSong returns one global safe or blocked song, or null for an unknown or account-owned ID. publicSystemPlaylist returns one system playlist or null for an unknown ID. Their REST twins return 404 for missing detail records.
publicSongSitemap returns { items { id lastmod } hasMore }. first defaults to and is capped at 5,000; pass the last item's ID as after while hasMore is true. The server chooses the included global lists (safe and blocked); lastmod is the song's creation date or null. The REST twin is GET /v1/songs/sitemap with after and limit, returning has_more in snake_case.
Extension version mutations
createExtensionVersion validates the manifest schema, extension ID, and declared version before creating a draft. uploadExtensionVersion takes extensionId, version, bundleHash, and manifest, plus optional release metadata. It accepts no client storage-key fields; the server derives keys from the extension ID and validated version. The version input must match the manifest version. submitExtensionVersion revalidates the stored manifest before entering review.
For team-owned extensions, creation and upload require team-versions:create; submission requires team-extensions:publish. A missing grant returns FORBIDDEN with Missing team permission: <permission>. The REST twin uses the same validation and role check.
Developer create/update mutations accept only private, unlisted, public, or paid visibility and reject slugs beginning with system-. installExtension requires a published extension; a private one also requires an account grant or tester invitation. Ineligible targets return Extension not found, and an extension the account already has returns Extension is already installed. Reinstalling within 30 days of uninstallExtension restores the install with its saved configuration.
API-Key Fields
Personal keys:
query ListPersonalKeys {
userApiKeys {
id
keyPrefix
label
permissions
expiresAt
lastUsedAt
}
}
mutation CreatePersonalKey {
createUserApiKey(input: {
label: "Event reader"
permissions: ["events:read"]
expiresAt: "2026-12-31T23:59:59Z"
}) {
key
apiKey {
id
keyPrefix
label
permissions
}
}
}
Service keys:
query ListServiceKeys {
accountServiceKeys {
id
keyPrefix
label
permissions
createdBy
}
}
mutation CreateServiceKey {
createAccountServiceKey(input: {
label: "Chat relay"
permissions: ["chat:read", "chat:write"]
}) {
key
serviceKey {
id
keyPrefix
permissions
}
}
}
Developer keys:
query ListDeveloperKeys {
developerApiKeys {
id
keyPrefix
label
permissions
developerId
teamId
revokedAt
}
}
mutation CreateDeveloperKey {
createDeveloperApiKey(input: {
label: "CI deploy"
permissions: ["extension-dev:read", "extension-dev:edit", "extension-dev:publish"]
}) {
key
developerApiKey {
id
keyPrefix
permissions
}
}
}
Assignable Permissions
The GraphQL enum is:
enum ApiKeyKind {
PERSONAL
SERVICE
}
Use apiKeyAssignablePermissions(kind: PERSONAL) or apiKeyAssignablePermissions(kind: SERVICE) for personal and service keys. The generated schema does not contain DEVELOPER, so use REST GET /v1/api-keys/permissions?kind=developer for the developer-key scope registry.
query ServiceKeyScopes {
apiKeyAssignablePermissions(kind: SERVICE) {
key
label
category
}
}
WebSocket Tokens For Browsers
Browser clients cannot send Authorization headers during a WebSocket upgrade. A first-party browser session can mint a dedicated WebSocket token through GraphQL:
mutation IssueWsToken {
issueWsToken
}
Use that value as wss://api.lumio.vision/v1/ws?token=<ws_token>. API keys themselves are never accepted in ?token=.