{"openapi":"3.0.0","paths":{"/api":{"get":{"description":"Returns a simple greeting message to verify that the API service is up and running.","operationId":"getData","parameters":[],"responses":{"200":{"description":"Service is running"}},"summary":"Health check","tags":["Health"]}},"/api/auth/login":{"post":{"description":"Exchange a Fusion Session ID for an application JWT access token.","operationId":"login","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginRequest"}}}},"responses":{"201":{"description":"Login successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}},"401":{"description":"Unauthorized"}},"summary":"Login with Fusion Session ID","tags":["Auth"]}},"/api/auth/login-credentials":{"post":{"description":"Authenticate using email and password to receive a JWT access token.","operationId":"loginWithCredentials","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginCredentials"}}}},"responses":{"201":{"description":"Login successful","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginResponse"}}}}},"summary":"Login with credentials","tags":["Auth"]}},"/api/auth/me":{"get":{"description":"Retrieve details of the currently authenticated user based on the JWT token.","operationId":"getProfile","parameters":[],"responses":{"200":{"description":"User profile retrieved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UserProfile"}}}},"401":{"description":"Unauthorized"}},"security":[{"bearer":[]}],"summary":"Get current user profile","tags":["Auth"]}},"/api/projects/{projectId}/sessions":{"post":{"description":"Create a new session entity. A session tracks audio transcription and analysis for a room. By default, the bot is started automatically (set autoStart to false to disable).","operationId":"createSession","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSessionRequest"}}}},"responses":{"201":{"description":"Session created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"400":{"description":"Project settings not configured, or `position` set for a project on ODIN SDK v2"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Create a new session","tags":["Sessions"]},"get":{"description":"Retrieve the sessions of this project, newest first. Without query parameters the full list is returned as a plain array (legacy). With `status`, `limit` or `cursor` the response is a page (`SessionListResponse`): pass its `nextCursor` back as `cursor` to continue.","operationId":"listSessions","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","description":"Only sessions with this status","schema":{"example":"active","type":"string","enum":["active","inactive"]}},{"name":"limit","required":false,"in":"query","description":"Page size (1–200). Defaults to 50.","schema":{"minimum":1,"maximum":200,"default":50,"example":50,"type":"number"}},{"name":"cursor","required":false,"in":"query","description":"Opaque cursor from the `nextCursor` of the previous page. Sessions are listed newest first.","schema":{"example":"MjAyNi0wOS0yMlQxMDowMDowMC4wMDBafDU1MGU4NDAw","type":"string"}}],"responses":{"200":{"description":"The sessions: a plain `SessionResponse[]` for the legacy call without parameters, a `SessionListResponse` page otherwise","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/SessionListResponse"},{"type":"array","items":{"$ref":"#/components/schemas/SessionResponse"}}]}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List sessions of the project","tags":["Sessions"]}},"/api/projects/{projectId}/sessions/{id}/start":{"post":{"description":"Command the transcription bot to join the associated ODIN room and start processing. For an end-to-end encrypted room, pass `e2eEncryptionPassword` again: it is deleted when a session stops. Ignored when the session is already active.","operationId":"startSession","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartSessionRequest"}}}},"responses":{"201":{"description":"Bot start command sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartSessionResponse"}}}},"400":{"description":"Project settings not configured"},"404":{"description":"Session not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Start a session (bot)","tags":["Sessions"]}},"/api/projects/{projectId}/sessions/{id}/stop":{"post":{"description":"Command the transcription bot to leave the ODIN room and stop processing.","operationId":"stopSession","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}}],"responses":{"201":{"description":"Bot stop command sent","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StopSessionResponse"}}}},"404":{"description":"Session not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Stop a session (bot)","tags":["Sessions"]}},"/api/projects/{projectId}/sessions/{id}":{"delete":{"description":"Permanently delete a session together with its transcript (messages), message and session annotations, debug audio, participant links and bot state. A running bot is commanded out of the room first; no summary is generated. Kept: sanctions issued in the session (they stay in force, only their link to the session is cleared), gatherings (unlinked) and the participants themselves. Publishes `session.deleted`. Cannot be undone. Not a complete erasure: copies already delivered to webhooks, functions or external moderation providers are outside Cortex. API keys need the `sessions.delete` scope.","operationId":"deleteSession","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Session deleted"},"404":{"description":"Session not found in this project"},"409":{"description":"The session is being started right now; retry in a few seconds"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Delete a session","tags":["Sessions"]},"get":{"description":"Retrieve detailed information about a specific session.","operationId":"getSession","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Session details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionResponse"}}}},"404":{"description":"Session not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get a session by ID","tags":["Sessions"]}},"/api/projects/{projectId}/sessions/batch-delete":{"post":{"description":"Permanently delete up to 100 sessions, each exactly like `DELETE /sessions/{id}`. Sessions are deleted one by one, each on its own, so a failure does not stop the rest: every ID is reported as `deleted`, `notFound` (including already deleted) or `failed` (rolled back, safe to retry). To delete all sessions of a project, page through `GET /sessions?limit=100` and pass each page here. API keys need the `sessions.delete` scope.","operationId":"deleteSessions","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSessionsRequest"}}}},"responses":{"200":{"description":"Per-session result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteSessionsResponse"}}}},"400":{"description":"Empty list, more than 100 IDs, duplicates or non-UUID IDs"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Delete several sessions","tags":["Sessions"]}},"/api/projects/{projectId}/sessions/{id}/actions":{"get":{"description":"Returns all plugin actions available for this session, grouped by plugin.","operationId":"getSessionActions","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of available actions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SessionActionsResponse"}}}},"404":{"description":"Session not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get available actions for a session","tags":["Sessions"]}},"/api/projects/{projectId}/sessions/{id}/regenerate-summary":{"post":{"description":"Delete existing summary and trigger the session-summary plugin to generate a new one with current settings.","operationId":"regenerateSummary","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Summary regeneration triggered"},"404":{"description":"Session not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Regenerate session summary","tags":["Sessions"]}},"/api/projects/{projectId}/sessions/{id}/messages/annotations/batch":{"post":{"description":"Batch retrieve annotations for a set of messages of this session, grouped by message ID.","operationId":"getSessionMessageAnnotationsBatch","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"messageIds":{"type":"array","items":{"type":"string"}}},"required":["messageIds"]}}}},"responses":{"200":{"description":"Message annotations grouped by message ID","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/MessageAnnotation"}}}}}},"404":{"description":"Session not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get annotations for messages of a session","tags":["Messages"]}},"/api/projects/{projectId}/sessions/{id}/messages":{"get":{"description":"Read the transcript of a session. Without query parameters the full transcript is returned as a plain array (legacy, deprecated for polling). With `after`, `limit`, `order` or `include` the response is a page (`MessageListResponse`) with keyset pagination on the per-session `seq`: poll with `after=<latestSeq>` and `If-None-Match: \"<latestSeq>\"` to receive `304 Not Modified` while nothing changed. `include=annotations,debugAudio` adds plugin annotations and the `hasDebugAudio` flag; both cost an extra query and are omitted otherwise.","operationId":"getMessages","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}},{"name":"after","required":false,"in":"query","description":"Cursor: only messages with a `seq` greater than this. Pass the `nextCursor` (or `latestSeq`) of the previous response. 0 or omitted starts at the beginning.","schema":{"minimum":0,"example":120,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size (1–200). Defaults to 50.","schema":{"minimum":1,"maximum":200,"default":50,"example":50,"type":"number"}},{"name":"order","required":false,"in":"query","description":"`asc` (default) walks the transcript forward from `after`; `desc` returns the newest page first (no continuation cursor).","schema":{"default":"asc","type":"string","enum":["asc","desc"]}},{"name":"include","required":false,"in":"query","description":"Extras to include per message, comma-separated or repeated: `annotations` (plugin annotations) and/or `debugAudio` (the `hasDebugAudio` flag). Each costs one extra query; omit what you do not need.","schema":{"example":["annotations"],"type":"array","items":{"type":"string","enum":["annotations","debugAudio"]}}},{"name":"if-none-match","required":false,"in":"header","description":"The `ETag` (newest `seq`, quoted) of a previous response. Answered with 304 when the session has no newer messages.","schema":{"type":"string"}}],"responses":{"200":{"description":"The transcript: a plain `Message[]` for the legacy call without parameters, a `MessageListResponse` page otherwise","headers":{"ETag":{"description":"Newest `seq` of the session, quoted (omitted when `annotations` are included)","schema":{"type":"string","example":"\"180\""}}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/MessageListResponse"},{"type":"array","items":{"$ref":"#/components/schemas/Message"}}]}}}},"304":{"description":"Not modified: the session has no messages newer than the `If-None-Match` value"},"404":{"description":"Session not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get messages for a session","tags":["Messages"]}},"/api/projects/{projectId}/sessions/{id}/messages/{messageId}/debug-audio":{"get":{"description":"Download the WAV file that was sent to the transcription API for this message. Only available when debug audio is enabled and within the 24-hour retention window.","operationId":"downloadDebugAudio","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}},{"name":"messageId","required":true,"in":"path","description":"Message ID","schema":{"type":"string"}}],"responses":{"200":{"description":"WAV audio file"},"404":{"description":"Debug audio not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Download debug audio for a message","tags":["Messages"]}},"/api/projects/{projectId}/webhooks/subscriptions":{"post":{"description":"Register a new webhook endpoint. You can specify which events to listen to and authentication details for the callback.","operationId":"createSubscription","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateWebhookSubscriptionRequest"}}}},"responses":{"201":{"description":"Subscription created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Create a webhook subscription","tags":["Webhooks"]},"get":{"description":"Retrieve a list of all webhook subscriptions configured for this tenant, optionally filtered by project.","operationId":"listSubscriptions","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"List of subscriptions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookSubscription"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List webhook subscriptions","tags":["Webhooks"]}},"/api/projects/{projectId}/webhooks/events":{"get":{"description":"Retrieve a paginated log of events that were processed by the webhook system.","operationId":"listEvents","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"status","required":false,"in":"query","description":"Filter by delivery status","schema":{"enum":["pending","delivered","failed"],"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Items to return","schema":{"type":"number"}},{"name":"offset","required":false,"in":"query","description":"Items to skip","schema":{"type":"number"}}],"responses":{"200":{"description":"List of webhook events","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/WebhookEvent"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List webhook events","tags":["Webhooks"]}},"/api/projects/{projectId}/webhooks/events/{id}":{"get":{"description":"Retrieve details of a specific webhook event, including the payload and a list of delivery attempts.","operationId":"getEvent","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Event ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Event details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookEventDetail"}}}},"404":{"description":"Event not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get webhook event details with deliveries","tags":["Webhooks"]}},"/api/projects/{projectId}/webhooks/subscriptions/{id}":{"get":{"description":"Retrieve details of a specific webhook subscription.","operationId":"getSubscription","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Subscription ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}}},"404":{"description":"Subscription not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get a webhook subscription by ID","tags":["Webhooks"]},"put":{"description":"Update the configuration of an existing webhook subscription.","operationId":"updateSubscription","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Subscription ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWebhookSubscriptionRequest"}}}},"responses":{"200":{"description":"Subscription updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/WebhookSubscription"}}}},"404":{"description":"Subscription not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update a webhook subscription","tags":["Webhooks"]},"delete":{"description":"Permanently remove a webhook subscription.","operationId":"deleteSubscription","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Subscription ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Subscription deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteWebhookSubscriptionResponse"}}}},"404":{"description":"Subscription not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Delete a webhook subscription","tags":["Webhooks"]}},"/api/plugins":{"get":{"description":"Retrieve a list of all plugins available in the system catalog.","operationId":"listPlugins","parameters":[],"responses":{"200":{"description":"List of available plugins","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Plugin"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List available plugins","tags":["Plugins"]}},"/api/plugins/{slug}":{"get":{"description":"Retrieve detailed information about a specific plugin from the catalog.","operationId":"getPlugin","parameters":[{"name":"slug","required":true,"in":"path","description":"Plugin slug (e.g., profanity-filter)","schema":{"type":"string"}}],"responses":{"200":{"description":"Plugin details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Plugin"}}}},"404":{"description":"Plugin not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get plugin by slug","tags":["Plugins"]}},"/api/plugins/annotations/sessions/{sessionId}":{"get":{"description":"Retrieve a list of annotations created by plugins for a specific session.","operationId":"getSessionAnnotations","parameters":[{"name":"sessionId","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Session annotations","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SessionAnnotation"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get session annotations","tags":["Plugins"]}},"/api/plugins/annotations/messages/batch":{"post":{"description":"Batch retrieve annotations for a set of messages, grouped by message ID. Only annotations of the caller's own tenant are returned; for a project you were invited to, use POST /projects/{projectId}/sessions/{id}/messages/annotations/batch.","operationId":"getMessageAnnotationsBatch","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","properties":{"messageIds":{"type":"array","items":{"type":"string"}}}}}}},"responses":{"200":{"description":"Message annotations grouped by message ID","content":{"application/json":{"schema":{"type":"object","additionalProperties":{"type":"array","items":{"$ref":"#/components/schemas/MessageAnnotation"}}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get annotations for multiple messages","tags":["Plugins"]}},"/api/projects/{projectId}/plugins/instances":{"get":{"description":"Retrieve a list of all installed plugins (instances) for the specified project.","operationId":"listInstances","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"List of plugin instances","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/PluginInstanceWithPlugin"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List installed plugin instances","tags":["Plugin Instances"]},"post":{"description":"Create a new instance of a plugin, configuring it for the specified project.","operationId":"createInstance","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePluginInstanceRequest"}}}},"responses":{"201":{"description":"Plugin installed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PluginInstance"}}}},"400":{"description":"Invalid request"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Install a plugin","tags":["Plugin Instances"]}},"/api/projects/{projectId}/plugins/instances/{id}":{"get":{"description":"Retrieve details of a specific plugin installation.","operationId":"getInstance","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Instance ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Plugin instance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PluginInstanceWithPlugin"}}}},"404":{"description":"Instance not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get plugin instance","tags":["Plugin Instances"]},"put":{"description":"Update settings or enable/disable a specific plugin instance.","operationId":"updateInstance","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Instance ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdatePluginInstanceRequest"}}}},"responses":{"200":{"description":"Instance updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PluginInstance"}}}},"404":{"description":"Instance not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update plugin instance","tags":["Plugin Instances"]},"delete":{"description":"Permanently remove a plugin instance.","operationId":"deleteInstance","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Instance ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Plugin uninstalled","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeletePluginInstanceResponse"}}}},"404":{"description":"Instance not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Uninstall a plugin","tags":["Plugin Instances"]}},"/api/api-keys":{"get":{"description":"Retrieve a list of all active API keys, including metadata like last used date and scopes.","operationId":"listKeys","parameters":[],"responses":{"200":{"description":"List of API keys","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ApiKeyResponse"}}}}}},"security":[{"bearer":[]}],"summary":"List all API keys for the tenant","tags":["API Keys"]},"post":{"description":"Generate a new API key with specified scopes. The secret key is only returned in the response and cannot be retrieved later.","operationId":"createKey","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKey"}}}},"responses":{"201":{"description":"API key created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateApiKeyResponse"}}}}},"security":[{"bearer":[]}],"summary":"Create a new API key","tags":["API Keys"]}},"/api/api-keys/{id}":{"delete":{"description":"Permanently revoke an API key. It will essentially be deleted and cannot be used anymore.","operationId":"revokeKey","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"API key revoked"},"404":{"description":"API key not found"}},"security":[{"bearer":[]}],"summary":"Revoke an API key","tags":["API Keys"]}},"/api/api-keys/scopes":{"get":{"description":"Every grantable API key scope with display metadata (product, family, label, access kind, sensitivity), plus named presets. UIs should render their scope picker from this instead of hardcoding scopes.","operationId":"getScopes","parameters":[],"responses":{"200":{"description":"The scope catalog","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApiKeyScopeCatalog"}}}}},"security":[{"bearer":[]}],"summary":"Get the API key scope catalog","tags":["API Keys"]}},"/api/projects/token":{"post":{"description":"Generate a short-lived access token for a specific room and project context.","operationId":"generateTokenLegacy","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateTokenRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateTokenResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Generate access token","tags":["Projects"]}},"/api/projects/{projectId}/settings":{"get":{"description":"Retrieve configuration settings for a specific project.","operationId":"getAppSettings","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Project settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSettingsResponse"}}}},"404":{"description":"Settings not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get project settings","tags":["App Settings"]},"put":{"description":"Save or update configuration settings for a specific project, including token provider details.","operationId":"upsertAppSettings","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpsertAppSettingsRequest"}}}},"responses":{"200":{"description":"Settings saved","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSettingsResponse"}}}},"409":{"description":"Project is already configured in Cortex by another account"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Create or update project settings","tags":["App Settings"]},"patch":{"description":"Update only the provided settings fields; all others are preserved. Ideal for changing just the bot userData without re-sending the whole configuration.","operationId":"patchAppSettings","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAppSettingsRequest"}}}},"responses":{"200":{"description":"Settings updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSettingsResponse"}}}},"409":{"description":"Project is already configured in Cortex by another account"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Partially update project settings","tags":["App Settings"]},"delete":{"description":"Remove configuration settings for a specific project.","operationId":"deleteAppSettings","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Settings deleted"},"404":{"description":"Settings not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Delete project settings","tags":["App Settings"]}},"/api/projects/{projectId}/settings/secret":{"get":{"description":"Console (JWT) only. API keys and project-secret callers receive 403.","operationId":"getSecret","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Project secret value","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ProjectSecretResponse"}}}},"403":{"description":"Called with an API key or the project secret"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get the decrypted project secret","tags":["App Settings"]}},"/api/projects/{projectId}/settings/debug-audio":{"patch":{"description":"Enable or disable debug audio storage for transcription debugging. WAV files are auto-deleted after 24 hours.","operationId":"updateDebugAudio","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDebugAudioRequest"}}}},"responses":{"200":{"description":"Debug audio setting updated"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Toggle debug audio recording","tags":["App Settings"]}},"/api/projects/{projectId}/settings/e2e-encryption":{"patch":{"description":"Set how the bot derives the ODIN cipher password. Mode `manual` requires a password (on this call or already stored); `disabled` and `automatic` clear any stored password.","operationId":"updateE2EEncryption","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateE2EEncryptionRequest"}}}},"responses":{"200":{"description":"E2E encryption setting updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSettingsResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update end-to-end encryption mode","tags":["App Settings"]}},"/api/projects/{projectId}/settings/transcription":{"patch":{"description":"Select which installed transcription plugin (audio plugin with the `transcribe` capability) transcribes this project, or `null` for the default Cortex transcription (billed per audio minute). Applies to running sessions within ~30 s.","operationId":"updateTranscription","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTranscriptionSettingsRequest"}}}},"responses":{"200":{"description":"Transcriber selected","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSettingsResponse"}}}},"400":{"description":"The instance is not an enabled transcription plugin of this project"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Select the transcription provider","tags":["App Settings"]}},"/api/projects/{projectId}/settings/announcements":{"patch":{"description":"Enable/disable the spoken recording announcement and set the text spoken when the bot joins and leaves. Text changes take effect on the next session.","operationId":"updateAnnouncements","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateAnnouncementsRequest"}}}},"responses":{"200":{"description":"Announcement settings updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AppSettingsResponse"}}}},"400":{"description":"An announcement text was blank"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update recording announcements","tags":["App Settings"]}},"/api/projects/{projectId}/settings/sync-secret":{"post":{"operationId":"syncSecret","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Secret synced successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretOperationResponse"}}}},"400":{"description":"No secret found in Payment system"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Sync project secret from Payment system","tags":["App Settings"]}},"/api/projects/{projectId}/settings/revoke-secret":{"delete":{"operationId":"revokeSecret","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Secret revoked successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SecretOperationResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Revoke the locally cached project secret","tags":["App Settings"]}},"/api/events/stream":{"get":{"description":"API keys need at least one event-bearing scope and receive only the event domains their scopes cover: `session.*`/`bot.*` → `sessions`, `message.*` → `messages`, `participant.*` → `participants`, `gathering.*` → `gatherings`, `sanction.*` → `sanctions` or `sanctions.read`, `webhook.*` → `webhooks`. Ingested ODIN Fleet events (`image.*`, `config.*`, `deployment.*`, `server.*`) need the reserved `fleet:events` scope and are therefore not delivered to API keys yet. Console (JWT) users receive every event.","operationId":"stream","parameters":[{"name":"types","required":false,"in":"query","description":"Comma-separated list of event types (supports prefix wildcards like \"gathering.*\")","schema":{"type":"string"}},{"name":"resources","required":false,"in":"query","description":"Comma-separated list of resource IDs","schema":{"type":"string"}},{"name":"last-event-id","required":false,"in":"header","description":"Optional ID of the last event received for reconnection support","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Subscribe to real-time events via SSE (Tenant-wide)","tags":["Events"]}},"/api/projects/{projectId}/events/stream":{"get":{"description":"API keys need at least one event-bearing scope and receive only the event domains their scopes cover: `session.*`/`bot.*` → `sessions`, `message.*` → `messages`, `participant.*` → `participants`, `gathering.*` → `gatherings`, `sanction.*` → `sanctions` or `sanctions.read`, `webhook.*` → `webhooks`. Ingested ODIN Fleet events (`image.*`, `config.*`, `deployment.*`, `server.*`) need the reserved `fleet:events` scope and are therefore not delivered to API keys yet. Console (JWT) users receive every event.","operationId":"streamProject","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID to filter events by","schema":{"type":"string"}},{"name":"types","required":false,"in":"query","description":"Comma-separated list of event types (supports prefix wildcards like \"gathering.*\")","schema":{"type":"string"}},{"name":"resources","required":false,"in":"query","description":"Comma-separated list of resource IDs","schema":{"type":"string"}},{"name":"last-event-id","required":false,"in":"header","description":"Optional ID of the last event received for reconnection support","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Subscribe to real-time events via SSE (Project-scoped)","tags":["Events"]}},"/api/events/schema":{"get":{"description":"Discriminated union (by `type`) of every DomainEvent emitted on the SSE streams. Not a live endpoint — events are delivered via text/event-stream on the /stream routes.","operationId":"getEventSchema","parameters":[],"responses":{"200":{"description":"Union of every domain event envelope emitted on the SSE streams (discriminated by `type`).","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/SessionCreatedEvent"},{"$ref":"#/components/schemas/SessionUpdatedEvent"},{"$ref":"#/components/schemas/SessionDeletedEvent"},{"$ref":"#/components/schemas/MessageCreatedEvent"},{"$ref":"#/components/schemas/WebhookDeliveryUpdatedEvent"},{"$ref":"#/components/schemas/ParticipantCreatedEvent"},{"$ref":"#/components/schemas/ParticipantUpdatedEvent"},{"$ref":"#/components/schemas/ParticipantDeletedEvent"},{"$ref":"#/components/schemas/MessageAnnotationCreatedEvent"},{"$ref":"#/components/schemas/SessionAnnotationCreatedEvent"},{"$ref":"#/components/schemas/SanctionCreatedEvent"},{"$ref":"#/components/schemas/SanctionUpdatedEvent"},{"$ref":"#/components/schemas/SanctionRevokedEvent"},{"$ref":"#/components/schemas/GatheringCreatedEvent"},{"$ref":"#/components/schemas/GatheringUpdatedEvent"},{"$ref":"#/components/schemas/GatheringStartedEvent"},{"$ref":"#/components/schemas/GatheringEndedEvent"},{"$ref":"#/components/schemas/GatheringCancelledEvent"},{"$ref":"#/components/schemas/GatheringMemberJoinedEvent"},{"$ref":"#/components/schemas/GatheringMemberReadyEvent"},{"$ref":"#/components/schemas/GatheringMemberLeftEvent"},{"$ref":"#/components/schemas/GatheringInvitationSentEvent"},{"$ref":"#/components/schemas/GatheringInvitationAcceptedEvent"},{"$ref":"#/components/schemas/GatheringInvitationDeclinedEvent"},{"$ref":"#/components/schemas/GenericDomainEvent"}]}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Documentation-only: shape of real-time domain events","tags":["Events"]}},"/api/events/ingest":{"post":{"description":"Accepts domain events from trusted ODIN platform services (Voice server, Fleet) and publishes them into the Cortex event pipeline. Requires HMAC-SHA256 project secret authentication via X-Project-Id, X-Signature, and X-Timestamp headers.","operationId":"ingest","parameters":[{"name":"X-Timestamp","in":"header","description":"ISO 8601 timestamp of when the request was signed (must be within 5 minutes)","required":true,"schema":{"type":"string"}},{"name":"X-Signature","in":"header","description":"HMAC-SHA256 hex digest of {timestamp}{rawBody} using the shared project secret","required":true,"schema":{"type":"string"}},{"name":"X-Project-Id","in":"header","description":"The Payment project/app ID identifying the target project","required":true,"schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IngestEventDto"}}}},"responses":{"201":{"description":"Event accepted and published to the event bus","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"The generated event UUID","example":"550e8400-e29b-41d4-a716-446655440000"}}}}}},"400":{"description":"Invalid request body (validation failed)"},"401":{"description":"Authentication failed (missing or invalid signature)"},"429":{"description":"Rate limit exceeded (max 100 events/s per project)"}},"summary":"Ingest an external domain event","tags":["Events"]}},"/api/projects/{projectId}/sanctions":{"post":{"description":"Apply a sanction (mute, kick, ban) to a user or participant of this project. Define duration and reason for the action.","operationId":"createSanction","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSanctionRequest"}}}},"responses":{"201":{"description":"Sanction created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SanctionResponse"}}}},"400":{"description":"Invalid request (missing participant or user ID, or participant of another project)"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Create a new sanction","tags":["Sanctions"]},"get":{"description":"Retrieve a paginated log of the sanctions issued in this project, with optional filtering.","operationId":"listSanctions","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"type","required":false,"in":"query","description":"Filter by sanction type","schema":{"type":"string","enum":["warn","mute","listen_only","text_only","rate_limit","shadow_mute","temp_ban","perm_ban","ranked_restriction","queue_delay","party_restriction","human_review"]}},{"name":"activeOnly","required":false,"in":"query","description":"Filter to only active sanctions","schema":{"example":true,"type":"boolean"}},{"name":"participantIds","required":false,"in":"query","description":"Only sanctions of these participants (comma-separated or repeated). Lets a game check a whole lobby in one request.","schema":{"example":["550e8400-e29b-41d4-a716-446655440000"],"type":"array","items":{"type":"string"}}},{"name":"externalUserId","required":false,"in":"query","description":"Only sanctions of this external user ID","schema":{"type":"string"}},{"name":"sessionId","required":false,"in":"query","description":"Only sanctions scoped to this session","schema":{"type":"string"}},{"name":"gatheringId","required":false,"in":"query","description":"Only sanctions issued in this gathering","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Pagination limit (max 200)","schema":{"example":50,"type":"number"}},{"name":"offset","required":false,"in":"query","description":"Pagination offset","schema":{"example":0,"type":"number"}}],"responses":{"200":{"description":"List of sanctions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SanctionListResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List sanctions of the project","tags":["Sanctions"]}},"/api/projects/{projectId}/sanctions/{id}":{"get":{"description":"Retrieve details of a specific sanction record.","operationId":"getSanction","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Sanction ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Sanction details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SanctionResponse"}}}},"404":{"description":"Sanction not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get a sanction by ID","tags":["Sanctions"]},"patch":{"description":"Update the details of an active sanction.","operationId":"updateSanction","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Sanction ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateSanctionRequest"}}}},"responses":{"200":{"description":"Sanction updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SanctionResponse"}}}},"404":{"description":"Sanction not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update a sanction","tags":["Sanctions"]}},"/api/projects/{projectId}/sanctions/{id}/revoke":{"post":{"description":"Prematurely end a sanction, effectively un-muting or un-banning the user.","operationId":"revokeSanction","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Sanction ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeSanctionRequest"}}}},"responses":{"200":{"description":"Sanction revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SanctionResponse"}}}},"404":{"description":"Sanction not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Revoke a sanction","tags":["Sanctions"]}},"/api/projects/{projectId}/sanctions/user/{externalUserId}":{"get":{"description":"Retrieve all sanctions (active and inactive) of a user in this project.","operationId":"getSanctionsByUser","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"externalUserId","required":true,"in":"path","description":"External user ID from ODIN","schema":{"type":"string"}}],"responses":{"200":{"description":"List of sanctions for user","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SanctionResponse"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get sanctions for a user","tags":["Sanctions"]}},"/api/projects/{projectId}/sanctions/participant/{participantId}":{"get":{"description":"Retrieve all sanctions of a participant of this project.","operationId":"getSanctionsByParticipant","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"participantId","required":true,"in":"path","description":"Participant ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of sanctions for participant","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SanctionResponse"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get sanctions for a participant","tags":["Sanctions"]}},"/api/projects/{projectId}/sanctions/user/{externalUserId}/active":{"get":{"description":"Retrieve only the currently active sanctions of a user in this project.","operationId":"getActiveSanctions","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"externalUserId","required":true,"in":"path","description":"External user ID from ODIN","schema":{"type":"string"}},{"name":"type","required":false,"in":"query","description":"Filter by specific sanction type","schema":{"enum":["warn","mute","listen_only","text_only","rate_limit","shadow_mute","temp_ban","perm_ban","ranked_restriction","queue_delay","party_restriction","human_review"],"type":"string"}}],"responses":{"200":{"description":"List of active sanctions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/SanctionResponse"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get active sanctions for a user","tags":["Sanctions"]}},"/api/projects/{projectId}/participants":{"get":{"description":"Retrieve a list of participants for a specific project. Supports pagination and search.","operationId":"listParticipants","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Pagination limit","schema":{"type":"number"}},{"name":"offset","required":false,"in":"query","description":"Pagination offset","schema":{"type":"number"}},{"name":"search","required":false,"in":"query","description":"Search by display name or user ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of participants","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantListResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List participants","tags":["Participants"]},"post":{"description":"Create a new participant or retrieve an existing one based on projectId and externalUserId.","operationId":"createParticipant","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateParticipantRequest"}}}},"responses":{"201":{"description":"Participant created or found","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Create or find a participant","tags":["Participants"]}},"/api/projects/{projectId}/participants/{id}":{"get":{"description":"Retrieve detailed information about a specific participant using their UUID.","operationId":"getParticipant","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Participant ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Participant details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantResponse"}}}},"404":{"description":"Participant not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get a participant by ID","tags":["Participants"]},"patch":{"description":"Update display name or user data for an existing participant.","operationId":"updateParticipant","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Participant ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateParticipantRequest"}}}},"responses":{"200":{"description":"Participant updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ParticipantResponse"}}}},"404":{"description":"Participant not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update a participant","tags":["Participants"]},"delete":{"description":"Erase a participant from the project. The participant's personal data (display name, email, user data and external user ID) is permanently scrubbed, and the participant stops appearing in any list or lookup -- subsequent requests for it return 404, and the external user ID becomes available again, so the same end user returns as a new participant.\n\nTheir activity is **not** deleted: message transcripts, session participation and gathering membership are retained in de-identified form, because they are also part of other participants' history. Sanctions are likewise retained and remain enforceable -- erasing a participant does not lift a ban. Use the sanction revoke endpoint for that.","operationId":"deleteParticipant","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Participant ID","schema":{"type":"string"}}],"responses":{"204":{"description":"Participant erased"},"404":{"description":"Participant not found, or already erased"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Delete a participant","tags":["Participants"]}},"/api/projects/{projectId}/gatherings":{"post":{"description":"Create a new gathering with the provided metadata. The gathering will be in CREATED status initially.","operationId":"createGathering","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateGatheringRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Create a new gathering","tags":["Gatherings"]},"get":{"description":"Retrieve a list of gatherings for the current tenant. Supports filtering by Project ID, Type, and Status.","operationId":"listGatherings","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"type","required":false,"in":"query","description":"Filter by gathering type","schema":{"enum":["lobby","appointment","open"],"type":"string"}},{"name":"status","required":false,"in":"query","description":"Filter by gathering status","schema":{"enum":["pending","active","started","ended","cancelled","expired"],"type":"string"}},{"name":"listed","required":false,"in":"query","description":"Filter to only listed gatherings","schema":{"type":"boolean"}},{"name":"limit","required":false,"in":"query","description":"Number of items to return","schema":{"example":50,"type":"number"}},{"name":"offset","required":false,"in":"query","description":"Number of items to skip","schema":{"example":0,"type":"number"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringListResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List gatherings for the tenant","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/{id}":{"get":{"description":"Retrieve detailed information about a specific gathering using its UUID. Optionally include the list of members.","operationId":"getGathering","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}},{"name":"includeMembers","required":false,"in":"query","description":"If true, includes the list of members in the response","schema":{"type":"boolean"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringResponse"}}}},"404":{"description":"Gathering not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get a gathering by ID","tags":["Gatherings"]},"patch":{"description":"Update the properties of an existing gathering. Only provided fields will be updated.","operationId":"updateGathering","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateGatheringRequest"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringResponse"}}}},"404":{"description":"Gathering not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update a gathering","tags":["Gatherings"]},"delete":{"description":"Permanently cancel a gathering. This action may notify invited members.","operationId":"cancelGathering","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}},{"name":"actorParticipantId","required":false,"in":"query","description":"Participant acting on behalf of the caller; must own the gathering","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringResponse"}}}},"404":{"description":"Gathering not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Cancel a gathering","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/{id}/start":{"post":{"description":"Transitions the gathering state to STARTED. If a roomId is provided, it associates the gathering with that active room.","operationId":"startGathering","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartGatheringRequest"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringResponse"}}}},"404":{"description":"Gathering not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Start a gathering","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/{id}/end":{"post":{"description":"Transitions the gathering state to ENDED. This signifies the gathering is over.","operationId":"endGathering","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringActorRequest"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringResponse"}}}},"404":{"description":"Gathering not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"End a gathering","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/join":{"post":{"description":"Allows a participant to join a gathering using an 8-character join code.","operationId":"joinByCode","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project UUID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinGatheringRequest"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinGatheringResponse"}}}},"404":{"description":"Invalid join code"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Join a gathering by code","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/lookup/{joinCode}":{"get":{"description":"Retrieves gathering details using its join code, without actually joining it. Useful for preview screens. The code is only resolved within the project in the path; a code of another project returns 404.","operationId":"lookupByCode","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"joinCode","required":true,"in":"path","description":"8-character join code (case-insensitive, hyphens ignored)","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringResponse"}}}},"401":{"description":"No valid credential"},"403":{"description":"Credential not allowed on this project or missing the gatherings scope"},"404":{"description":"The project has no gathering with this join code"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Look up a gathering by join code","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/{id}/members":{"post":{"description":"Manually add a participant to a gathering.","operationId":"addMember","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddMemberRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringMember"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Add a member to a gathering","tags":["Gatherings"]},"get":{"description":"Retrieve all members currently part of the gathering.","operationId":"listMembers","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/GatheringMember"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List members of a gathering","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/{id}/members/{memberId}":{"patch":{"description":"Update the status or properties of a specific member within the gathering.","operationId":"updateMember","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}},{"name":"memberId","required":true,"in":"path","description":"Member UUID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateMemberRequest"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringMember"}}}},"404":{"description":"Member not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update a member","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/{id}/members/{participantId}":{"delete":{"description":"Remove a participant from the gathering.","operationId":"removeMember","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}},{"name":"participantId","required":true,"in":"path","description":"Participant UUID","schema":{"type":"string"}},{"name":"actorParticipantId","required":false,"in":"query","description":"Participant acting on behalf of the caller; owners may remove anyone, participants may remove themselves","schema":{"type":"string"}}],"responses":{"204":{"description":""},"404":{"description":"Member not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Remove a member from a gathering","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/{id}/invite":{"post":{"description":"Send invitations to multiple participants at once. Generates invitation records.","operationId":"sendInvitations","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendInvitationsRequest"}}}},"responses":{"201":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InvitationResponse"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Send invitations to a gathering","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/{id}/invitations":{"get":{"description":"Retrieve all invitation records for a specific gathering, regardless of status.","operationId":"listGatheringInvitations","parameters":[{"name":"projectId","required":true,"in":"path","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InvitationResponse"}}}}},"404":{"description":"Gathering not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List invitations for a gathering","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/invitations/pending":{"get":{"description":"Retrieve all pending invitations for a specific participant.","operationId":"getPendingInvitations","parameters":[{"name":"participantId","required":true,"in":"query","description":"The participant UUID to check invitations for","schema":{"type":"string"}},{"name":"projectId","required":true,"in":"path","description":"Project UUID","schema":{}}],"responses":{"200":{"description":"","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/InvitationResponse"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get pending invitations","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/invitations/{invitationId}/accept":{"post":{"description":"Accept an invitation, which adds the participant to the gathering and updates the invitation status.","operationId":"acceptInvitation","parameters":[{"name":"invitationId","required":true,"in":"path","description":"Invitation UUID","schema":{"type":"string"}},{"name":"projectId","required":true,"in":"path","description":"Project UUID","schema":{}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AcceptInvitationRequest"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinGatheringResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Accept an invitation","tags":["Gatherings"]}},"/api/projects/{projectId}/gatherings/invitations/{invitationId}/decline":{"post":{"description":"Decline an invitation, marking it as DECLINED.","operationId":"declineInvitation","parameters":[{"name":"invitationId","required":true,"in":"path","description":"Invitation UUID","schema":{"type":"string"}},{"name":"projectId","required":true,"in":"path","description":"Project UUID","schema":{}}],"responses":{"204":{"description":""}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Decline an invitation","tags":["Gatherings"]}},"/api/projects/{projectId}/token":{"post":{"description":"Generate an access token for ODIN using the method configured in the project settings.","operationId":"generateProjectToken","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateProjectTokenRequest"}}}},"responses":{"201":{"description":"Token generated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GenerateTokenResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Generate ODIN Voice Token","tags":["Projects"]}},"/api/projects/{projectId}/functions/settings":{"get":{"description":"Retrieve Functions feature settings for the project.","operationId":"getSettings","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Function settings","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FunctionSettings"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get function settings","tags":["Functions"]},"put":{"description":"Update Functions feature settings for the project.","operationId":"updateSettings","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFunctionSettingsRequest"}}}},"responses":{"200":{"description":"Settings updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/FunctionSettings"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update function settings","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/verify-credentials":{"post":{"description":"Check if stored Fleet credentials are valid for Functions activation.","operationId":"verifyFleetCredentials","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{}}],"responses":{"200":{"description":"Credentials verification result","content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyCredentialsResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Verify Fleet credentials","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/resource-packages":{"get":{"description":"Get available Fleet resource packages for function deployment.","operationId":"getResourcePackages","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{}}],"responses":{"200":{"description":"Resource packages list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/ResourcePackage"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get resource packages","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/locations":{"get":{"description":"Get available Fleet deployment locations for function deployment.","operationId":"getDeploymentLocations","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{}}],"responses":{"200":{"description":"Locations list","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Location"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get deployment locations","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/activate":{"post":{"description":"Start Functions activation in the background. Poll GET /settings to track progress.","operationId":"activateFunctions","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivateFunctionsRequest"}}}},"responses":{"201":{"description":"Activation started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivateFunctionsResponse"}}}},"400":{"description":"Invalid request or already activating"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Activate Functions (async)","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/dependencies":{"put":{"description":"Update NPM package dependencies for all functions in the project. Changes trigger a runtime restart.","operationId":"updateDependencies","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDependenciesRequest"}}}},"responses":{"200":{"description":"Dependencies updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateDependenciesResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update project dependencies","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/env-vars":{"put":{"description":"Update environment variables for all functions in the project. Secrets are encrypted at rest. Changes trigger a runtime restart.","operationId":"updateEnvVars","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEnvVarsRequest"}}}},"responses":{"200":{"description":"Environment variables updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateEnvVarsResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update project environment variables","tags":["Functions"]}},"/api/projects/{projectId}/functions/npm-package/{packageName}":{"get":{"description":"Fetch available versions for an npm package. Used for dependency version selection.","operationId":"getNpmPackageInfo","parameters":[{"name":"packageName","required":true,"in":"path","description":"NPM package name (e.g. \"lodash\" or \"@scope/package\")","schema":{"type":"string"}},{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{}}],"responses":{"200":{"description":"Package version info","content":{"application/json":{"schema":{"$ref":"#/components/schemas/NpmPackageInfo"}}}},"404":{"description":"Package not found on npm"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Lookup npm package versions","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/runtime-status":{"get":{"description":"Get the status of the Functions runtime (Fleet deployment).","operationId":"getRuntimeStatus","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Runtime status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuntimeStatusResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get runtime status","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/runtime/start":{"post":{"description":"Start the Functions runtime (Fleet deployment servers).","operationId":"startRuntime","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Runtime started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartRuntimeResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Start runtime","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/runtime/stop":{"post":{"description":"Stop the Functions runtime (Fleet deployment servers).","operationId":"stopRuntime","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Runtime stopped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StopRuntimeResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Stop runtime","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/runtime/restart":{"post":{"description":"Restart the Functions runtime (stops then starts all servers).","operationId":"restartRuntime","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Runtime restarting","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StartRuntimeResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Restart runtime","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/runtime/versions":{"get":{"description":"List the available function-runtime versions (with release notes and breaking flags) and the version this project is currently pinned to.","operationId":"getRuntimeVersions","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Runtime versions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuntimeVersionsResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List runtime versions","tags":["Functions"]}},"/api/projects/{projectId}/functions/settings/runtime/upgrade":{"post":{"description":"Pin the project to a specific runtime version: swaps the Fleet binary image and restarts the runtime so the new image is pulled.","operationId":"upgradeRuntime","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpgradeRuntimeRequest"}}}},"responses":{"200":{"description":"Runtime upgrading","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RuntimeStatusResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Upgrade runtime version","tags":["Functions"]}},"/api/projects/{projectId}/functions":{"get":{"description":"Retrieve all functions for the project.","operationId":"listFunctions","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of functions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/Function"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List functions","tags":["Functions"]},"post":{"description":"Create a new serverless function.","operationId":"createFunction","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateFunctionRequest"}}}},"responses":{"201":{"description":"Function created","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Function"}}}},"400":{"description":"Invalid request or limit exceeded"},"409":{"description":"Function with this slug already exists"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Create function","tags":["Functions"]}},"/api/projects/{projectId}/functions/{functionId}":{"get":{"description":"Retrieve a specific function by ID.","operationId":"getFunction","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"functionId","required":true,"in":"path","description":"Function ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Function details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Function"}}}},"404":{"description":"Function not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Get function","tags":["Functions"]},"put":{"description":"Update function draft code and configuration.","operationId":"updateFunction","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"functionId","required":true,"in":"path","description":"Function ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateFunctionRequest"}}}},"responses":{"200":{"description":"Function updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Function"}}}},"404":{"description":"Function not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Update function","tags":["Functions"]},"delete":{"description":"Delete a function and all its versions and deployments.","operationId":"deleteFunction","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"functionId","required":true,"in":"path","description":"Function ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Function deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteFunctionResponse"}}}},"404":{"description":"Function not found"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Delete function","tags":["Functions"]}},"/api/projects/{projectId}/functions/{functionId}/versions":{"get":{"description":"Retrieve all published versions of a function.","operationId":"listVersions","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"functionId","required":true,"in":"path","description":"Function ID","schema":{"type":"string"}}],"responses":{"200":{"description":"List of versions","content":{"application/json":{"schema":{"type":"array","items":{"$ref":"#/components/schemas/FunctionVersion"}}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List function versions","tags":["Functions"]}},"/api/projects/{projectId}/functions/{functionId}/publish":{"post":{"description":"Publish the current draft as a new immutable version and deploy to the shared runtime.","operationId":"publishVersion","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"functionId","required":true,"in":"path","description":"Function ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishFunctionRequest"}}}},"responses":{"201":{"description":"Version published","content":{"application/json":{"schema":{"$ref":"#/components/schemas/PublishFunctionResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Publish function version","tags":["Functions"]}},"/api/projects/{projectId}/functions/{functionId}/deploy":{"post":{"description":"Deploy the latest published version to Fleet.","operationId":"deployFunction","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"functionId","required":true,"in":"path","description":"Function ID","schema":{"type":"string"}}],"responses":{"201":{"description":"Deployment started","content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeployFunctionResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Deploy function","tags":["Functions"]}},"/api/projects/{projectId}/functions/{functionId}/stop":{"post":{"description":"Stop the active deployment of a function.","operationId":"stopDeployment","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"functionId","required":true,"in":"path","description":"Function ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Function stopped","content":{"application/json":{"schema":{"$ref":"#/components/schemas/StopFunctionResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Stop function","tags":["Functions"]}},"/api/projects/{projectId}/participants/token":{"post":{"description":"Mint a short-lived, Ed25519-signed token for one player of this project. The client sends it as `Authorization: Bearer <token>` to the `/client/v1/*` routes, where it may only read its own data. The participant is created on first use. Pass `roomId` to receive the ODIN voice token in the same response. Verify tokens elsewhere with the public keys from `GET /api/.well-known/jwks.json`. Join gate: an active `temp_ban`/`perm_ban` answers 403 with the ban in the body; an active `mute`, `listen_only` or `text_only` tags the ODIN token `cortex:muted`, and ODIN tokens live 300 s. Tags and lifetime need the `access_key` token provider — `odinTokenRestrictions.applied` says whether they are in.","operationId":"issueParticipantToken","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueParticipantTokenRequest"}}}},"responses":{"201":{"description":"Token issued","content":{"application/json":{"schema":{"$ref":"#/components/schemas/IssueParticipantTokenResponse"}}}},"403":{"description":"Either the credential may not mint participant tokens for this project, or the player has an active `temp_ban`/`perm_ban` — then the body carries the ban (`JoinRefusedResponse`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JoinRefusedResponse"}}}},"503":{"description":"This deployment has no participant token signing key configured"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Issue a participant token","tags":["Participants"]}},"/api/projects/{projectId}/participants/token/revoke-all":{"post":{"description":"Invalidate every participant token issued for this project by raising its token epoch. Clients must fetch a new token. Effective on every API instance within 30 seconds (the epoch cache TTL).","operationId":"revokeAllParticipantTokens","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}}],"responses":{"200":{"description":"Tokens revoked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevokeParticipantTokensResponse"}}}},"403":{"description":"The credential may not manage participant tokens for this project"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Revoke all participant tokens of a project","tags":["Participants"]}},"/api/.well-known/jwks.json":{"get":{"description":"The Ed25519 public keys that participant tokens are signed with, as a JWKS document. Public, unauthenticated and cacheable — match a token's `kid` header against `keys[].kid` to verify it without calling Cortex. Every key that still verifies is listed, so a key rotation needs no coordination with verifiers.","operationId":"getJwks","parameters":[],"responses":{"200":{"description":"The key set (empty when this deployment issues no participant tokens)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Jwks"}}}}},"summary":"Public keys for participant tokens (JWKS)","tags":["Auth"]}},"/api/projects/{projectId}/gatherings/{id}/sanctions":{"get":{"description":"Retrieve the sanctions issued in the context of this gathering, with the same filters as the project-wide list. Pass `activeOnly=true` for the ones currently in force.","operationId":"listGatheringSanctions","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"id","required":true,"in":"path","description":"Gathering UUID","schema":{"type":"string"}},{"name":"type","required":false,"in":"query","description":"Filter by sanction type","schema":{"type":"string","enum":["warn","mute","listen_only","text_only","rate_limit","shadow_mute","temp_ban","perm_ban","ranked_restriction","queue_delay","party_restriction","human_review"]}},{"name":"activeOnly","required":false,"in":"query","description":"Filter to only active sanctions","schema":{"example":true,"type":"boolean"}},{"name":"participantIds","required":false,"in":"query","description":"Only sanctions of these participants (comma-separated or repeated). Lets a game check a whole lobby in one request.","schema":{"example":["550e8400-e29b-41d4-a716-446655440000"],"type":"array","items":{"type":"string"}}},{"name":"externalUserId","required":false,"in":"query","description":"Only sanctions of this external user ID","schema":{"type":"string"}},{"name":"sessionId","required":false,"in":"query","description":"Only sanctions scoped to this session","schema":{"type":"string"}},{"name":"gatheringId","required":false,"in":"query","description":"Only sanctions issued in this gathering","schema":{"type":"string"}},{"name":"limit","required":false,"in":"query","description":"Pagination limit (max 200)","schema":{"example":50,"type":"number"}},{"name":"offset","required":false,"in":"query","description":"Pagination offset","schema":{"example":0,"type":"number"}}],"responses":{"200":{"description":"Sanctions of the gathering","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SanctionListResponse"}}}}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"List sanctions of a gathering","tags":["Sanctions"]}},"/api/client/v1/me":{"get":{"description":"Return the identity and capabilities of the participant token used for this request, together with the participant record it points at. Requires the `me` scope.","operationId":"getClientIdentity","parameters":[],"responses":{"200":{"description":"The token holder","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientIdentity"}}}},"401":{"description":"Missing, expired or revoked participant token"}},"security":[{"bearer":[]}],"summary":"Who am I","tags":["Client"]}},"/api/client/v1/sessions/{id}/messages":{"get":{"description":"The participant-token mirror of `GET /projects/{projectId}/sessions/{id}/messages`, with the same cursor, `include` and `ETag`/`304` contract. The session must belong to the token's project, and — when the token is bound to a gathering — must be the session that gathering started. Requires the `messages.read` scope.","operationId":"getClientSessionMessages","parameters":[{"name":"id","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}},{"name":"after","required":false,"in":"query","description":"Cursor: only messages with a `seq` greater than this. Pass the `nextCursor` (or `latestSeq`) of the previous response. 0 or omitted starts at the beginning.","schema":{"minimum":0,"example":120,"type":"number"}},{"name":"limit","required":false,"in":"query","description":"Page size (1–200). Defaults to 50.","schema":{"minimum":1,"maximum":200,"default":50,"example":50,"type":"number"}},{"name":"order","required":false,"in":"query","description":"`asc` (default) walks the transcript forward from `after`; `desc` returns the newest page first (no continuation cursor).","schema":{"default":"asc","type":"string","enum":["asc","desc"]}},{"name":"include","required":false,"in":"query","description":"Extras to include per message, comma-separated or repeated: `annotations` (plugin annotations) and/or `debugAudio` (the `hasDebugAudio` flag). Each costs one extra query; omit what you do not need.","schema":{"example":["annotations"],"type":"array","items":{"type":"string","enum":["annotations","debugAudio"]}}},{"name":"if-none-match","required":false,"in":"header","description":"The `ETag` (newest `seq`, quoted) of a previous response. Answered with 304 when the session has no newer messages.","schema":{"type":"string"}}],"responses":{"200":{"description":"The transcript: a plain `Message[]` without query parameters, a `MessageListResponse` page otherwise","headers":{"ETag":{"description":"Newest `seq` of the session, quoted (omitted when `annotations` are included)","schema":{"type":"string","example":"\"180\""}}},"content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/MessageListResponse"},{"type":"array","items":{"$ref":"#/components/schemas/Message"}}]}}}},"304":{"description":"Not modified: no messages newer than the `If-None-Match` value"},"401":{"description":"Missing, expired or revoked participant token"},"404":{"description":"No such session, or not one this token may read"}},"security":[{"bearer":[]}],"summary":"Get messages of a session (client)","tags":["Client"]}},"/api/client/v1/sanctions":{"get":{"description":"Return the currently active sanctions of the participant this token belongs to, plus the two questions a client usually asks: whether it may speak and whether it is banned. Requires the `sanctions.read` scope.","operationId":"getClientSanctions","parameters":[],"responses":{"200":{"description":"Own active sanctions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClientSanctionList"}}}},"401":{"description":"Missing, expired or revoked participant token"}},"security":[{"bearer":[]}],"summary":"Get my active sanctions","tags":["Client"]}},"/api/client/v1/gatherings/{id}":{"get":{"description":"Return the gathering this participant token is bound to, including its members. Only the gathering in the token's `gth` claim is accessible, and only while the holder is still a member. Requires the `gatherings.read` scope.","operationId":"getClientGathering","parameters":[{"name":"id","required":true,"in":"path","description":"Gathering UUID — must be the one the token is bound to","schema":{"type":"string"}}],"responses":{"200":{"description":"The gathering","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringResponse"}}}},"401":{"description":"Missing, expired or revoked participant token"},"404":{"description":"Not the gathering this token is bound to, or no longer a member"}},"security":[{"bearer":[]}],"summary":"Get my gathering (client)","tags":["Client"]}},"/api/client/v1/gatherings/{id}/ready":{"post":{"description":"Set the calling participant's member status in its own gathering to `ready`, emitting `gathering.member.ready`. Requires the `gatherings.ready` scope.","operationId":"markClientGatheringReady","parameters":[{"name":"id","required":true,"in":"path","description":"Gathering UUID — must be the one the token is bound to","schema":{"type":"string"}}],"responses":{"200":{"description":"The updated member","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GatheringMember"}}}},"401":{"description":"Missing, expired or revoked participant token"},"404":{"description":"Not the gathering this token is bound to, or no longer a member"}},"security":[{"bearer":[]}],"summary":"Mark myself ready (client)","tags":["Client"]}},"/api/projects/{projectId}/sessions/{sessionId}/room-messages":{"post":{"description":"Push a custom message to the players of a live session. The transcription bot sends it into the ODIN room as a Cortex Room Protocol frame (bytes `0x43 0x58` \"CX\", a version byte, then JSON `{ type, id, seq, data }`); clients receive it through `MessageReceived` from the bot peer. Pass `targetExternalUserIds` to reach only some players. Limits: 8 KB per encoded frame, 20 messages per second per room. Requires a bot in the room (an active session). Delivery is best effort in real time — for state a client must not miss, keep an authoritative copy it can fetch after a reconnect.","operationId":"sendRoomMessage","parameters":[{"name":"projectId","required":true,"in":"path","description":"Project ID","schema":{"type":"string"}},{"name":"sessionId","required":true,"in":"path","description":"Session ID","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendRoomMessageRequest"}}}},"responses":{"202":{"description":"Handed to the bot of the session","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendRoomMessageResponse"}}}},"400":{"description":"Invalid type or payload (e.g. a reserved `cortex.` type)"},"403":{"description":"The credential lacks the `rooms.push` scope or access to this project"},"404":{"description":"Session not found in this project"},"409":{"description":"The session is not active, or no bot is connected to its room"},"413":{"description":"The encoded frame exceeds 8 KB"},"429":{"description":"The room exceeded 20 messages per second; see `Retry-After`"}},"security":[{"api-key":[]},{"bearer":[]}],"summary":"Send a message into the ODIN room of a session","tags":["Room Messages"]}}},"info":{"title":"ODIN Cortex API","description":"API for ODIN Cortex","version":"1.0","contact":{}},"tags":[{"name":"Auth","description":"Authentication endpoints"},{"name":"Sessions","description":"Session management endpoints"},{"name":"Messages","description":"Message retrieval endpoints"},{"name":"Webhooks","description":"Webhook subscription management"},{"name":"Health","description":"Health check endpoints"},{"name":"Client","description":"Participant-token routes a game client may call directly (/client/v1)"}],"servers":[],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"},"api-key":{"type":"apiKey","in":"header","name":"X-API-Key"}},"schemas":{"LoginRequest":{"type":"object","properties":{"fusionSessionId":{"type":"string","description":"Fusion Session ID for authentication","example":"fsid_1234567890"}},"required":["fusionSessionId"]},"LoginResponse":{"type":"object","properties":{"accessToken":{"type":"string","description":"JWT access token","example":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."},"sessionId":{"type":"string","description":"Fusion Session ID for Fleet and Payment SDK authentication. Only returned from the credentials login flow.","example":"abc123def456"}},"required":["accessToken"]},"LoginCredentials":{"type":"object","properties":{"email":{"type":"string","description":"User email address","example":"user@example.com"},"password":{"type":"string","description":"User password","example":"password123","minLength":6}},"required":["email","password"]},"UserProfile":{"type":"object","properties":{"userId":{"type":"string","description":"User ID","example":"user_123"},"tenantId":{"type":"string","description":"Tenant ID","example":"tenant_456"},"username":{"type":"string","description":"User name","example":"John Doe"}},"required":["userId","tenantId","username"]},"SessionPosition":{"type":"object","properties":{"x":{"type":"number","description":"X coordinate, in the same coordinate system the room's clients pass to `room.setPosition`","example":0.5},"y":{"type":"number","description":"Y coordinate, in the same coordinate system the room's clients pass to `room.setPosition`","example":0.25},"z":{"type":"number","description":"Z coordinate, in the same coordinate system the room's clients pass to `room.setPosition`","example":0}},"required":["x","y","z"]},"SessionResponse":{"type":"object","properties":{"id":{"type":"string","description":"Session ID","example":"sess_789"},"tenantId":{"type":"string","description":"Tenant ID","example":"tenant_456"},"externalRoomId":{"type":"string","description":"External Room ID","example":"room_abc123"},"projectId":{"type":"string","description":"Project ID","example":"proj_123"},"gateway":{"type":"string","description":"ODIN Gateway URL","example":"gateway.odin.4players.io"},"title":{"type":"string","description":"Session Title","example":"Daily Standup"},"createdByUserId":{"type":"string","description":"ID of the user who created the session","example":"user_123"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp","example":"2023-10-01T10:00:00Z"},"status":{"type":"string","description":"Session Status","example":"active"},"endedAt":{"format":"date-time","type":"string","description":"When the session was stopped. Set the first time the session goes to `inactive` (manual stop, the owning gathering ending or being cancelled, or the bot idle timeout) and cleared again if the session is restarted. Null while the session is running, and for sessions created with `autoStart: false` that were never started.","example":"2023-10-01T11:30:00Z","nullable":true},"idleTimeout":{"type":"number","description":"Idle timeout in seconds. 0 or null means no auto-stop.","example":300},"position":{"description":"Position the bot peer joins the ODIN room at. Null means the origin.","nullable":true,"type":"object","allOf":[{"$ref":"#/components/schemas/SessionPosition"}]}},"required":["id","tenantId","externalRoomId","projectId","gateway","title","createdByUserId","createdAt","status"]},"SessionListResponse":{"type":"object","properties":{"sessions":{"description":"One page of sessions, newest first","type":"array","items":{"$ref":"#/components/schemas/SessionResponse"}},"nextCursor":{"type":"string","description":"Opaque cursor for the next page (`cursor=<nextCursor>`), or null when this page is the last one","nullable":true}},"required":["sessions","nextCursor"]},"CreateSessionRequest":{"type":"object","properties":{"title":{"type":"string","description":"Title of the session","example":"Daily Standup"},"externalRoomId":{"type":"string","description":"External Room ID","example":"room_abc123"},"gateway":{"type":"string","description":"ODIN Gateway URL","example":"gateway.odin.4players.io"},"idleTimeout":{"type":"number","description":"Idle timeout in seconds. If > 0, session auto-stops after this duration with no participants. 0 or omitted means no auto-stop.","example":300},"autoStart":{"type":"boolean","description":"Whether to automatically start the bot after session creation. Defaults to true when omitted; set to false to disable.","example":true},"position":{"description":"Position the bot peer joins the ODIN room at. ODIN only delivers voice between peers that are close to each other, so set this near the participants if your clients use `room.setPosition` — a bot at the origin may otherwise hear nobody. Stored on the session and reused on restart. Omitted means the origin. Only supported for projects on ODIN SDK v1 (rejected with 400 for v2).","allOf":[{"$ref":"#/components/schemas/SessionPosition"}]},"e2eEncryptionPassword":{"type":"string","description":"ODIN end-to-end encryption (cipher) password of the room. When set, the bot joins with an ODIN cipher using this password instead of the project's `e2eEncryptionMode`, so it can decrypt the room's audio, messages and peer user data. Used verbatim as UTF-8 bytes — pass exactly the string the room's clients give their cipher. Stored encrypted for the lifetime of the run (bot restarts and recovery reuse it) and deleted when the session stops. Write-only: never returned and never included in events or webhooks.","example":"correct-horse-battery-staple","maxLength":1024,"writeOnly":true}},"required":["title","externalRoomId"]},"StartSessionRequest":{"type":"object","properties":{"e2eEncryptionPassword":{"type":"string","description":"ODIN end-to-end encryption (cipher) password of the room. When set, the bot joins with an ODIN cipher using this password instead of the project's `e2eEncryptionMode`, so it can decrypt the room's audio, messages and peer user data. Used verbatim as UTF-8 bytes — pass exactly the string the room's clients give their cipher. Stored encrypted for the lifetime of the run (bot restarts and recovery reuse it) and deleted when the session stops. Write-only: never returned and never included in events or webhooks.","example":"correct-horse-battery-staple","maxLength":1024,"writeOnly":true}}},"StartSessionResponse":{"type":"object","properties":{"message":{"type":"string","description":"Status message","example":"Bot start command sent"}},"required":["message"]},"StopSessionResponse":{"type":"object","properties":{"message":{"type":"string","description":"Status message","example":"Bot stop command sent"}},"required":["message"]},"DeleteSessionsRequest":{"type":"object","properties":{"sessionIds":{"description":"IDs of the sessions to delete (1–100, no duplicates).","minItems":1,"maxItems":100,"example":["3f1c2b7e-8d4a-4c1e-9b2f-1a2b3c4d5e6f"],"type":"array","items":{"type":"string","format":"uuid"}}},"required":["sessionIds"]},"DeleteSessionsResponse":{"type":"object","properties":{"deleted":{"description":"IDs of the sessions that were deleted","type":"array","items":{"type":"string"}},"notFound":{"description":"IDs that do not exist in this project, including ones already deleted","type":"array","items":{"type":"string"}},"failed":{"description":"IDs whose deletion failed and was rolled back completely (e.g. the session was starting). Safe to retry.","type":"array","items":{"type":"string"}}},"required":["deleted","notFound","failed"]},"SessionAction":{"type":"object","properties":{"id":{"type":"string","description":"Action ID (unique within the plugin)","example":"generate-minutes"},"label":{"type":"string","description":"Human-readable action label","example":"Generate Minutes"},"icon":{"type":"string","description":"Heroicons icon name","example":"heroicons_outline:document-text"},"endpointPath":{"type":"string","description":"Endpoint path on the plugin, relative to the plugin slug","example":"minutes"},"method":{"type":"string","description":"HTTP method to invoke the action with","example":"POST"},"type":{"type":"string","description":"How the result is handled by the client","example":"download"}},"required":["id","label","endpointPath","method","type"]},"PluginActionGroup":{"type":"object","properties":{"pluginSlug":{"type":"string","description":"Plugin slug","example":"session-summary"},"pluginName":{"type":"string","description":"Plugin display name","example":"Session Summary"},"pluginIcon":{"type":"string","description":"Plugin icon (Heroicons name)"},"instanceId":{"type":"string","description":"Plugin instance ID"},"actions":{"description":"Available actions for this plugin","type":"array","items":{"$ref":"#/components/schemas/SessionAction"}}},"required":["pluginSlug","pluginName","instanceId","actions"]},"SessionActionsResponse":{"type":"object","properties":{"sessionId":{"type":"string","description":"Session ID the actions apply to"},"plugins":{"description":"Plugins (with their actions) available for this session","type":"array","items":{"$ref":"#/components/schemas/PluginActionGroup"}}},"required":["sessionId","plugins"]},"MessageAnnotation":{"type":"object","properties":{"id":{"type":"string","description":"Annotation UUID"},"tenantId":{"type":"string","description":"Tenant ID"},"pluginInstanceId":{"type":"string","description":"Plugin instance that created this"},"type":{"type":"string","description":"Annotation type","enum":["profanity","summary","action_items","sentiment"]},"data":{"type":"object","additionalProperties":true,"description":"Annotation data"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"messageId":{"type":"string","description":"Message ID this annotation is attached to"}},"required":["id","tenantId","type","data","createdAt","messageId"]},"Message":{"type":"object","properties":{"id":{"type":"string","description":"Message ID","example":"msg_123"},"sessionId":{"type":"string","description":"Session ID","example":"sess_789"},"seq":{"type":"number","description":"Per-session sequence number, starting at 1 and gap-free. Use it as the `after` cursor of the messages route and as the value of `If-None-Match` when polling.","example":42},"type":{"type":"string","description":"Message type: user (transcribed speech) or system (join/leave events)","enum":["user","system"],"default":"user","example":"user"},"participantId":{"type":"string","description":"Participant who spoke (null for system messages)","nullable":true},"externalUserId":{"type":"string","description":"External user ID of the speaker from ODIN (null for system messages)","nullable":true},"senderName":{"type":"string","description":"Sender name (or [System] for system messages)","example":"Alice"},"content":{"type":"string","description":"Message content","example":"Hello team!"},"timestamp":{"format":"date-time","type":"string","description":"Timestamp","example":"2023-10-01T10:05:00Z"},"hasDebugAudio":{"type":"boolean","description":"Whether a debug audio WAV file is available for download. Only computed when the request includes `debugAudio` (or on the legacy full-list read); otherwise omitted.","default":false},"annotations":{"description":"Plugin annotations of this message. Only present when the request includes `annotations`.","type":"array","items":{"$ref":"#/components/schemas/MessageAnnotation"}}},"required":["id","sessionId","seq","senderName","content","timestamp"]},"MessageListResponse":{"type":"object","properties":{"messages":{"description":"One page of messages in the requested order","type":"array","items":{"$ref":"#/components/schemas/Message"}},"nextCursor":{"type":"number","description":"Cursor for the next page (`after=<nextCursor>`), or null when this page reached the end in the requested order. Always null for `order=desc`.","nullable":true,"example":170},"latestSeq":{"type":"number","description":"Newest `seq` of the session at the time of the request (0 while it has no messages). Send it back as `If-None-Match: \"<latestSeq>\"` to get a 304 when nothing changed.","example":180}},"required":["messages","nextCursor","latestSeq"]},"CreateWebhookSubscriptionRequest":{"type":"object","properties":{"url":{"type":"string","description":"Webhook URL","example":"https://example.com/webhook"},"secret":{"type":"string","description":"Secret for HMAC-SHA256 signature verification","example":"my_secret_key"},"events":{"description":"List of events to subscribe to. Supports wildcards like \"message.*\" or \"*\"","example":["session.created","message.*"],"type":"array","items":{"type":"string"}},"description":{"type":"string","description":"Description of this webhook","example":"Production webhook endpoint"},"authType":{"type":"string","description":"How Cortex authenticates when POSTing to your webhook URL. Defaults to 'none' if omitted.","enum":["none","api_key","basic_auth","custom_headers"]},"apiKey":{"type":"string","description":"API key value (for api_key auth)","example":"sk_live_xxx"},"apiKeyHeader":{"type":"string","description":"Header name to send the API key in (for api_key auth). Defaults to 'X-API-Key' if omitted.","example":"X-API-Key"},"basicAuthUsername":{"type":"string","description":"Basic auth username"},"basicAuthPassword":{"type":"string","description":"Basic auth password"},"customHeaders":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom headers to send with webhook requests","example":{"X-Custom-Header":"value"}}},"required":["url","secret","events"]},"WebhookSubscription":{"type":"object","properties":{"id":{"type":"string","description":"Subscription ID"},"tenantId":{"type":"string","description":"Tenant ID"},"projectId":{"type":"string","description":"Project/App ID"},"url":{"type":"string","description":"Webhook URL"},"events":{"description":"Subscribed events","type":"array","items":{"type":"string"}},"isActive":{"type":"boolean","description":"Whether the subscription is active"},"description":{"type":"string","description":"Description"},"authType":{"type":"string","description":"Authentication type","enum":["none","api_key","basic_auth","custom_headers"]},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"}},"required":["id","tenantId","projectId","url","events","isActive","authType","createdAt","updatedAt"]},"WebhookEvent":{"type":"object","properties":{"id":{"type":"string","description":"Event ID"},"tenantId":{"type":"string","description":"Tenant ID"},"projectId":{"type":"string","description":"Project/App ID"},"eventType":{"type":"string","description":"Event type","example":"message.created"},"payload":{"type":"object","additionalProperties":true,"description":"Event payload"},"status":{"type":"string","description":"Delivery status","enum":["pending","delivered","failed"]},"attemptsCount":{"type":"number","description":"Number of delivery attempts"},"lastError":{"type":"string","description":"Last error message"},"nextAttemptAt":{"format":"date-time","type":"string","description":"Next delivery attempt time"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"}},"required":["id","tenantId","eventType","payload","status","attemptsCount","nextAttemptAt","createdAt"]},"WebhookDelivery":{"type":"object","properties":{"id":{"type":"string","description":"Delivery ID"},"webhookEventId":{"type":"string","description":"Webhook Event ID"},"subscriptionId":{"type":"string","description":"Subscription ID"},"statusCode":{"type":"number","description":"HTTP status code"},"responseBody":{"type":"string","description":"Response body"},"responseHeaders":{"type":"object","additionalProperties":{"type":"string"},"description":"Response headers"},"requestBody":{"type":"string","description":"Request body sent"},"createdAt":{"format":"date-time","type":"string","description":"Delivery timestamp"}},"required":["id","webhookEventId","subscriptionId","createdAt"]},"WebhookEventDetail":{"type":"object","properties":{"id":{"type":"string","description":"Event ID"},"tenantId":{"type":"string","description":"Tenant ID"},"projectId":{"type":"string","description":"Project/App ID"},"eventType":{"type":"string","description":"Event type","example":"message.created"},"payload":{"type":"object","additionalProperties":true,"description":"Event payload"},"status":{"type":"string","description":"Delivery status","enum":["pending","delivered","failed"]},"attemptsCount":{"type":"number","description":"Number of delivery attempts"},"lastError":{"type":"string","description":"Last error message"},"nextAttemptAt":{"format":"date-time","type":"string","description":"Next delivery attempt time"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"deliveries":{"description":"Delivery attempts","type":"array","items":{"$ref":"#/components/schemas/WebhookDelivery"}}},"required":["id","tenantId","eventType","payload","status","attemptsCount","nextAttemptAt","createdAt","deliveries"]},"UpdateWebhookSubscriptionRequest":{"type":"object","properties":{"url":{"type":"string","description":"Webhook URL","example":"https://example.com/webhook"},"secret":{"type":"string","description":"Secret for HMAC-SHA256 signature verification"},"events":{"description":"List of events to subscribe to","type":"array","items":{"type":"string"}},"description":{"type":"string","description":"Description of this webhook"},"isActive":{"type":"boolean","description":"Whether the subscription is active"},"authType":{"type":"string","description":"Authentication type","enum":["none","api_key","basic_auth","custom_headers"]},"apiKey":{"type":"string","description":"API key value"},"apiKeyHeader":{"type":"string","description":"API key header name"},"basicAuthUsername":{"type":"string","description":"Basic auth username"},"basicAuthPassword":{"type":"string","description":"Basic auth password"},"customHeaders":{"type":"object","additionalProperties":{"type":"string"},"description":"Custom headers"}}},"DeleteWebhookSubscriptionResponse":{"type":"object","properties":{"message":{"type":"string","description":"Status message","example":"Subscription deleted"}},"required":["message"]},"PluginSettingField":{"type":"object","properties":{"key":{"type":"string","description":"Unique key for this setting","example":"apiKey"},"type":{"type":"string","description":"Input type","enum":["text","number","email","select","boolean","password","chips"],"example":"password"},"label":{"type":"string","description":"Human-readable label","example":"OpenAI API Key"},"description":{"type":"string","description":"Help text","example":"Your API key from OpenAI dashboard"},"required":{"type":"boolean","description":"Whether this setting is required","default":false},"default":{"oneOf":[{"type":"string"},{"type":"number"},{"type":"boolean"},{"type":"array","items":{"type":"string"}}],"description":"Default value"},"options":{"description":"Options for select type","type":"array","items":{"type":"string"}},"validation":{"type":"object","properties":{"min":{"type":"number"},"max":{"type":"number"},"pattern":{"type":"string"}},"description":"Validation rules"}},"required":["key","type","label"]},"Plugin":{"type":"object","properties":{"id":{"type":"string","description":"Plugin UUID"},"slug":{"type":"string","description":"URL-friendly identifier","example":"profanity-filter"},"name":{"type":"string","description":"Display name","example":"Profanity Filter"},"description":{"type":"string","description":"Description"},"version":{"type":"string","description":"Semantic version","example":"1.0.0"},"category":{"type":"string","description":"Category (UI grouping)","enum":["moderation","analytics","automation","integration","transcription"]},"kind":{"type":"string","description":"How the plugin runs: `event` = reacts to domain events; `audio` = processes utterance audio in the bot (transcription providers, audio moderation)","enum":["event","audio"],"example":"event"},"capabilities":{"type":"array","description":"Capabilities of an `audio` plugin (empty for `event` plugins). A `transcribe` plugin can be selected as the project transcriber via PATCH /projects/{projectId}/settings/transcription.","example":[],"items":{"type":"string","enum":["transcribe","analyze"]}},"events":{"description":"Event patterns","example":["message.created"],"type":"array","items":{"type":"string"}},"outputAnnotationType":{"type":"string","description":"Output annotation type","enum":["profanity","summary","action_items","sentiment"]},"settingsSchema":{"description":"Settings schema","type":"array","items":{"$ref":"#/components/schemas/PluginSettingField"}},"productSlug":{"type":"string","description":"Payment product slug this plugin activates (set = paid plugin). When present, installing/enabling the plugin activates this product and disabling/uninstalling deactivates it.","example":"amanda"},"pricingUrl":{"type":"string","description":"Pricing page URL for the tied Payment product, shown in the install cost-acceptance notice.","example":"https://odin.4players.io/pricing"},"icon":{"type":"string","description":"Heroicons icon name","example":"heroicons_outline:shield-check"},"isActive":{"type":"boolean","description":"Whether plugin is available"}},"required":["id","slug","name","version","category","kind","capabilities","events","isActive"]},"SessionAnnotation":{"type":"object","properties":{"id":{"type":"string","description":"Annotation UUID"},"tenantId":{"type":"string","description":"Tenant ID"},"pluginInstanceId":{"type":"string","description":"Plugin instance that created this"},"type":{"type":"string","description":"Annotation type","enum":["profanity","summary","action_items","sentiment"]},"data":{"type":"object","additionalProperties":true,"description":"Annotation data"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"sessionId":{"type":"string","description":"Session ID this annotation is attached to"}},"required":["id","tenantId","type","data","createdAt","sessionId"]},"PluginInstanceWithPlugin":{"type":"object","properties":{"id":{"type":"string","description":"Instance UUID"},"tenantId":{"type":"string","description":"Tenant ID"},"projectId":{"type":"string","description":"Project ID (null = all projects)"},"pluginId":{"type":"string","description":"Plugin ID"},"settings":{"type":"object","additionalProperties":true,"description":"User-configured settings"},"isEnabled":{"type":"boolean","description":"Whether this instance is enabled"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"},"plugin":{"description":"Plugin definition","allOf":[{"$ref":"#/components/schemas/Plugin"}]}},"required":["id","tenantId","pluginId","settings","isEnabled","createdAt","updatedAt","plugin"]},"CreatePluginInstanceRequest":{"type":"object","properties":{"pluginId":{"type":"string","description":"Plugin ID to activate"},"settings":{"type":"object","additionalProperties":true,"description":"Initial settings"}},"required":["pluginId"]},"PluginInstance":{"type":"object","properties":{"id":{"type":"string","description":"Instance UUID"},"tenantId":{"type":"string","description":"Tenant ID"},"projectId":{"type":"string","description":"Project ID (null = all projects)"},"pluginId":{"type":"string","description":"Plugin ID"},"settings":{"type":"object","additionalProperties":true,"description":"User-configured settings"},"isEnabled":{"type":"boolean","description":"Whether this instance is enabled"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"}},"required":["id","tenantId","pluginId","settings","isEnabled","createdAt","updatedAt"]},"UpdatePluginInstanceRequest":{"type":"object","properties":{"settings":{"type":"object","additionalProperties":true,"description":"Updated settings"},"isEnabled":{"type":"boolean","description":"Enable/disable the instance"}}},"DeletePluginInstanceResponse":{"type":"object","properties":{"message":{"type":"string","description":"Status message","example":"Plugin instance deleted"}},"required":["message"]},"ApiKeyScope":{"type":"string","enum":["sessions","sessions.delete","messages","participants","participants.token","gatherings","sanctions","sanctions.read","rooms.push","plugins","plugin-endpoints","webhooks","functions","settings"],"description":"Scopes this key has access to"},"ApiKeyResponse":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier"},"name":{"type":"string","description":"Key name"},"projectId":{"type":"string","description":"Project ID this key is scoped to (null = all projects)"},"keyPrefix":{"type":"string","description":"First characters of the key for identification","example":"ots_live_abc1..."},"scopes":{"type":"array","description":"Scopes this key has access to","items":{"$ref":"#/components/schemas/ApiKeyScope"}},"lastUsedAt":{"format":"date-time","type":"string","description":"When the key was last used"},"expiresAt":{"format":"date-time","type":"string","description":"Expiration date"},"createdAt":{"format":"date-time","type":"string","description":"Creation date"}},"required":["id","name","keyPrefix","scopes","createdAt"]},"CreateApiKey":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name for the key","example":"Aiba Webhook"},"scopes":{"type":"array","description":"Scopes to grant to this key (at least one). A plain scope (`sanctions`) covers reads and writes of its resource family, a `.read` scope (`sanctions.read`) only its read routes, and a capability scope (`participants.token`, `rooms.push`) one narrow capability the family scope does not include. `GET /api-keys/scopes` describes every scope.","example":["participants.token","sanctions.read"],"items":{"$ref":"#/components/schemas/ApiKeyScope"}},"projectId":{"type":"string","description":"Optional project ID to scope this key to","example":"my-project-id"},"expiresAt":{"type":"string","description":"Optional expiration date (ISO string)","example":"2025-12-31T23:59:59Z"}},"required":["name","scopes"]},"CreateApiKeyResponse":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier"},"name":{"type":"string","description":"Key name"},"projectId":{"type":"string","description":"Project ID this key is scoped to (null = all projects)"},"keyPrefix":{"type":"string","description":"First characters of the key for identification","example":"ots_live_abc1..."},"scopes":{"type":"array","description":"Scopes this key has access to","items":{"$ref":"#/components/schemas/ApiKeyScope"}},"lastUsedAt":{"format":"date-time","type":"string","description":"When the key was last used"},"expiresAt":{"format":"date-time","type":"string","description":"Expiration date"},"createdAt":{"format":"date-time","type":"string","description":"Creation date"},"key":{"type":"string","description":"Full API key - shown only once at creation!"}},"required":["id","name","keyPrefix","scopes","createdAt","key"]},"ApiKeyScopeInfo":{"type":"object","properties":{"scope":{"description":"The scope string stored on the key","allOf":[{"$ref":"#/components/schemas/ApiKeyScope"}]},"product":{"type":"string","description":"The 4Players product the scope belongs to","enum":["cortex","fleet"]},"family":{"type":"string","description":"Stable identifier of the resource family, for grouping","example":"sanctions"},"familyLabel":{"type":"string","description":"Display name of the family","example":"Sanctions"},"label":{"type":"string","description":"Display name of the scope within its family","example":"Read only"},"description":{"type":"string","description":"What the scope grants, including the events it receives on the SSE streams"},"access":{"type":"string","description":"`full` = read and write of the family; `read` = its read routes only; `capability` = one narrow capability the family scope does not include. A family with `full` and `read` offers a single None / Read / Read & write choice.","enum":["full","read","capability"]},"sensitive":{"type":"boolean","description":"The scope acts on players or on how the project authenticates. Presets never include it; UIs should ask for it explicitly and warn."}},"required":["scope","product","family","familyLabel","label","description","access","sensitive"]},"ApiKeyScopePreset":{"type":"object","properties":{"id":{"type":"string","description":"Stable identifier","example":"game-backend"},"label":{"type":"string","description":"Display name","example":"Game backend"},"description":{"type":"string","description":"What the preset is for"},"scopes":{"type":"array","description":"The scopes the preset selects","items":{"$ref":"#/components/schemas/ApiKeyScope"}}},"required":["id","label","description","scopes"]},"ApiKeyScopeCatalog":{"type":"object","properties":{"scopes":{"description":"Every grantable scope, in display order","type":"array","items":{"$ref":"#/components/schemas/ApiKeyScopeInfo"}},"presets":{"description":"Named starting selections, in display order","type":"array","items":{"$ref":"#/components/schemas/ApiKeyScopePreset"}}},"required":["scopes","presets"]},"GenerateTokenRequest":{"type":"object","properties":{"roomId":{"type":"string","description":"The Room ID"},"projectId":{"type":"string","description":"The Project ID (API Key)"},"secret":{"type":"string","description":"The Project Secret"}},"required":["roomId","projectId","secret"]},"GenerateTokenResponse":{"type":"object","properties":{"token":{"type":"string","description":"The generated access token"}},"required":["token"]},"AppSettingsResponse":{"type":"object","properties":{"id":{"type":"string","description":"Settings ID"},"appId":{"type":"string","description":"App ID"},"tokenProviderType":{"type":"string","description":"Token provider type","enum":["access_key","webservice","rooms"]},"botUserId":{"type":"string","description":"Bot User ID (for access_key provider)"},"webserviceUrl":{"type":"string","description":"Webservice URL (for webservice provider)"},"webserviceMethod":{"type":"string","description":"Webservice method"},"webserviceHeaders":{"type":"object","additionalProperties":true,"description":"HTTP headers sent to the token webservice"},"webserviceBodyTemplate":{"type":"string","description":"Request body template with {{roomId}} placeholder"},"webserviceResponseTokenPath":{"type":"string","description":"JSONPath used to extract the token from the webservice response"},"hasAccessKey":{"type":"boolean","description":"Whether an ODIN access key is stored for this project (the key itself is never returned)","default":false},"botUserData":{"type":"object","additionalProperties":true,"description":"Bot peer userData"},"hasSecret":{"type":"boolean","description":"Whether a project secret is cached locally for HMAC authentication"},"debugAudioEnabled":{"type":"boolean","description":"Whether debug audio storage is enabled for transcription debugging","default":false},"projectName":{"type":"string","description":"Human-readable project name (synced from Payment system)"},"e2eEncryptionMode":{"type":"string","description":"End-to-end encryption mode for the bot","enum":["disabled","automatic","manual"],"default":"disabled"},"e2eEncryptionPassword":{"type":"string","description":"Decrypted manual cipher password (only returned when mode=manual). Will be removed once a webhook-based mechanism replaces at-rest storage."},"odinSdkVersion":{"type":"string","description":"ODIN SDK major the transcription bot joins rooms with (v1 = ODIN 1.x, v2 = ODIN 2.x)","enum":["v1","v2"],"default":"v1"},"announcementsEnabled":{"type":"boolean","description":"Whether the bot speaks a recording announcement when joining and before leaving a room","default":true},"announcementJoinText":{"type":"string","description":"Text spoken when the bot joins the room"},"announcementLeaveText":{"type":"string","description":"Text spoken immediately before the bot leaves the room"},"transcriptPush":{"type":"string","description":"Push the live transcript into the ODIN room through the bot (Cortex Room Protocol, frame `cortex.transcript`): `off` = never (default), `all` = every segment to every peer, `own` = each segment only to the peer who spoke it. Takes effect when the bot next joins a room.","enum":["off","all","own"],"default":"off"},"sanctionPush":{"type":"boolean","description":"Let the bot tell the room about sanctions (`cortex.sanction` / `cortex.mutes` frames) and enforce voice mutes on the receiving side: clients stop hearing a muted peer, and the bot stops transcribing it. Only enable once your clients handle these frames. Defaults to false. Takes effect when the bot next joins a room.","default":false},"transcriberInstanceId":{"type":"string","description":"Plugin instance that transcribes this project; null = the default Cortex transcription (billed per minute)","nullable":true,"format":"uuid"}},"required":["id","appId","tokenProviderType","hasSecret","transcriberInstanceId"]},"ProjectSecretResponse":{"type":"object","properties":{"secret":{"type":"string","description":"The decrypted project secret, or null when none is cached","nullable":true}},"required":["secret"]},"AccessKeyConfig":{"type":"object","properties":{"accessKey":{"type":"string","description":"ODIN Access Key","example":"AK..."},"botUserId":{"type":"string","description":"Bot User ID","example":"transcription-bot"}},"required":["accessKey","botUserId"]},"WebserviceConfig":{"type":"object","properties":{"url":{"type":"string","description":"Token generation endpoint URL","example":"https://myserver.com/token"},"method":{"type":"string","description":"HTTP method","enum":["GET","POST"],"example":"POST"},"headers":{"type":"object","additionalProperties":{"type":"string"},"description":"HTTP headers","example":{"Authorization":"Bearer xxx"}},"bodyTemplate":{"type":"string","description":"Request body template with {{roomId}} placeholder","example":"{\"roomId\": \"{{roomId}}\"}"},"responseTokenPath":{"type":"string","description":"JSONPath to extract token from response","example":"data.token"}},"required":["url","method","responseTokenPath"]},"UpsertAppSettingsRequest":{"type":"object","properties":{"tokenProviderType":{"type":"string","description":"Token provider type","enum":["access_key","webservice","rooms"]},"accessKeyConfig":{"description":"Access Key config (required for access_key provider)","allOf":[{"$ref":"#/components/schemas/AccessKeyConfig"}]},"webserviceConfig":{"description":"Webservice config (required for webservice provider)","allOf":[{"$ref":"#/components/schemas/WebserviceConfig"}]},"botUserData":{"type":"object","additionalProperties":true,"description":"Bot peer userData as JSON","example":{"name":"Transcription Bot","role":"bot"}},"debugAudioEnabled":{"type":"boolean","description":"Enable debug audio storage for transcription debugging (auto-deleted after 24h). Defaults to false when omitted."},"e2eEncryptionMode":{"type":"string","description":"End-to-end encryption mode for the bot","enum":["disabled","automatic","manual"]},"e2eEncryptionPassword":{"type":"string","description":"Manual cipher password (only used when mode=manual). Stored encrypted at rest."},"odinSdkVersion":{"type":"string","description":"ODIN SDK major the transcription bot joins rooms with. Must match the SDK major used by the project's client applications (v1 = ODIN 1.x, v2 = ODIN 2.x — the two are not interoperable). Defaults to v1 when omitted.","enum":["v1","v2"]},"announcementsEnabled":{"type":"boolean","description":"Whether the bot speaks a recording announcement when joining and before leaving a room. Defaults to true."},"announcementJoinText":{"type":"string","description":"Text spoken when the bot joins the room","example":"This conversation is now being recorded and transcribed."},"announcementLeaveText":{"type":"string","description":"Text spoken immediately before the bot leaves the room","example":"Recording and transcription have stopped."},"transcriptPush":{"type":"string","description":"Push the live transcript into the ODIN room through the bot (Cortex Room Protocol, frame `cortex.transcript`): `off` = never (default), `all` = every segment to every peer, `own` = each segment only to the peer who spoke it. Takes effect when the bot next joins a room.","enum":["off","all","own"]},"sanctionPush":{"type":"boolean","description":"Let the bot tell the room about sanctions (`cortex.sanction` / `cortex.mutes` frames) and enforce voice mutes on the receiving side: clients stop hearing a muted peer, and the bot stops transcribing it. Only enable once your clients handle these frames. Defaults to false. Takes effect when the bot next joins a room."}},"required":["tokenProviderType"]},"UpdateAppSettingsRequest":{"type":"object","properties":{"tokenProviderType":{"type":"string","description":"Token provider type","enum":["access_key","webservice","rooms"]},"accessKeyConfig":{"description":"Access Key config (required for access_key provider)","allOf":[{"$ref":"#/components/schemas/AccessKeyConfig"}]},"webserviceConfig":{"description":"Webservice config (required for webservice provider)","allOf":[{"$ref":"#/components/schemas/WebserviceConfig"}]},"botUserData":{"type":"object","additionalProperties":true,"description":"Bot peer userData as JSON","example":{"name":"Transcription Bot","role":"bot"}},"debugAudioEnabled":{"type":"boolean","description":"Enable debug audio storage for transcription debugging (auto-deleted after 24h). Defaults to false when omitted."},"e2eEncryptionMode":{"type":"string","description":"End-to-end encryption mode for the bot","enum":["disabled","automatic","manual"]},"e2eEncryptionPassword":{"type":"string","description":"Manual cipher password (only used when mode=manual). Stored encrypted at rest."},"odinSdkVersion":{"type":"string","description":"ODIN SDK major the transcription bot joins rooms with. Must match the SDK major used by the project's client applications (v1 = ODIN 1.x, v2 = ODIN 2.x — the two are not interoperable).","enum":["v1","v2"]},"announcementsEnabled":{"type":"boolean","description":"Whether the bot speaks a recording announcement when joining and before leaving a room."},"announcementJoinText":{"type":"string","description":"Text spoken when the bot joins the room","example":"This conversation is now being recorded and transcribed."},"announcementLeaveText":{"type":"string","description":"Text spoken immediately before the bot leaves the room","example":"Recording and transcription have stopped."},"transcriptPush":{"type":"string","description":"Push the live transcript into the ODIN room through the bot (Cortex Room Protocol, frame `cortex.transcript`): `off` = never (default), `all` = every segment to every peer, `own` = each segment only to the peer who spoke it. Takes effect when the bot next joins a room.","enum":["off","all","own"]},"sanctionPush":{"type":"boolean","description":"Let the bot tell the room about sanctions (`cortex.sanction` / `cortex.mutes` frames) and enforce voice mutes on the receiving side: clients stop hearing a muted peer, and the bot stops transcribing it. Only enable once your clients handle these frames. Defaults to false. Takes effect when the bot next joins a room."}}},"UpdateDebugAudioRequest":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether to enable debug audio storage","example":true}},"required":["enabled"]},"UpdateE2EEncryptionRequest":{"type":"object","properties":{"mode":{"type":"string","description":"End-to-end encryption mode","enum":["disabled","automatic","manual"]},"password":{"type":"string","description":"Cipher password — required when mode=manual. Leave empty to keep existing."}},"required":["mode"]},"UpdateTranscriptionSettingsRequest":{"type":"object","properties":{"transcriberInstanceId":{"type":"string","description":"Plugin instance that transcribes this project (an enabled `audio` plugin with the `transcribe` capability), or null for the default Cortex transcription","nullable":true,"format":"uuid"}},"required":["transcriberInstanceId"]},"UpdateAnnouncementsRequest":{"type":"object","properties":{"enabled":{"type":"boolean","description":"Whether the bot speaks a recording announcement when joining and before leaving a room"},"joinText":{"type":"string","description":"Text spoken when the bot joins the room","example":"This conversation is now being recorded and transcribed.","maxLength":500},"leaveText":{"type":"string","description":"Text spoken immediately before the bot leaves the room","example":"Recording and transcription have stopped.","maxLength":500}}},"SecretOperationResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether the operation completed successfully"}},"required":["success"]},"SessionCreatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["created"]},"data":{"$ref":"#/components/schemas/SessionResponse"}},"required":["action","data"]},"SessionCreatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["session.created"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/SessionCreatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"SessionUpdatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["updated"]},"data":{"$ref":"#/components/schemas/SessionResponse"},"changes":{"type":"object","additionalProperties":true,"description":"Previous values of changed fields"}},"required":["action","data"]},"SessionUpdatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["session.updated"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/SessionUpdatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"SessionDeletedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["deleted"]},"data":{"$ref":"#/components/schemas/SessionResponse"}},"required":["action","data"]},"SessionDeletedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["session.deleted"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/SessionDeletedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"MessageCreatedEventData":{"type":"object","properties":{"id":{"type":"string","description":"Message ID"},"sessionId":{"type":"string","description":"Session ID the message belongs to"},"seq":{"type":"number","description":"Per-session sequence number of the message (the cursor of the messages route)","example":42},"participantId":{"type":"string","description":"Participant ID (null for system messages)","nullable":true},"externalUserId":{"type":"string","description":"External user ID from ODIN (null for system messages)","nullable":true},"type":{"type":"string","description":"Message type (only set on system messages)","enum":["user","system"]},"senderName":{"type":"string","description":"Sender display name (or \"[System]\" for system messages)"},"content":{"type":"string","description":"Transcribed message content"},"timestamp":{"type":"string","description":"Message timestamp","format":"date-time"},"hasDebugAudio":{"type":"boolean","description":"Whether a debug audio WAV is available (user messages only)"}},"required":["id","sessionId","seq","participantId","externalUserId","senderName","content","timestamp"]},"MessageCreatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["created"]},"data":{"$ref":"#/components/schemas/MessageCreatedEventData"}},"required":["action","data"]},"MessageCreatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["message.created"]},"source":{"type":"string","description":"Origin service","enum":["cortex:bot"]},"payload":{"$ref":"#/components/schemas/MessageCreatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"WebhookDeliveryStatusEventData":{"type":"object","properties":{"id":{"type":"string","description":"Webhook event ID"},"eventType":{"type":"string","description":"The domain event type that triggered the webhook","example":"message.created"},"status":{"type":"string","description":"Delivery status","enum":["pending","delivered","failed"]},"attemptsCount":{"type":"number","description":"Number of delivery attempts so far"},"lastError":{"type":"string","description":"Last delivery error message, if any"},"projectId":{"type":"string","description":"Project/app ID the webhook event belongs to"}},"required":["id","eventType","status","attemptsCount"]},"WebhookDeliveryUpdatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["occurred"]},"data":{"$ref":"#/components/schemas/WebhookDeliveryStatusEventData"}},"required":["action","data"]},"WebhookDeliveryUpdatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["webhook.delivery.updated"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/WebhookDeliveryUpdatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"ParticipantEventData":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the participant"},"projectId":{"type":"string","description":"ODIN project ID this participant belongs to"},"displayName":{"type":"string","description":"Display name of the participant"},"externalUserId":{"type":"string","description":"External User ID from ODIN (stable identifier)"},"tenantId":{"type":"string","description":"Tenant ID"},"joinedAt":{"format":"date-time","type":"string","description":"When the participant first joined any session in the project"}},"required":["id","projectId","displayName","externalUserId","tenantId","joinedAt"]},"ParticipantCreatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["created"]},"data":{"$ref":"#/components/schemas/ParticipantEventData"}},"required":["action","data"]},"ParticipantCreatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["participant.created"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/ParticipantCreatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"ParticipantResponse":{"type":"object","properties":{"id":{"type":"string","description":"Unique identifier for the participant"},"projectId":{"type":"string","description":"ODIN project ID this participant belongs to"},"displayName":{"type":"string","description":"Display name of the participant"},"externalUserId":{"type":"string","description":"External User ID from ODIN (stable identifier)"},"tenantId":{"type":"string","description":"Tenant ID"},"userData":{"type":"object","additionalProperties":true,"nullable":true,"description":"User data from ODIN (parsed JSON). Present in REST responses and in `participant.updated` events (the new value); stripped from all other domain events — fetch the participant to read it there."},"joinedAt":{"format":"date-time","type":"string","description":"When the participant first joined any session in the project"}},"required":["id","projectId","displayName","externalUserId","tenantId","userData","joinedAt"]},"ParticipantUpdatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["updated"]},"data":{"$ref":"#/components/schemas/ParticipantResponse"},"changes":{"type":"object","additionalProperties":true,"description":"Previous values of changed fields (the old userData is never included; the new value is in data.userData)"}},"required":["action","data"]},"ParticipantUpdatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["participant.updated"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/ParticipantUpdatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"ParticipantDeletedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["deleted"]},"data":{"$ref":"#/components/schemas/ParticipantEventData"}},"required":["action","data"]},"ParticipantDeletedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["participant.deleted"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/ParticipantDeletedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"MessageAnnotationCreatedEventData":{"type":"object","properties":{"annotationId":{"type":"string","description":"ID of the created annotation row"},"messageId":{"type":"string","description":"Message ID this annotation is attached to"},"participantId":{"type":"string","description":"Participant ID, when available from the source message"},"externalUserId":{"type":"string","description":"External user ID, when available from the source message"},"type":{"type":"string","description":"Annotation type","example":"profanity"},"content":{"type":"object","additionalProperties":true,"description":"Annotation content (free-form, plugin-defined). Named `content` rather than `data` so the event reads `payload.data.content` instead of `payload.data.data`."}},"required":["annotationId","messageId","type","content"]},"MessageAnnotationCreatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["created"]},"data":{"$ref":"#/components/schemas/MessageAnnotationCreatedEventData"}},"required":["action","data"]},"MessageAnnotationCreatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["message.annotation.created"]},"source":{"type":"string","description":"Origin service","enum":["cortex:plugin"]},"payload":{"$ref":"#/components/schemas/MessageAnnotationCreatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"SessionAnnotationCreatedEventData":{"type":"object","properties":{"annotationId":{"type":"string","description":"ID of the created annotation row"},"sessionId":{"type":"string","description":"Session ID this annotation is attached to"},"type":{"type":"string","description":"Annotation type","example":"summary"},"content":{"type":"object","additionalProperties":true,"description":"Annotation content (free-form, plugin-defined). Named `content` rather than `data` so the event reads `payload.data.content` instead of `payload.data.data`."}},"required":["annotationId","sessionId","type","content"]},"SessionAnnotationCreatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["created"]},"data":{"$ref":"#/components/schemas/SessionAnnotationCreatedEventData"}},"required":["action","data"]},"SessionAnnotationCreatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["session.annotation.created"]},"source":{"type":"string","description":"Origin service","enum":["cortex:plugin"]},"payload":{"$ref":"#/components/schemas/SessionAnnotationCreatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"SanctionParticipantEventInfo":{"type":"object","properties":{"id":{"type":"string","description":"Participant ID"},"projectId":{"type":"string","description":"ODIN project ID this participant belongs to"},"displayName":{"type":"string","description":"Display name of the participant"},"externalUserId":{"type":"string","description":"External user ID from ODIN"},"joinedAt":{"format":"date-time","type":"string","description":"When the participant first joined the project"}},"required":["id","projectId","displayName","externalUserId"]},"SanctionEventData":{"type":"object","properties":{"id":{"type":"string","description":"Sanction ID"},"tenantId":{"type":"string","description":"Tenant ID"},"projectId":{"type":"string","description":"Project the sanction belongs to"},"participantId":{"type":"string","description":"Participant ID (if session-scoped)"},"externalUserId":{"type":"string","description":"External user ID"},"sessionId":{"type":"string","description":"Session ID (if session-scoped)"},"gatheringId":{"type":"string","description":"Gathering the sanction was issued in (if any)"},"type":{"type":"string","enum":["warn","mute","listen_only","text_only","rate_limit","shadow_mute","temp_ban","perm_ban","ranked_restriction","queue_delay","party_restriction","human_review"],"description":"Type of sanction"},"scope":{"type":"object","additionalProperties":true,"description":"Scope configuration"},"reason":{"type":"string","description":"Reason for sanction"},"metadata":{"type":"object","additionalProperties":true,"description":"Additional metadata"},"startAt":{"format":"date-time","type":"string","description":"When sanction becomes active"},"endAt":{"format":"date-time","type":"string","description":"When sanction expires (null = permanent)"},"createdByUserId":{"type":"string","description":"User who created the sanction"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"},"revokedAt":{"format":"date-time","type":"string","description":"When sanction was revoked"},"revokedByUserId":{"type":"string","description":"User who revoked the sanction"},"isActive":{"type":"boolean","description":"Whether the sanction is currently active"},"status":{"type":"string","description":"Status: active, expired, or revoked"},"participant":{"description":"Participant information (if available); never includes userData","allOf":[{"$ref":"#/components/schemas/SanctionParticipantEventInfo"}]}},"required":["id","tenantId","projectId","type","startAt","createdAt","updatedAt","isActive","status"]},"SanctionCreatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["created"]},"data":{"$ref":"#/components/schemas/SanctionEventData"}},"required":["action","data"]},"SanctionCreatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["sanction.created"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/SanctionCreatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"SanctionUpdatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["updated"]},"data":{"$ref":"#/components/schemas/SanctionEventData"},"changes":{"type":"object","additionalProperties":true,"description":"Previous values of changed fields"}},"required":["action","data"]},"SanctionUpdatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["sanction.updated"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/SanctionUpdatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"SanctionRevokedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["occurred"]},"data":{"$ref":"#/components/schemas/SanctionEventData"}},"required":["action","data"]},"SanctionRevokedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["sanction.revoked"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/SanctionRevokedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringMember":{"type":"object","properties":{"id":{"type":"string","description":"Member record ID"},"gatheringId":{"type":"string","description":"Gathering ID"},"participantId":{"type":"string","description":"Participant ID"},"displayName":{"type":"string","description":"Participant display name"},"status":{"type":"string","enum":["invited","joined","ready","left"],"description":"Member status"},"role":{"type":"string","enum":["owner","member"],"description":"Member role"},"properties":{"type":"object","additionalProperties":true,"description":"Custom member properties"},"invitedAt":{"format":"date-time","type":"string","description":"When the member was invited"},"joinedAt":{"format":"date-time","type":"string","description":"When the member joined"},"readyAt":{"format":"date-time","type":"string","description":"When the member marked as ready"}},"required":["id","gatheringId","participantId","displayName","status","role"]},"GatheringResponse":{"type":"object","properties":{"id":{"type":"string","description":"Gathering ID"},"projectId":{"type":"string","description":"Project ID"},"tenantId":{"type":"string","description":"Tenant ID"},"name":{"type":"string","description":"Gathering name"},"description":{"type":"string","description":"Gathering description"},"inviterName":{"type":"string","description":"Display name of the gathering organizer"},"inviterEmail":{"type":"string","description":"Email address of the gathering organizer (used as Reply-To in invitation emails)"},"type":{"type":"string","enum":["lobby","appointment","open"],"description":"Gathering type"},"status":{"type":"string","enum":["pending","active","started","ended","cancelled","expired"],"description":"Gathering status"},"maxMembers":{"type":"number","description":"Maximum members allowed"},"memberCount":{"type":"number","description":"Current member count"},"accessPolicy":{"type":"string","description":"Access policy"},"listed":{"type":"boolean","description":"Whether gathering is listed for discovery"},"startsAt":{"format":"date-time","type":"string","description":"Scheduled start time"},"endsAt":{"format":"date-time","type":"string","description":"Scheduled end time"},"timezone":{"type":"string","description":"Timezone for scheduled times"},"roomId":{"type":"string","description":"Target ODIN room ID"},"autoStartSession":{"type":"boolean","description":"Whether session starts automatically"},"sessionId":{"type":"string","description":"Created session ID"},"joinCode":{"type":"string","description":"Join code for sharing"},"properties":{"type":"object","additionalProperties":true,"description":"Custom properties"},"ownerId":{"type":"string","description":"Owner participant ID"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"expiresAt":{"format":"date-time","type":"string","description":"Expiration timestamp"},"members":{"description":"Members (included in detailed view)","type":"array","items":{"$ref":"#/components/schemas/GatheringMember"}}},"required":["id","projectId","tenantId","name","type","status","maxMembers","memberCount","accessPolicy","listed","autoStartSession","joinCode","createdAt"]},"GatheringCreatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["created"]},"data":{"$ref":"#/components/schemas/GatheringResponse"}},"required":["action","data"]},"GatheringCreatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.created"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringCreatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringUpdatedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["updated"]},"data":{"$ref":"#/components/schemas/GatheringResponse"},"changes":{"type":"object","additionalProperties":true,"description":"Previous values of changed fields"}},"required":["action","data"]},"GatheringUpdatedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.updated"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringUpdatedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringOccurredPayload":{"type":"object","properties":{"action":{"type":"string","enum":["occurred"]},"data":{"$ref":"#/components/schemas/GatheringResponse"}},"required":["action","data"]},"GatheringStartedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.started"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringOccurredPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringEndedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.ended"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringOccurredPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringCancelledEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.cancelled"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringOccurredPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringMemberEventData":{"type":"object","properties":{"gatheringId":{"type":"string","description":"Gathering ID"},"member":{"description":"The member that joined / became ready","allOf":[{"$ref":"#/components/schemas/GatheringMember"}]}},"required":["gatheringId","member"]},"GatheringMemberPayload":{"type":"object","properties":{"action":{"type":"string","enum":["occurred"]},"data":{"$ref":"#/components/schemas/GatheringMemberEventData"}},"required":["action","data"]},"GatheringMemberJoinedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.member.joined"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringMemberPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringMemberReadyEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.member.ready"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringMemberPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringMemberLeftEventData":{"type":"object","properties":{"gatheringId":{"type":"string","description":"Gathering ID"},"participantId":{"type":"string","description":"Participant ID of the member that left"}},"required":["gatheringId","participantId"]},"GatheringMemberLeftPayload":{"type":"object","properties":{"action":{"type":"string","enum":["occurred"]},"data":{"$ref":"#/components/schemas/GatheringMemberLeftEventData"}},"required":["action","data"]},"GatheringMemberLeftEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.member.left"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringMemberLeftPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringInvitationSentPayload":{"type":"object","properties":{"action":{"type":"string","enum":["occurred"]},"data":{"oneOf":[{"$ref":"#/components/schemas/InvitationResponse"},{"$ref":"#/components/schemas/GatheringInvitationSentEmailData"}]}},"required":["action","data"]},"GatheringInvitationSentEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.invitation.sent"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringInvitationSentPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringInvitationAcceptedEventData":{"type":"object","properties":{"invitationId":{"type":"string","description":"Invitation ID"},"gatheringId":{"type":"string","description":"Gathering ID"},"participantId":{"type":"string","description":"Participant ID that accepted"}},"required":["invitationId","gatheringId","participantId"]},"GatheringInvitationAcceptedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["occurred"]},"data":{"$ref":"#/components/schemas/GatheringInvitationAcceptedEventData"}},"required":["action","data"]},"GatheringInvitationAcceptedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.invitation.accepted"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringInvitationAcceptedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GatheringInvitationDeclinedEventData":{"type":"object","properties":{"invitationId":{"type":"string","description":"Invitation ID"},"gatheringId":{"type":"string","description":"Gathering ID"}},"required":["invitationId","gatheringId"]},"GatheringInvitationDeclinedPayload":{"type":"object","properties":{"action":{"type":"string","enum":["occurred"]},"data":{"$ref":"#/components/schemas/GatheringInvitationDeclinedEventData"}},"required":["action","data"]},"GatheringInvitationDeclinedEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","enum":["gathering.invitation.declined"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api"]},"payload":{"$ref":"#/components/schemas/GatheringInvitationDeclinedPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"GenericEventPayload":{"type":"object","properties":{"action":{"type":"string","enum":["created","updated","deleted","occurred"]},"data":{"type":"object","additionalProperties":true,"description":"Event-specific data (open object for non-modelled events)"},"changes":{"type":"object","additionalProperties":true,"description":"Previous values of changed fields (updated only)"}},"required":["action","data"]},"GenericDomainEvent":{"type":"object","properties":{"id":{"type":"string","description":"Unique ID for this specific event occurrence (UUIDv4)"},"tenantId":{"type":"string","description":"Tenant this event belongs to"},"projectId":{"type":"string","description":"Project/app ID for project-scoped events"},"resourceId":{"type":"string","description":"Composite resource identifier in resource:id format","example":"session:abc-123"},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred","format":"date-time"},"type":{"type":"string","description":"Event type for externally-ingested / non-Cortex-native events","enum":["participant.joined","participant.left","bot.status.changed","image.created","image.updated","image.ready","config.created","config.updated","config.ready","deployment.created","deployment.updated","server.created","server.updated","server.stopped","server.started","server.running"]},"source":{"type":"string","description":"Origin service","enum":["cortex:api","cortex:bot","cortex:plugin","voice:server","voice:gateway","fleet:api","fleet:server"]},"payload":{"$ref":"#/components/schemas/GenericEventPayload"}},"required":["id","tenantId","resourceId","timestamp","type","source","payload"]},"InvitationResponse":{"type":"object","properties":{"id":{"type":"string","description":"Invitation ID"},"gatheringId":{"type":"string","description":"Gathering ID"},"participantId":{"type":"string","description":"Invited participant ID"},"email":{"type":"string","description":"Invited email"},"displayName":{"type":"string","description":"Display name of the invitee"},"invitedById":{"type":"string","description":"Who sent the invitation (null for system-generated invitations)"},"status":{"type":"string","enum":["pending","accepted","declined","expired"],"description":"Invitation status"},"joinUrl":{"type":"string","description":"Join URL"},"joinCode":{"type":"string","description":"Join code"},"invitedAt":{"format":"date-time","type":"string","description":"When invitation was sent"},"expiresAt":{"format":"date-time","type":"string","description":"When invitation expires"},"respondedAt":{"format":"date-time","type":"string","description":"When invitee responded"}},"required":["id","gatheringId","status","joinUrl","joinCode","invitedAt"]},"GatheringInvitationSentEmailData":{"type":"object","properties":{"email":{"type":"string","description":"Invitee email address"},"name":{"type":"string","description":"Invitee display name"}},"required":["email","name"]},"IngestEventDto":{"type":"object","properties":{"type":{"type":"string","description":"The type of domain event being ingested","enum":["session.created","session.updated","session.deleted","message.created","participant.joined","participant.left","participant.updated","participant.created","participant.deleted","bot.status.changed","webhook.delivery.updated","message.annotation.created","session.annotation.created","sanction.created","sanction.updated","sanction.revoked","gathering.created","gathering.updated","gathering.started","gathering.ended","gathering.cancelled","gathering.member.joined","gathering.member.left","gathering.member.ready","gathering.invitation.sent","gathering.invitation.accepted","gathering.invitation.declined","image.created","image.updated","image.ready","config.created","config.updated","config.ready","deployment.created","deployment.updated","server.created","server.updated","server.stopped","server.started","server.running"],"example":"participant.joined"},"source":{"type":"string","description":"The source service that generated this event (namespace:service format)","example":"voice:server","enum":["voice:server","voice:gateway","fleet:api","fleet:server"]},"resourceId":{"type":"string","description":"Resource identifier in resource:id format (e.g. session:abc-123)","example":"session:550e8400-e29b-41d4-a716-446655440000","pattern":"^[a-z_]+:.+$"},"payload":{"type":"object","additionalProperties":true,"description":"Action-typed event payload: { action, data, changes? }","example":{"action":"occurred","data":{"sessionId":"550e8400-e29b-41d4-a716-446655440000","peerId":42,"userId":"user-1"}}},"timestamp":{"type":"string","description":"ISO 8601 timestamp of when the event occurred (defaults to server time)","example":"2026-03-20T12:00:00.000Z"}},"required":["type","source","resourceId","payload"]},"CreateSanctionRequest":{"type":"object","properties":{"participantId":{"type":"string","description":"Participant ID to sanction (for session-scoped sanctions)","example":"550e8400-e29b-41d4-a716-446655440000"},"externalUserId":{"type":"string","description":"External user ID from ODIN (for cross-session sanctions)","example":"user_abc123"},"sessionId":{"type":"string","description":"Session ID (for session-scoped sanctions)","example":"550e8400-e29b-41d4-a716-446655440001"},"gatheringId":{"type":"string","description":"Gathering (lobby) the sanction was issued in, so a game can list the sanctions of one lobby","example":"550e8400-e29b-41d4-a716-446655440002"},"type":{"type":"string","enum":["warn","mute","listen_only","text_only","rate_limit","shadow_mute","temp_ban","perm_ban","ranked_restriction","queue_delay","party_restriction","human_review"],"description":"Type of sanction to apply","example":"mute"},"scope":{"type":"object","additionalProperties":true,"description":"Scope configuration for channel-specific sanctions","example":{"channels":["team","proximity"]}},"reason":{"type":"string","description":"Reason for the sanction","example":"Repeated use of profanity detected by automated filter"},"metadata":{"type":"object","additionalProperties":true,"description":"Additional metadata (evidence, AI confidence, etc.)","example":{"confidence":0.95,"triggeredBy":"profanity_filter"}},"startAt":{"type":"string","description":"When the sanction starts (defaults to now)","example":"2024-01-15T10:00:00Z"},"endAt":{"type":"string","description":"When the sanction ends (null = permanent)","example":"2024-01-15T11:00:00Z"}},"required":["type"]},"ParticipantInfo":{"type":"object","properties":{"id":{"type":"string","description":"Participant ID"},"projectId":{"type":"string","description":"ODIN project ID this participant belongs to"},"displayName":{"type":"string","description":"Display name of the participant"},"externalUserId":{"type":"string","description":"External user ID from ODIN"},"userData":{"type":"object","additionalProperties":true,"description":"User data from ODIN (parsed JSON)"},"joinedAt":{"format":"date-time","type":"string","description":"When the participant first joined the project"}},"required":["id","projectId","displayName","externalUserId"]},"SanctionResponse":{"type":"object","properties":{"id":{"type":"string","description":"Sanction ID"},"tenantId":{"type":"string","description":"Tenant ID"},"projectId":{"type":"string","description":"Project the sanction belongs to"},"participantId":{"type":"string","description":"Participant ID (if session-scoped)"},"externalUserId":{"type":"string","description":"External user ID"},"sessionId":{"type":"string","description":"Session ID (if session-scoped)"},"gatheringId":{"type":"string","description":"Gathering the sanction was issued in (if any)"},"type":{"type":"string","enum":["warn","mute","listen_only","text_only","rate_limit","shadow_mute","temp_ban","perm_ban","ranked_restriction","queue_delay","party_restriction","human_review"],"description":"Type of sanction"},"scope":{"type":"object","additionalProperties":true,"description":"Scope configuration"},"reason":{"type":"string","description":"Reason for sanction"},"metadata":{"type":"object","additionalProperties":true,"description":"Additional metadata"},"startAt":{"format":"date-time","type":"string","description":"When sanction becomes active"},"endAt":{"format":"date-time","type":"string","description":"When sanction expires (null = permanent)"},"createdByUserId":{"type":"string","description":"User who created the sanction"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"},"revokedAt":{"format":"date-time","type":"string","description":"When sanction was revoked"},"revokedByUserId":{"type":"string","description":"User who revoked the sanction"},"isActive":{"type":"boolean","description":"Whether the sanction is currently active"},"status":{"type":"string","description":"Status: active, expired, or revoked"},"participant":{"description":"Participant information (if available)","allOf":[{"$ref":"#/components/schemas/ParticipantInfo"}]}},"required":["id","tenantId","projectId","type","startAt","createdAt","updatedAt","isActive","status"]},"SanctionListResponse":{"type":"object","properties":{"sanctions":{"description":"List of sanctions","type":"array","items":{"$ref":"#/components/schemas/SanctionResponse"}},"total":{"type":"number","description":"Total count (for pagination)"}},"required":["sanctions","total"]},"UpdateSanctionRequest":{"type":"object","properties":{"type":{"type":"string","description":"Updated sanction type","enum":["warn","mute","listen_only","text_only","rate_limit","shadow_mute","temp_ban","perm_ban","ranked_restriction","queue_delay","party_restriction","human_review"]},"scope":{"type":"object","additionalProperties":true,"description":"Updated scope configuration"},"reason":{"type":"string","description":"Updated reason"},"metadata":{"type":"object","additionalProperties":true,"description":"Updated metadata"},"endAt":{"type":"string","description":"Updated end time"}}},"RevokeSanctionRequest":{"type":"object","properties":{"reason":{"type":"string","description":"Reason for revocation","example":"False positive from automated detection"}}},"ParticipantListResponse":{"type":"object","properties":{"participants":{"description":"List of participants","type":"array","items":{"$ref":"#/components/schemas/ParticipantResponse"}},"total":{"type":"number","description":"Total count of participants"}},"required":["participants","total"]},"CreateParticipantRequest":{"type":"object","properties":{"externalUserId":{"type":"string","description":"External User ID from ODIN (stable identifier)"},"displayName":{"type":"string","description":"Display name of the participant"},"userData":{"type":"object","additionalProperties":true,"description":"User data from ODIN (parsed JSON)"}},"required":["externalUserId"]},"UpdateParticipantRequest":{"type":"object","properties":{"displayName":{"type":"string","description":"Display name of the participant"},"userData":{"type":"object","additionalProperties":true,"description":"User data from ODIN (parsed JSON)"}}},"InvitedParticipant":{"type":"object","properties":{"name":{"type":"string","description":"Display name of the invitee","example":"Jane Doe"},"email":{"type":"string","description":"Email address for sending the invitation","example":"jane@example.com"},"userId":{"type":"string","description":"External user ID (from ODIN) for linking to a participant record. If provided, the invitee will be added as a gathering member for attendance tracking.","example":"usr_abc123"}},"required":["name","email"]},"CreateGatheringRequest":{"type":"object","properties":{"name":{"type":"string","description":"Human-readable name for the gathering","example":"Friday Game Night"},"type":{"type":"string","enum":["lobby","appointment","open"],"description":"Type of gathering","example":"lobby"},"maxMembers":{"type":"number","description":"Maximum number of members (2-100)","example":8,"minimum":2,"maximum":100},"accessPolicy":{"type":"string","enum":["public","private","invite-only"],"description":"Access policy for joining","example":"public"},"listed":{"type":"boolean","description":"Whether gathering appears in public search","example":false},"startsAt":{"type":"string","description":"Scheduled start time (for appointments)","example":"2024-01-20T15:00:00Z"},"endsAt":{"type":"string","description":"Scheduled end time (for appointments)","example":"2024-01-20T16:00:00Z"},"timezone":{"type":"string","description":"Timezone for scheduled times (IANA format)","example":"Europe/Berlin"},"autoStartSession":{"type":"boolean","description":"Whether to auto-start transcription session when gathering starts","example":true},"properties":{"type":"object","additionalProperties":true,"description":"Custom properties (game mode, lobby settings, etc.)","example":{"gameMode":"deathmatch","map":"dust2"}},"searchProperties":{"type":"object","additionalProperties":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Indexed search properties for discovery","example":{"gameMode":"deathmatch","skillLevel":1200}},"roomId":{"type":"string","description":"Custom room ID for ODIN. If not provided, the join code will be used as the room ID.","example":"game-room-lobby-123"},"ownerId":{"type":"string","description":"Participant UUID of the gathering owner. If not provided, the creator becomes the owner."},"description":{"type":"string","description":"Description providing context about the gathering (shown in invitation emails)","example":"Weekly team sync to discuss project progress and blockers."},"invitationUrl":{"type":"string","description":"URL where participants join the meeting (e.g., Rooms instance URL). Used in invitation emails and calendar entries as the location.","example":"https://myapp.rooms.chat/room/abc123"},"participants":{"description":"List of participants to invite. Each invitee receives an email invitation. For Appointment gatherings with a start date, a calendar invite (.ics) is attached.","type":"array","items":{"$ref":"#/components/schemas/InvitedParticipant"}},"inviterName":{"type":"string","description":"Display name of the person organizing the gathering. Shown as \"Invited by {name}\" in invitation emails and used as the ORGANIZER CN in calendar invites.","example":"Alice Smith"},"inviterEmail":{"type":"string","description":"Email address of the person organizing the gathering. Used as the Reply-To address in invitation emails and as the ORGANIZER mailto in calendar invites so recipients can reply directly to the inviter.","example":"alice@example.com"}},"required":["name","type"]},"GatheringListResponse":{"type":"object","properties":{"gatherings":{"description":"List of gatherings","type":"array","items":{"$ref":"#/components/schemas/GatheringResponse"}},"total":{"type":"number","description":"Total count (for pagination)"}},"required":["gatherings","total"]},"UpdateGatheringRequest":{"type":"object","properties":{"actorParticipantId":{"type":"string","description":"Participant acting on behalf of the caller. When set, the request is only accepted if that participant owns the gathering."},"name":{"type":"string","description":"Updated name"},"maxMembers":{"type":"number","description":"Updated max members"},"accessPolicy":{"type":"string","enum":["public","private","invite-only"],"description":"Updated access policy"},"listed":{"type":"boolean","description":"Updated visibility"},"startsAt":{"type":"string","description":"Updated scheduled start time"},"endsAt":{"type":"string","description":"Updated scheduled end time"},"properties":{"type":"object","additionalProperties":true,"description":"Updated properties"},"searchProperties":{"type":"object","additionalProperties":{"oneOf":[{"type":"string"},{"type":"number"}]},"description":"Updated search properties"},"description":{"type":"string","description":"Updated description"},"inviterName":{"type":"string","description":"Updated inviter display name"},"inviterEmail":{"type":"string","description":"Updated inviter email address"}}},"StartGatheringRequest":{"type":"object","properties":{"roomId":{"type":"string","description":"ODIN room ID to use. Defaults to the room the gathering was created with.","example":"room-123"},"actorParticipantId":{"type":"string","description":"Participant acting on behalf of the caller. When set, the request is only accepted if that participant owns the gathering."}}},"GatheringActorRequest":{"type":"object","properties":{"actorParticipantId":{"type":"string","description":"Participant acting on behalf of the caller. When set, the request is only accepted if that participant owns the gathering."}}},"JoinGatheringRequest":{"type":"object","properties":{"joinCode":{"type":"string","description":"8-character join code","example":"ABCD1234"},"participantId":{"type":"string","description":"Participant ID of the joining member"}},"required":["joinCode","participantId"]},"JoinGatheringResponse":{"type":"object","properties":{"success":{"type":"boolean","description":"Whether join was successful"},"gathering":{"description":"Gathering data","allOf":[{"$ref":"#/components/schemas/GatheringResponse"}]},"member":{"description":"Member data","allOf":[{"$ref":"#/components/schemas/GatheringMember"}]},"error":{"type":"string","description":"Error message if failed"}},"required":["success"]},"AddMemberRequest":{"type":"object","properties":{"participantId":{"type":"string","description":"Participant ID to add"},"role":{"type":"string","enum":["owner","member"],"description":"Role for the member (default: member)"},"properties":{"type":"object","additionalProperties":true,"description":"Custom member properties"}},"required":["participantId"]},"UpdateMemberRequest":{"type":"object","properties":{"status":{"type":"string","enum":["invited","joined","ready","left"],"description":"Updated member status"},"properties":{"type":"object","additionalProperties":true,"description":"Updated member properties"}}},"SendInvitationsRequest":{"type":"object","properties":{"participantIds":{"description":"Participant IDs to invite","type":"array","items":{"type":"string"}},"emails":{"description":"Email addresses to invite","type":"array","items":{"type":"string"}},"customProtocol":{"type":"string","description":"Custom protocol format for join URL","example":"mygame://"},"baseUrl":{"type":"string","description":"Base URL for HTTP join links","example":"https://game.example.com"},"invitedById":{"type":"string","description":"Participant UUID of the person sending invitations"}},"required":["invitedById"]},"AcceptInvitationRequest":{"type":"object","properties":{"participantId":{"type":"string","description":"The accepting participant UUID"}},"required":["participantId"]},"GenerateProjectTokenRequest":{"type":"object","properties":{"roomId":{"type":"string","description":"The Room ID","example":"my-awesome-room"},"userId":{"type":"string","description":"The User ID","example":"my-user-id"}},"required":["roomId","userId"]},"FunctionDependency":{"type":"object","properties":{"name":{"type":"string","description":"NPM package name","example":"node-mailjet"},"version":{"type":"string","description":"Semantic version or tag","example":"^6.0.0"}},"required":["name","version"]},"FunctionEnvVar":{"type":"object","properties":{"key":{"type":"string","description":"Environment variable name","example":"API_KEY"},"value":{"type":"string","description":"Environment variable value (masked if secret)","example":"***"},"isSecret":{"type":"boolean","description":"Whether this is a secret (encrypted at rest)","default":false}},"required":["key","value","isSecret"]},"FunctionSettings":{"type":"object","properties":{"isActivated":{"type":"boolean","description":"Whether Functions feature is activated"},"activationStep":{"type":"string","description":"Current activation step (for async progress tracking)","enum":["pending","registry","image","config","deployment","server","complete","error"]},"activationError":{"type":"string","description":"Error message if activation failed"},"fleetAppId":{"type":"number","description":"Fleet app ID"},"fleetRegistryId":{"type":"number","description":"Fleet Docker registry ID"},"fleetImageId":{"type":"string","description":"Fleet binary/image ID"},"fleetServerConfigId":{"type":"string","description":"Fleet server config ID"},"fleetDeploymentId":{"type":"number","description":"Fleet deployment ID (app location setting)"},"resourcePackageSlug":{"type":"string","description":"Resource package slug","example":"fleet-s1"},"locationCity":{"type":"string","description":"Deployment location city","example":"frankfurt"},"fleetRegion":{"type":"string","description":"Fleet region (deprecated, use locationCity)","example":"eu-west"},"maxFunctions":{"type":"number","description":"Maximum functions allowed","example":10},"maxMemoryPerFunction":{"type":"number","description":"Maximum memory per function (MB)","example":512},"dependencies":{"description":"NPM dependencies shared by all functions","type":"array","items":{"$ref":"#/components/schemas/FunctionDependency"}},"envVars":{"description":"Environment variables shared by all functions (secrets masked)","type":"array","items":{"$ref":"#/components/schemas/FunctionEnvVar"}}},"required":["isActivated","maxFunctions","maxMemoryPerFunction"]},"VerifyCredentialsResponse":{"type":"object","properties":{"valid":{"type":"boolean","description":"Whether credentials are valid"},"email":{"type":"string","description":"User email"}},"required":["valid","email"]},"ResourcePackage":{"type":"object","properties":{"id":{"type":"number","description":"Resource package ID","example":1},"name":{"type":"string","description":"Resource package name","example":"Fleet S1"},"slug":{"type":"string","description":"URL-friendly slug","example":"fleet-s1"},"type":{"type":"string","description":"Package type","enum":["shared","dedicated"],"example":"shared"},"cpuLimit":{"type":"number","description":"CPU limit (cores)","example":0.5},"memoryLimitMiB":{"type":"number","description":"Memory limit in MiB","example":512}},"required":["id","name","slug","type","cpuLimit","memoryLimitMiB"]},"Location":{"type":"object","properties":{"city":{"type":"string","description":"City code","example":"frankfurt"},"cityDisplay":{"type":"string","description":"Display name","example":"Frankfurt, Germany"},"continent":{"type":"string","description":"Continent","example":"europe"},"country":{"type":"string","description":"Country code","example":"de"},"isProtected":{"type":"boolean","description":"Whether location is DDoS protected"}},"required":["city","continent","country"]},"ActivateFunctionsRequest":{"type":"object","properties":{"resourcePackage":{"type":"string","description":"Resource package slug. Defaults to \"fleet-s1\" when omitted.","example":"fleet-s1"},"locationCity":{"type":"string","description":"Deployment location city","example":"frankfurt"},"locationCityDisplay":{"type":"string","description":"Deployment location city display name","example":"Frankfurt, Germany"},"locationContinent":{"type":"string","description":"Deployment location continent","example":"europe"},"locationCountry":{"type":"string","description":"Deployment location country","example":"de"},"locationIsProtected":{"type":"boolean","description":"Whether location is DDoS protected. Defaults to false when omitted."}},"required":["locationCity","locationContinent","locationCountry"]},"ActivateFunctionsResponse":{"type":"object","properties":{"message":{"type":"string","description":"Status message","example":"Activation started"}},"required":["message"]},"UpdateFunctionSettingsRequest":{"type":"object","properties":{"fleetRegion":{"type":"string","description":"Fleet region"},"maxFunctions":{"type":"number","description":"Maximum functions allowed (requires upgrade)"},"maxMemoryPerFunction":{"type":"number","description":"Maximum memory per function (MB)"}}},"UpdateDependenciesRequest":{"type":"object","properties":{"dependencies":{"description":"NPM dependencies for the project","type":"array","items":{"$ref":"#/components/schemas/FunctionDependency"}}},"required":["dependencies"]},"UpdateDependenciesResponse":{"type":"object","properties":{"settings":{"description":"Updated settings","allOf":[{"$ref":"#/components/schemas/FunctionSettings"}]},"message":{"type":"string","description":"Status message","example":"Dependencies updated. Runtime restarting..."}},"required":["settings","message"]},"SetEnvVarRequest":{"type":"object","properties":{"key":{"type":"string","description":"Environment variable name","example":"API_KEY"},"value":{"type":"string","description":"Environment variable value","example":"sk-..."},"isSecret":{"type":"boolean","description":"Whether this is a secret. Defaults to false when omitted."}},"required":["key","value"]},"UpdateEnvVarsRequest":{"type":"object","properties":{"envVars":{"description":"Environment variables for the project","type":"array","items":{"$ref":"#/components/schemas/SetEnvVarRequest"}}},"required":["envVars"]},"UpdateEnvVarsResponse":{"type":"object","properties":{"settings":{"description":"Updated settings","allOf":[{"$ref":"#/components/schemas/FunctionSettings"}]},"message":{"type":"string","description":"Status message","example":"Environment variables updated. Runtime restarting..."}},"required":["settings","message"]},"NpmPackageInfo":{"type":"object","properties":{"name":{"type":"string","description":"Package name","example":"node-mailjet"},"versions":{"description":"All available versions (sorted newest first)","example":["6.0.6","6.0.5","6.0.4"],"type":"array","items":{"type":"string"}},"distTags":{"type":"object","additionalProperties":{"type":"string"},"description":"Dist tags (latest, next, beta, etc.)","example":{"latest":"6.0.6","next":"7.0.0-beta.1"}}},"required":["name","versions","distTags"]},"RuntimeStatusResponse":{"type":"object","properties":{"status":{"type":"string","description":"Runtime status","enum":["running","stopped","starting","stopping","unknown"]},"serverCount":{"type":"number","description":"Number of running servers"},"fleetDeploymentId":{"type":"number","description":"Fleet deployment ID"},"message":{"type":"string","description":"Status message"},"runtimeVersion":{"type":"string","description":"Runtime version this deployment is pinned to (null = legacy/unpinned)","example":"v1","nullable":true},"latestVersion":{"type":"string","description":"The latest available runtime version","example":"v1"},"upgradeAvailable":{"type":"boolean","description":"Whether a newer runtime version is available to upgrade to"}},"required":["status","message"]},"StartRuntimeResponse":{"type":"object","properties":{"message":{"type":"string","description":"Status message","example":"Runtime started successfully"},"status":{"type":"string","description":"New runtime status","enum":["running","stopped","starting","stopping","unknown"]}},"required":["message","status"]},"StopRuntimeResponse":{"type":"object","properties":{"message":{"type":"string","description":"Status message","example":"Runtime stopped successfully"},"status":{"type":"string","description":"New runtime status","enum":["running","stopped","starting","stopping","unknown"]}},"required":["message","status"]},"RuntimeVersion":{"type":"object","properties":{"version":{"type":"string","description":"Immutable version identifier","example":"v1"},"node":{"type":"number","description":"Node.js major version baked into the image","example":20},"releasedAt":{"type":"string","description":"ISO date the version was published","example":"2026-06-03"},"notes":{"type":"string","description":"Summary of what changed in this version"},"breaking":{"type":"boolean","description":"Whether upgrading to this version may break existing function code"}},"required":["version","node","releasedAt","notes","breaking"]},"RuntimeVersionsResponse":{"type":"object","properties":{"current":{"type":"string","description":"Version the project is currently pinned to (null = legacy/unpinned)","nullable":true,"example":"v1"},"latest":{"type":"string","description":"The latest available runtime version","example":"v1"},"upgradeAvailable":{"type":"boolean","description":"Whether a newer runtime version is available"},"versions":{"description":"All published runtime versions (oldest first)","type":"array","items":{"$ref":"#/components/schemas/RuntimeVersion"}}},"required":["current","latest","upgradeAvailable","versions"]},"UpgradeRuntimeRequest":{"type":"object","properties":{"version":{"type":"string","description":"Runtime version to deploy (must be a known version)","example":"v1"}},"required":["version"]},"FunctionDeployment":{"type":"object","properties":{"id":{"type":"string","description":"Deployment UUID"},"functionId":{"type":"string","description":"Function ID"},"versionId":{"type":"string","description":"Deployed version ID"},"status":{"type":"string","description":"Deployment status","enum":["pending","deploying","running","stopped","failed"]},"fleetServiceId":{"type":"string","description":"Fleet service ID"},"fleetDeploymentId":{"type":"string","description":"Fleet deployment ID"},"serviceIp":{"type":"string","description":"Service IP address (internal)"},"servicePort":{"type":"number","description":"Service port (internal)"},"invokeUrl":{"type":"string","description":"Invoke URL (external via Cortex proxy)"},"lastError":{"type":"string","description":"Last error message"},"requestCount":{"type":"number","description":"Total request count","example":0},"lastInvokedAt":{"format":"date-time","type":"string","description":"Last invocation timestamp"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"}},"required":["id","functionId","versionId","status","requestCount","createdAt","updatedAt"]},"Function":{"type":"object","properties":{"id":{"type":"string","description":"Function UUID"},"tenantId":{"type":"string","description":"Tenant ID"},"projectId":{"type":"string","description":"Project ID"},"name":{"type":"string","description":"Function name","example":"process-webhook"},"slug":{"type":"string","description":"URL-friendly identifier","example":"process-webhook"},"description":{"type":"string","description":"Function description"},"draftCode":{"type":"string","description":"Draft source code"},"draftEntrypoint":{"type":"string","description":"Entrypoint function name","example":"handler"},"runtime":{"type":"string","description":"Runtime environment","enum":["nodejs20","nodejs22"]},"authMode":{"type":"string","description":"Authentication mode: `public` (none), `api_key` (a Cortex API key), `signed` (HMAC like webhooks) or `participant` (a participant token — the function receives the verified identity as `event.auth`)","enum":["public","api_key","signed","participant"]},"timeout":{"type":"number","description":"Execution timeout in milliseconds","example":30000},"memory":{"type":"number","description":"Memory limit in MB","example":256},"envVars":{"description":"Environment variables (secrets masked)","type":"array","items":{"$ref":"#/components/schemas/FunctionEnvVar"}},"publishedVersionId":{"type":"string","description":"Published version ID (if published)"},"status":{"type":"string","description":"Function status: draft (not published) or published (available in runtime)","enum":["draft","published"]},"activeDeployment":{"description":"Active deployment info (deprecated - use status field instead)","deprecated":true,"allOf":[{"$ref":"#/components/schemas/FunctionDeployment"}]},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"},"updatedAt":{"format":"date-time","type":"string","description":"Last update timestamp"}},"required":["id","tenantId","projectId","name","slug","draftCode","draftEntrypoint","runtime","authMode","timeout","memory","envVars","status","createdAt","updatedAt"]},"CreateFunctionRequest":{"type":"object","properties":{"name":{"type":"string","description":"Function name","example":"process-webhook"},"slug":{"type":"string","description":"URL-friendly slug (auto-generated if not provided)","example":"process-webhook"},"description":{"type":"string","description":"Function description"},"code":{"type":"string","description":"Initial source code","example":"exports.handler = async (event, ctx) => { return { statusCode: 200, body: \"Hello\" }; };"},"entrypoint":{"type":"string","description":"Entrypoint function name. Defaults to \"handler\" when omitted."},"runtime":{"type":"string","description":"Runtime environment. Defaults to \"nodejs20\" when omitted.","enum":["nodejs20","nodejs22"]},"authMode":{"type":"string","description":"Authentication mode. Defaults to \"api_key\" when omitted. Use `participant` to let game clients call this function directly with a participant token; the verified identity arrives as `event.auth`.","enum":["public","api_key","signed","participant"]},"timeout":{"type":"number","description":"Execution timeout in ms (1000-300000). Defaults to 30000 when omitted."},"memory":{"type":"number","description":"Memory limit in MB (128-1024). Defaults to 256 when omitted."},"envVars":{"description":"Initial environment variables","type":"array","items":{"$ref":"#/components/schemas/SetEnvVarRequest"}}},"required":["name","code"]},"UpdateFunctionRequest":{"type":"object","properties":{"name":{"type":"string","description":"Function name"},"description":{"type":"string","description":"Function description"},"draftCode":{"type":"string","description":"Draft source code"},"draftEntrypoint":{"type":"string","description":"Entrypoint function name"},"runtime":{"type":"string","description":"Runtime environment","enum":["nodejs20","nodejs22"]},"authMode":{"type":"string","description":"Authentication mode. Use `participant` to let game clients call this function directly with a participant token; the verified identity arrives as `event.auth`.","enum":["public","api_key","signed","participant"]},"timeout":{"type":"number","description":"Execution timeout in ms (1000-300000)"},"memory":{"type":"number","description":"Memory limit in MB (128-1024)"},"envVars":{"description":"Updated environment variables (replaces all)","type":"array","items":{"$ref":"#/components/schemas/SetEnvVarRequest"}}}},"DeleteFunctionResponse":{"type":"object","properties":{"message":{"type":"string","description":"Status message","example":"Function deleted successfully"}},"required":["message"]},"FunctionVersion":{"type":"object","properties":{"id":{"type":"string","description":"Version UUID"},"functionId":{"type":"string","description":"Function ID"},"version":{"type":"number","description":"Version number","example":1},"code":{"type":"string","description":"Source code (frozen)"},"entrypoint":{"type":"string","description":"Entrypoint function name"},"status":{"type":"string","description":"Build status","enum":["building","ready","failed"]},"buildLogs":{"type":"string","description":"Build logs output"},"buildError":{"type":"string","description":"Build error message"},"runtime":{"type":"string","description":"Runtime environment","enum":["nodejs20","nodejs22"]},"timeout":{"type":"number","description":"Execution timeout in ms"},"memory":{"type":"number","description":"Memory limit in MB"},"publishedBy":{"type":"string","description":"User who published this version"},"changelog":{"type":"string","description":"Changelog description"},"createdAt":{"format":"date-time","type":"string","description":"Creation timestamp"}},"required":["id","functionId","version","code","entrypoint","status","runtime","timeout","memory","createdAt"]},"PublishFunctionRequest":{"type":"object","properties":{"changelog":{"type":"string","description":"Changelog/description of changes"}}},"PublishFunctionResponse":{"type":"object","properties":{"version":{"description":"The published version","allOf":[{"$ref":"#/components/schemas/FunctionVersion"}]},"message":{"type":"string","description":"Status message","example":"Version 1 published successfully"}},"required":["version","message"]},"DeployFunctionResponse":{"type":"object","properties":{"deployment":{"description":"The deployment","allOf":[{"$ref":"#/components/schemas/FunctionDeployment"}]},"invokeUrl":{"type":"string","description":"Public invoke URL","example":"https://cortex.odin.4players.io/invoke/proj123/my-function"},"message":{"type":"string","description":"Status message","example":"Deployment started"}},"required":["deployment","invokeUrl","message"]},"StopFunctionResponse":{"type":"object","properties":{"message":{"type":"string","description":"Status message","example":"Function stopped successfully"}},"required":["message"]},"IssueParticipantTokenRequest":{"type":"object","properties":{"externalUserId":{"type":"string","description":"The game's own user identifier of the player. The participant is created on first use.","example":"steam:76561198000000000"},"displayName":{"type":"string","description":"Display name to store on the participant (updated when it changed).","example":"Alice"},"gatheringId":{"type":"string","description":"Bind the token to a gathering. The client may then read that gathering and mark itself ready, and only sees transcripts of the session this gathering started.","example":"0f4a7c1e-6a5b-4f28-9f7e-2b1d6b3c9a10"},"roomId":{"type":"string","description":"Bind the token to an ODIN room. When set, an ODIN voice token for that room is issued alongside the participant token, saving the client a second round trip.","example":"boss-room-lobby-17"},"ttlSeconds":{"type":"number","description":"Requested lifetime in seconds. Clamped to 900 (the maximum and the default).","minimum":1,"maximum":900,"default":900,"example":300}},"required":["externalUserId"]},"OdinTokenRestrictions":{"type":"object","properties":{"tags":{"description":"Tags requested for the peer. `cortex:muted` marks a player with an active `mute`, `listen_only` or `text_only` sanction — every client in the room sees it on `PeerJoined` and can mask the peer at once.","example":["cortex:muted"],"type":"array","items":{"type":"string"}},"lifetimeSeconds":{"type":"number","description":"Requested token lifetime in seconds.","example":300},"applied":{"type":"boolean","description":"Whether the tags and lifetime are actually in the token. Only the `access_key` provider can sign them; with `rooms` (Payment) or `webservice` the token is issued without them and this is false — rely on the `cortex.mutes` room frame instead.","example":true},"provider":{"type":"string","description":"The project's token provider.","enum":["access_key","webservice","rooms"],"example":"access_key"}},"required":["tags","lifetimeSeconds","applied","provider"]},"IssueParticipantTokenResponse":{"type":"object","properties":{"token":{"type":"string","description":"The participant token: a compact EdDSA (Ed25519) JWT. Send it as `Authorization: Bearer <token>` to `/client/v1/*`.","example":"eyJhbGciOiJFZERTQSIsInR5cCI6IkpXVCIsImtpZCI6Ii4uLiJ9.eyJpc3MiOiJvZGluLWNvcnRleCIsInN1YiI6Ii4uLiJ9.…"},"expiresAt":{"type":"string","description":"When the token stops being accepted (ISO 8601).","example":"2026-09-22T12:15:00.000Z"},"participantId":{"type":"string","description":"Cortex participant UUID the token speaks for (the `sub` claim).","example":"0f4a7c1e-6a5b-4f28-9f7e-2b1d6b3c9a10"},"scopes":{"description":"Capabilities of the token (the `scp` claim).","example":["me","messages.read"],"type":"array","items":{"type":"string"}},"odinToken":{"type":"string","description":"ODIN voice token for `roomId`, issued with the project's configured token provider. Only present when `roomId` was given and generation succeeded.","nullable":true},"odinTokenRestrictions":{"description":"What the join gate put into `odinToken` (lifetime, `cortex:muted` for a muted player) and whether the token provider could sign it. Present whenever `roomId` was given.","allOf":[{"$ref":"#/components/schemas/OdinTokenRestrictions"}]},"activeSanctions":{"description":"Active sanctions of the player at issue time. A ban never gets here — it refuses the request with 403; a voice mute tags the ODIN token (see `odinTokenRestrictions`).","type":"array","items":{"$ref":"#/components/schemas/SanctionResponse"}}},"required":["token","expiresAt","participantId","scopes"]},"JoinRefusedResponse":{"type":"object","properties":{"statusCode":{"type":"number","example":403},"error":{"type":"string","example":"Forbidden"},"message":{"type":"string","example":"The player is banned until 2026-09-23T12:00:00.000Z."},"sanction":{"description":"The ban that refused the token — the one that lasts longest.","allOf":[{"$ref":"#/components/schemas/SanctionResponse"}]}},"required":["statusCode","error","message","sanction"]},"RevokeParticipantTokensResponse":{"type":"object","properties":{"projectId":{"type":"string","description":"The project whose tokens were invalidated.","example":"abc123"},"tokenEpoch":{"type":"number","description":"The new token epoch. Every token issued with a lower `ep` claim is rejected from now on.","example":7},"effectiveWithinSeconds":{"type":"number","description":"Worst-case delay in seconds until every API instance enforces the new epoch (its cache TTL).","example":30}},"required":["projectId","tokenEpoch","effectiveWithinSeconds"]},"JsonWebKey":{"type":"object","properties":{"kty":{"type":"string","description":"Key type; always `OKP` for Ed25519.","example":"OKP"},"crv":{"type":"string","description":"Curve; always `Ed25519`.","example":"Ed25519"},"x":{"type":"string","description":"Base64url-encoded raw public key.","example":"11qYAYKxCrfVS_7TyWQHOg7hcvPapiMlrwIaaPcHURo"},"use":{"type":"string","description":"Intended use; always `sig`.","example":"sig"},"alg":{"type":"string","description":"Algorithm; always `EdDSA`.","example":"EdDSA"},"kid":{"type":"string","description":"Key identifier, matching the `kid` in a token's JWS header.","example":"ta8n1cS4E0bJmYyR1nQ0jl6SYt5bJ1lkC8cQdU-RGCk"}},"required":["kty","crv","x","use","alg","kid"]},"Jwks":{"type":"object","properties":{"keys":{"description":"The public keys participant tokens are verified with. Empty when the deployment issues no tokens.","type":"array","items":{"$ref":"#/components/schemas/JsonWebKey"}}},"required":["keys"]},"ClientIdentity":{"type":"object","properties":{"participantId":{"type":"string","description":"Cortex participant UUID (the `sub` claim).","example":"0f4a7c1e-6a5b-4f28-9f7e-2b1d6b3c9a10"},"externalUserId":{"type":"string","description":"The game's own user identifier (the `ext` claim).","example":"steam:76561198000000000"},"projectId":{"type":"string","description":"Project the token is scoped to (the `pid` claim).","example":"abc123"},"displayName":{"type":"string","description":"Display name of the participant.","nullable":true},"gatheringId":{"type":"string","description":"Gathering the token is bound to (the `gth` claim).","nullable":true},"roomId":{"type":"string","description":"ODIN room the token is bound to (the `rid` claim).","nullable":true},"scopes":{"description":"Capabilities of the token (the `scp` claim).","example":["me","messages.read"],"type":"array","items":{"type":"string"}},"expiresAt":{"type":"string","description":"When the token expires (ISO 8601). Fetch a new one from your backend before then.","example":"2026-09-22T12:15:00.000Z"},"userData":{"type":"object","description":"Arbitrary user data stored on the participant.","nullable":true}},"required":["participantId","externalUserId","projectId","scopes","expiresAt"]},"ClientSanctionList":{"type":"object","properties":{"sanctions":{"description":"Currently active sanctions of the token holder.","type":"array","items":{"$ref":"#/components/schemas/SanctionResponse"}},"muted":{"type":"boolean","description":"Whether any active sanction prevents speaking (`mute`, `listen_only`, or a ban).","example":false},"banned":{"type":"boolean","description":"Whether any active sanction bans the holder from the project.","example":false}},"required":["sanctions","muted","banned"]},"SendRoomMessageRequest":{"type":"object","properties":{"type":{"type":"string","description":"Frame type the clients switch on. 1–64 characters: letters, digits and `._:-`, starting with a letter or digit. Types starting with `cortex.` are reserved for frames Cortex emits itself.","example":"match.countdown","pattern":"^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$"},"data":{"type":"object","description":"Frame payload, delivered to the clients as `data`. Any JSON object. The whole encoded frame must not exceed 8 KB; larger messages are rejected with 413.","additionalProperties":true,"example":{"secondsLeft":10}},"targetExternalUserIds":{"description":"Deliver only to the peers of these players (your own user ids, as used in their ODIN tokens). Omit to send to everyone in the room. Players who are not in the room are skipped. At most 64.","example":["steam:76561198000000000"],"type":"array","items":{"type":"string"}}},"required":["type","data"]},"SendRoomMessageResponse":{"type":"object","properties":{"id":{"type":"string","description":"Frame id — the clients receive it as the frame `id`","example":"7d0c4c2e-3f7b-4d0a-9f7e-1c2b3a4d5e6f"},"sessionId":{"type":"string","description":"Session whose room the message was sent into"},"type":{"type":"string","description":"The frame type","example":"match.countdown"},"sizeBytes":{"type":"number","description":"Encoded frame size in bytes (header included)","example":64},"acceptedAt":{"type":"string","description":"When Cortex accepted the message (ISO 8601)"}},"required":["id","sessionId","type","sizeBytes","acceptedAt"]}}}}