openapi: 3.1.0
info:
  title: Ringnity Developer Platform API
  version: 0.2.0-draft
  summary: Public REST contract for Ringnity SDKs and customer backend integrations.
  description: |
    Draft OpenAPI contract for the Ringnity Developer Platform.

    This contract powers Scalar docs, generated SDK clients, Bruno tests, and
    backend conformance checks. Realtime chat, internet call, AI streaming, and
    live report updates are documented separately in the Ringnity realtime
    contract.
  contact:
    name: Ringnity Support
    email: support@ringnity.com
servers:
  - url: https://api.ringnity.com/api
    description: Production
  - url: http://localhost:8192/api
    description: Local development
x-ringnity-contract-status: draft
x-ringnity-oauth-scope-coverage:
  description: |
    OpenAPI coverage map for OAuth scopes that are still marked `planned` in the
    dashboard scope catalog. This map documents the intended public REST
    operation coverage without enabling the scopes at runtime. Scopes with
    `openApiStatus: no-public-operation-yet` intentionally remain visible as
    roadmap scopes only.
  plannedScopes:
    offline_access:
      openApiStatus: documented
      operationIds: [exchangeRingnityOAuthToken, revokeRingnityOAuthToken]
      notes: Refresh-token issuance/exchange/revoke is represented by the OAuth token lifecycle endpoints.
    user.profile.read:
      openApiStatus: documented
      operationIds: [getRingnityOAuthUserInfo, getAgentProfile]
      notes: OIDC userinfo covers identity profile claims; Agent CRM profile covers runtime agent identity.
    user.profile.write:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Profile mutation is still dashboard/internal and should not be exposed as public OAuth API until policy and audit are finalized.
    user.avatar.read:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Avatar metadata is not yet exposed as a standalone public REST contract.
    user.avatar.write:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Avatar upload remains internal until media ownership and audit rules are public.
    tenant.profile.read:
      openApiStatus: documented
      operationIds: [getServerTenant, getAdminTenant, getRingnityOAuthUserInfo]
      notes: Tenant identity/profile reads are covered through Server API, Admin SDK, and OIDC claims.
    tenant.profile.write:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Tenant profile mutation is not yet a public OAuth-backed contract.
    tenant.branding.read:
      openApiStatus: documented
      operationIds: [bootstrapRuntime, getAdminTenant]
      notes: Runtime bootstrap and Admin SDK tenant data expose read-only branding/runtime identity.
    tenant.branding.write:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Branding mutation remains internal until media upload and audit policy are public.
    tenant.settings.read:
      openApiStatus: documented
      operationIds: [getDeveloperSettings, getAdminReadiness, getAdminServices]
      notes: Read-only developer/admin settings are already present in the public contract.
    tenant.settings.write:
      openApiStatus: partial
      operationIds: [updateDeveloperAllowedDomains]
      notes: Only allowed-domain settings have a public mutation contract today.
    tenant.domains.read:
      openApiStatus: documented
      operationIds: [getAdminDomains, getDeveloperSettings]
      notes: Domain reads are covered by Admin SDK domains and dashboard developer settings.
    tenant.domains.write:
      openApiStatus: documented
      operationIds: [updateDeveloperAllowedDomains]
      notes: Allowed-domain replacement is documented as the public write surface.
    staff.read:
      openApiStatus: documented
      operationIds: [getAdminStaffSummary, getAgentProfile]
      notes: Current public surface is summary/profile read, not full staff directory export.
    staff.write:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Staff mutation remains dashboard/internal pending permission and audit review.
    staff.invite:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Staff invite is not yet exposed as public OAuth API.
    staff.disable:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Staff disable is not yet exposed as public OAuth API.
    runtime.tokens.write:
      openApiStatus: documented
      operationIds: [createRuntimeSession, createVisitorContextToken, createAgentToken, createAdminToken]
      notes: Runtime token/session issuance is covered by SDK session and Server API token endpoints.
    runtime.sessions.read:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Active runtime-session listing is not yet part of the public REST contract.
    runtime.sessions.write:
      openApiStatus: documented
      operationIds: [createRuntimeSession, registerDevicePushToken, unregisterDevicePushToken]
      notes: Session creation and device registration lifecycle are documented.
    agents.read:
      openApiStatus: documented
      operationIds: [getAgentProfile, getAdminStaffSummary]
      notes: Agent profile and staff summary are public read surfaces; full dashboard agent lists remain internal.
    agents.write:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Agent profile/staff mutation is not yet exposed as public OAuth API.
    agents.presence.read:
      openApiStatus: documented
      operationIds: [getAgentPresence]
      notes: Agent SDK presence read is documented.
    agents.presence.write:
      openApiStatus: documented
      operationIds: [updateAgentPresence]
      notes: Agent SDK presence update is documented.
    contacts.read:
      openApiStatus: documented
      operationIds: [listContacts, getContact]
      notes: Server API contact reads are documented.
    contacts.write:
      openApiStatus: documented
      operationIds: [upsertContact, updateContact]
      notes: Server API contact upsert/update are documented.
    contacts.delete:
      openApiStatus: documented
      operationIds: [deleteContact]
      notes: Contact deletion is exposed through the tenant-scoped Server API contact endpoint.
    conversations.read:
      openApiStatus: documented
      operationIds: [listAgentConversations, getAgentConversation, listAgentConversationMessages, getVisitorConversation]
      notes: Agent and visitor conversation read contracts are documented.
    conversations.write:
      openApiStatus: documented
      operationIds: [createVisitorConversation, sendVisitorConversationMessage, editVisitorConversationMessage, sendAgentConversationMessage, markAgentMessageRead]
      notes: Message send/edit/read workflows are documented.
    conversations.assign:
      openApiStatus: documented
      operationIds: [assignAgentConversation]
      notes: Agent SDK assignment supports accepting, assigning, or transferring a conversation within tenant visibility rules.
    conversations.close:
      openApiStatus: documented
      operationIds: [closeAgentConversation]
      notes: Agent conversation close is documented.
    calls.read:
      openApiStatus: documented
      operationIds: [getCallSession, getServerReportSummary, getAdminReportSummary]
      notes: Call session reads and reporting surfaces are documented.
    calls.write:
      openApiStatus: documented
      operationIds: [createCallSession, endCallSession]
      notes: Product-level call session create/end are documented.
    calls.recordings.read:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Recording access policy is not yet public.
    calls.recordings.delete:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Recording deletion is not yet public.
    ai.read:
      openApiStatus: documented
      operationIds: [getAiVisitorRouting, getAdminAiReadiness]
      notes: Visitor AI routing and admin readiness are documented.
    ai.write:
      openApiStatus: documented
      operationIds: [createAiVisitorConversation, sendAiVisitorMessage, createAiVisitorRealtimeCall]
      notes: Visitor AI conversation/chat/realtime-call write surfaces are documented.
    ai.knowledge.read:
      openApiStatus: documented
      operationIds: [searchAiVisitorKnowledge]
      notes: Visitor knowledge search is documented; admin knowledge metadata is still internal.
    ai.knowledge.write:
      openApiStatus: documented
      operationIds: [uploadAdminKnowledgeDocument, ingestAdminKnowledgeUrl, reindexAdminKnowledge, deleteAdminKnowledge]
      notes: Admin SDK knowledge write operations proxy document upload, URL ingest, reindex, and delete to the AI service.
    ai.usage.read:
      openApiStatus: partial
      operationIds: [getAdminAiReadiness, getServerReportSummary, getAdminReportSummary]
      notes: Usage is currently visible only through readiness/report summaries.
    reports.read:
      openApiStatus: documented
      operationIds: [getServerReportSummary, getServerReportTimeseries, getAdminReportSummary, getAdminReportTimeseries]
      notes: Server API and Admin SDK reports are documented.
    reports.export:
      openApiStatus: documented
      operationIds: [exportServerReport, exportAdminReport]
      notes: Server API and Admin SDK report exports are available as JSON or CSV downloads.
    webhooks.read:
      openApiStatus: documented
      operationIds: [listWebhooks, listWebhookDeliveries]
      notes: Webhook config and delivery reads are documented.
    webhooks.write:
      openApiStatus: documented
      operationIds: [createWebhook, updateWebhook]
      notes: Webhook create/update are documented.
    webhooks.delete:
      openApiStatus: documented
      operationIds: [disableWebhook]
      notes: The public contract disables webhook endpoints rather than hard-deleting them.
    webhooks.test:
      openApiStatus: documented
      operationIds: [testWebhook]
      notes: Webhook test delivery is documented.
    developer.write:
      openApiStatus: documented
      operationIds: [updateDeveloperAllowedDomains, testDeveloperSdkSettings]
      notes: Public developer settings writes are currently limited to allowed domains and readiness testing.
    sdk.keys.write:
      openApiStatus: documented
      operationIds: [createServerApiCredential, updateServerApiCredential, rotateServerApiCredential, disableServerApiCredential]
      notes: Server API key lifecycle writes are documented.
    oauth.clients.read:
      openApiStatus: documented
      operationIds: [listOAuthClients]
      notes: OAuth client registry read is documented.
    oauth.clients.write:
      openApiStatus: documented
      operationIds: [createOAuthClient, updateOAuthClient, rotateOAuthClientSecret, revokeOAuthClient, deleteRevokedOAuthClient]
      notes: OAuth client lifecycle writes are documented.
    media.read:
      openApiStatus: partial
      operationIds: [listAgentConversationMessages, getVisitorConversation]
      notes: Media can appear in conversation payloads, but there is no standalone media-read API yet.
    media.write:
      openApiStatus: documented
      operationIds: [createVisitorConversationAttachment, uploadVisitorConversationAttachment]
      notes: Visitor chat attachment create/upload are documented.
    media.delete:
      openApiStatus: documented
      operationIds: [deleteAgentConversationAttachment]
      notes: Agent SDK can remove a conversation attachment by soft-deleting the attachment message for auditability.
    billing.read:
      openApiStatus: documented
      operationIds: [getAdminSubscription]
      notes: Admin subscription read is documented.
    billing.write:
      openApiStatus: no-public-operation-yet
      operationIds: []
      notes: Billing mutation remains outside the public developer contract.
tags:
  - name: Runtime Bootstrap
    description: Public website and mobile runtime bootstrap APIs.
  - name: Developer Settings
    description: Dashboard developer settings and Server API key management.
  - name: Dashboard Auth
    description: First-party owner and workspace JWT sessions for ringnity.com and tenant dashboards.
  - name: Login with Ringnity
    description: OAuth2 and OpenID Connect identity login for WordPress, CMS, CRM, mobile apps, and partner portals.
  - name: Server API
    description: Customer backend APIs using secret Server API keys.
  - name: Webhooks
    description: Customer backend webhook configuration and delivery history.
  - name: Agent CRM
    description: Agent/CRM HTTP APIs using short-lived agent tokens.
  - name: Admin Reports
    description: Admin readiness, tenant status, service status, and reporting APIs.
  - name: Platform Administration
    description: Platform-owner APIs for Ringnity product configuration.
  - name: Calls
    description: Product-level internet audio/video call APIs that hide media transport details.
  - name: Chat
    description: Chat conversation REST APIs.
  - name: AI
    description: AI chat, routing, and readiness REST APIs.
paths:
  /sdk/health:
    get:
      tags: [Runtime Bootstrap]
      operationId: getRuntimeHealth
      summary: Check public SDK runtime health.
      responses:
        "200":
          description: Runtime API is reachable.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /sdk/bootstrap:
    get:
      tags: [Runtime Bootstrap]
      operationId: bootstrapRuntime
      summary: Resolve tenant branding, services, and runtime capabilities.
      parameters:
        - $ref: "#/components/parameters/TenantSlugQuery"
        - $ref: "#/components/parameters/PublicKeyQuery"
        - $ref: "#/components/parameters/DebugQuery"
      responses:
        "200":
          description: Tenant runtime bootstrap.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RuntimeBootstrapEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /sdk/session:
    post:
      tags: [Runtime Bootstrap]
      operationId: createRuntimeSession
      summary: Create a short-lived runtime session token.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateRuntimeSessionRequest"
      responses:
        "201":
          description: Runtime session created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RuntimeSessionEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /sdk/devices/push-token:
    post:
      tags: [Runtime Bootstrap]
      operationId: registerDevicePushToken
      summary: Register or refresh a mobile push token for SDK runtime notifications.
      security:
        - RuntimeBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RegisterDevicePushTokenRequest"
      responses:
        "201":
          description: Device push token registered.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DevicePushTokenEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"
    delete:
      tags: [Runtime Bootstrap]
      operationId: unregisterDevicePushToken
      summary: Disable a previously registered SDK device push token.
      security:
        - RuntimeBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UnregisterDevicePushTokenRequest"
      responses:
        "200":
          description: Device push token disabled.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DevicePushTokenDisabledEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /auth/owner-login:
    post:
      tags: [Dashboard Auth]
      operationId: loginOwner
      summary: Sign in a tenant owner on ringnity.com.
      description: Returns an owner-scoped JWT pair. The response keeps `sessionToken` as a deprecated alias of `accessToken`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OwnerLoginRequest"
      responses:
        "200":
          description: Owner session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OwnerSessionEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /auth/owner-google-login:
    post:
      tags: [Dashboard Auth]
      operationId: loginOwnerWithGoogle
      summary: Sign in a tenant owner with Google Identity.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OwnerGoogleLoginRequest"
      responses:
        "200":
          description: Owner session or registration handoff.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OwnerGoogleLoginEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /auth/owner-me:
    get:
      tags: [Dashboard Auth]
      operationId: getOwnerSession
      summary: Read the current owner session profile.
      security:
        - OwnerBearer: []
      responses:
        "200":
          description: Owner session profile.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OwnerMeEnvelope"
        "401":
          $ref: "#/components/responses/ErrorResponse"

  /auth/owner-refresh:
    post:
      tags: [Dashboard Auth]
      operationId: refreshOwnerSession
      summary: Rotate an owner refresh token and return a new owner access token.
      description: Uses the `ownerRefreshToken` httpOnly cookie by default. A body refresh token may be supplied by trusted clients.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RefreshTokenRequest"
      responses:
        "200":
          description: Refreshed owner token pair.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OwnerRefreshEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /auth/owner-logout:
    post:
      tags: [Dashboard Auth]
      operationId: logoutOwner
      summary: Revoke the owner refresh token and clear owner auth cookies.
      responses:
        "200":
          description: Owner logged out.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /auth/workspace-entry-token:
    post:
      tags: [Dashboard Auth]
      operationId: createWorkspaceEntryToken
      summary: Create a one-time workspace auto-login token from an owner token.
      description: Bridges ringnity.com owner auth into a separate workspace session on `{slug}.ringnity.com`.
      security:
        - OwnerBearer: []
      responses:
        "200":
          description: One-time workspace entry token.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceEntryTokenEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /auth/workspace-login:
    post:
      tags: [Dashboard Auth]
      operationId: loginWorkspace
      summary: Sign in a workspace user on `{slug}.ringnity.com`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WorkspaceLoginRequest"
      responses:
        "200":
          description: Workspace session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceSessionEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /auth/workspace-auto-login:
    get:
      tags: [Dashboard Auth]
      operationId: autoLoginWorkspace
      summary: Exchange a one-time workspace entry token for a workspace session.
      parameters:
        - name: token
          in: query
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Workspace session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceAutoLoginEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /auth/workspace-refresh:
    post:
      tags: [Dashboard Auth]
      operationId: refreshWorkspaceSession
      summary: Rotate a workspace refresh token and return a new workspace access token.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RefreshTokenRequest"
      responses:
        "200":
          description: Refreshed workspace token pair.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WorkspaceRefreshEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /auth/workspace-logout:
    post:
      tags: [Dashboard Auth]
      operationId: logoutWorkspace
      summary: Revoke a workspace refresh token.
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/RefreshTokenRequest"
      responses:
        "200":
          description: Workspace user logged out.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /sdk/developer:
    get:
      tags: [Developer Settings]
      operationId: getDeveloperSettings
      summary: Read dashboard Developer SDK settings for the logged-in tenant.
      security:
        - DashboardBearer: []
      responses:
        "200":
          description: Developer settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeveloperSettingsEnvelope"
        "401":
          $ref: "#/components/responses/ErrorResponse"

  /sdk/developer/allowed-domains:
    put:
      tags: [Developer Settings]
      operationId: updateDeveloperAllowedDomains
      summary: Replace allowed browser/mobile origins for public SDK runtime.
      security:
        - DashboardBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateAllowedDomainsRequest"
      responses:
        "200":
          description: Allowed domains updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DeveloperSettingsEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /sdk/developer/test:
    post:
      tags: [Developer Settings]
      operationId: testDeveloperSdkSettings
      summary: Run dashboard SDK readiness checks for a supplied origin.
      security:
        - DashboardBearer: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeveloperTestRequest"
      responses:
        "200":
          description: Developer SDK test result.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadinessEnvelope"

  /sdk/developer/server-api-credentials:
    get:
      tags: [Developer Settings]
      operationId: listServerApiCredentials
      summary: List Server API credentials for the logged-in tenant.
      security:
        - DashboardBearer: []
      responses:
        "200":
          description: Server API credentials.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServerApiCredentialListEnvelope"
    post:
      tags: [Developer Settings]
      operationId: createServerApiCredential
      summary: Create a Server API credential and return its secret once.
      security:
        - DashboardBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateServerApiCredentialRequest"
      responses:
        "201":
          description: Server API credential created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServerApiCredentialSecretEnvelope"

  /sdk/developer/server-api-credentials/{credentialId}:
    parameters:
      - $ref: "#/components/parameters/CredentialIdPath"
    patch:
      tags: [Developer Settings]
      operationId: updateServerApiCredential
      summary: Update a Server API credential name, scopes, environment, or active state.
      security:
        - DashboardBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateServerApiCredentialRequest"
      responses:
        "200":
          description: Server API credential updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServerApiCredentialEnvelope"
    delete:
      tags: [Developer Settings]
      operationId: disableServerApiCredential
      summary: Disable a Server API credential.
      security:
        - DashboardBearer: []
      responses:
        "200":
          description: Server API credential disabled.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServerApiCredentialEnvelope"

  /sdk/developer/server-api-credentials/{credentialId}/rotate:
    parameters:
      - $ref: "#/components/parameters/CredentialIdPath"
    post:
      tags: [Developer Settings]
      operationId: rotateServerApiCredential
      summary: Rotate a Server API credential secret and return the new secret once.
      security:
        - DashboardBearer: []
      responses:
        "200":
          description: Server API credential rotated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServerApiCredentialSecretEnvelope"

  /sdk/developer/oauth-clients:
    get:
      tags: [Developer Settings]
      operationId: listOAuthClients
      summary: List Login with Ringnity OAuth clients for the logged-in tenant.
      security:
        - DashboardBearer: []
      responses:
        "200":
          description: OAuth clients.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthClientListEnvelope"
    post:
      tags: [Developer Settings]
      operationId: createOAuthClient
      summary: Create a Login with Ringnity OAuth client and return its secret once when confidential.
      security:
        - DashboardBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateOAuthClientRequest"
      responses:
        "201":
          description: OAuth client created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthClientSecretEnvelope"

  /sdk/developer/oauth-clients/{oauthClientId}:
    parameters:
      - $ref: "#/components/parameters/OAuthClientIdPath"
    patch:
      tags: [Developer Settings]
      operationId: updateOAuthClient
      summary: Update a Login with Ringnity OAuth client.
      security:
        - DashboardBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateOAuthClientRequest"
      responses:
        "200":
          description: OAuth client updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthClientEnvelope"
    delete:
      tags: [Developer Settings]
      operationId: revokeOAuthClient
      summary: Revoke a Login with Ringnity OAuth client and its refresh tokens.
      security:
        - DashboardBearer: []
      responses:
        "200":
          description: OAuth client revoked.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthClientEnvelope"

  /sdk/developer/oauth-clients/{oauthClientId}/rotate-secret:
    parameters:
      - $ref: "#/components/parameters/OAuthClientIdPath"
    post:
      tags: [Developer Settings]
      operationId: rotateOAuthClientSecret
      summary: Rotate a confidential OAuth client secret and return the new secret once.
      security:
        - DashboardBearer: []
      responses:
        "200":
          description: OAuth client secret rotated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthClientSecretEnvelope"

  /sdk/developer/oauth-clients/{oauthClientId}/revoked:
    parameters:
      - $ref: "#/components/parameters/OAuthClientIdPath"
    delete:
      tags: [Developer Settings]
      operationId: deleteRevokedOAuthClient
      summary: Permanently delete a revoked Login with Ringnity OAuth client.
      description: |
        This operation only succeeds after the OAuth client has already been revoked.
        Active OAuth clients must be revoked first so refresh tokens are invalidated before the registry record is removed.
      security:
        - DashboardBearer: []
      responses:
        "200":
          description: Revoked OAuth client deleted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthClientDeletedEnvelope"
        "409":
          $ref: "#/components/responses/ErrorResponse"

  /platform/auth/login:
    post:
      tags: [Platform Administration]
      operationId: loginPlatformAdmin
      summary: Sign in to the Ringnity platform console.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlatformLoginRequest"
      responses:
        "200":
          description: Platform admin session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformSessionEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /platform/auth/refresh:
    post:
      tags: [Platform Administration]
      operationId: refreshPlatformAdminSession
      summary: Rotate a platform console refresh token and return a new session.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlatformRefreshRequest"
      responses:
        "200":
          description: Refreshed platform admin session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlatformSessionEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /platform/plan-contracts:
    get:
      tags: [Platform Administration]
      operationId: getPlatformPlanContracts
      summary: Read the canonical plan feature and quota contract matrix.
      description: |
        Returns the feature-key catalog, quota-key catalog, and per-plan
        contract matrix used by the Ringnity rule engine. This replaces legacy
        `plans.features` and `plans.max_*` fields.
      security:
        - PlatformBearer: []
      responses:
        "200":
          description: Plan contracts.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlanContractCatalogEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"
    put:
      tags: [Platform Administration]
      operationId: updatePlatformPlanContracts
      summary: Update the canonical plan feature and quota contract matrix.
      security:
        - PlatformBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PlanContractCatalog"
      responses:
        "200":
          description: Plan contracts updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PlanContractCatalogEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /platform/ai-balance:
    get:
      tags: [Platform Administration]
      operationId: getPlatformAiBalance
      summary: Read AI balance, usage, and overage projections for all tenants.
      description: |
        Returns platform-level AI balance visibility for tenant OpenAI-backed usage.
        The response combines tenant balance, current plan quotas, current-month AI
        usage logs, estimated OpenAI cost, retail charge, and alert levels.
      security:
        - PlatformBearer: []
      responses:
        "200":
          description: AI balance overview.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiBalanceOverviewEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /platform/ai-balance/{clientId}/top-up:
    parameters:
      - $ref: "#/components/parameters/ClientIdPath"
    post:
      tags: [Platform Administration]
      operationId: topUpPlatformAiBalance
      summary: Add AI prepaid balance for a tenant.
      security:
        - PlatformBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AiBalanceTopUpRequest"
      responses:
        "200":
          description: Tenant AI balance topped up.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiBalanceTopUpEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /platform/ai-balance/{clientId}/settings:
    parameters:
      - $ref: "#/components/parameters/ClientIdPath"
    put:
      tags: [Platform Administration]
      operationId: updatePlatformAiBalanceTenantSettings
      summary: Update tenant AI overage and auto top-up controls.
      security:
        - PlatformBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AiBalanceTenantSettingsRequest"
      responses:
        "200":
          description: Tenant AI balance settings updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiBalanceTenantSettingsEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /platform/ai-pricing:
    get:
      tags: [Platform Administration]
      operationId: getPlatformAiPricing
      summary: Read AI provider cost, retail markup, and guardrail settings.
      description: |
        Returns the editable AI billing settings used to estimate OpenAI-backed
        chat and voice cost, retail charge, and gross margin.
      security:
        - PlatformBearer: []
      responses:
        "200":
          description: AI pricing settings.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiPricingCatalogEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"
    put:
      tags: [Platform Administration]
      operationId: updatePlatformAiPricing
      summary: Update AI provider cost, retail markup, and guardrail settings.
      security:
        - PlatformBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AiPricingSettingsUpdate"
      responses:
        "200":
          description: AI pricing settings updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiPricingCatalogEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /server/health:
    get:
      tags: [Server API]
      operationId: getServerApiHealth
      summary: Check Server API credential health.
      security:
        - ServerApiKey: []
      responses:
        "200":
          description: Server API credential is valid.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"
        "401":
          $ref: "#/components/responses/ErrorResponse"

  /server/tenant:
    get:
      tags: [Server API]
      operationId: getServerTenant
      summary: Read tenant identity for the current Server API key.
      security:
        - ServerApiKey: []
      responses:
        "200":
          description: Tenant identity.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TenantEnvelope"

  /server/tenant/sdk-readiness:
    get:
      tags: [Server API]
      operationId: getSdkReadiness
      summary: Read service readiness for SDK launch checks.
      security:
        - ServerApiKey: []
      responses:
        "200":
          description: SDK readiness.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadinessEnvelope"

  /server/reports/summary:
    get:
      tags: [Server API]
      operationId: getServerReportSummary
      summary: Read call, chat, satisfaction, and agent productivity summary.
      security:
        - ServerApiKey: []
      parameters:
        - $ref: "#/components/parameters/ReportPeriodQuery"
        - $ref: "#/components/parameters/ReportStartDateQuery"
        - $ref: "#/components/parameters/ReportEndDateQuery"
        - $ref: "#/components/parameters/ReportAgentIdQuery"
      responses:
        "200":
          description: Report summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportSummaryEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /server/reports/timeseries:
    get:
      tags: [Server API]
      operationId: getServerReportTimeseries
      summary: Read call, chat, and message trend data.
      security:
        - ServerApiKey: []
      parameters:
        - $ref: "#/components/parameters/ReportPeriodQuery"
        - $ref: "#/components/parameters/ReportStartDateQuery"
        - $ref: "#/components/parameters/ReportEndDateQuery"
        - $ref: "#/components/parameters/ReportAgentIdQuery"
      responses:
        "200":
          description: Report timeseries.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportTimeseriesEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /server/reports/export:
    get:
      tags: [Server API]
      operationId: exportServerReport
      summary: Export call, chat, and agent productivity reports.
      description: Returns a downloadable JSON envelope by default, or CSV when `format=csv`.
      security:
        - ServerApiKey: []
      parameters:
        - $ref: "#/components/parameters/ReportPeriodQuery"
        - $ref: "#/components/parameters/ReportStartDateQuery"
        - $ref: "#/components/parameters/ReportEndDateQuery"
        - $ref: "#/components/parameters/ReportAgentIdQuery"
        - $ref: "#/components/parameters/ReportFormatQuery"
      responses:
        "200":
          description: Report export.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportExportEnvelope"
            text/csv:
              schema:
                type: string
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /server/visitor-context-token:
    post:
      tags: [Server API]
      operationId: createVisitorContextToken
      summary: Create a signed visitor context token for public runtime SDKs.
      security:
        - ServerApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateVisitorContextTokenRequest"
      responses:
        "201":
          description: Visitor context token created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SdkTokenEnvelope"

  /server/sdk-token/agent:
    post:
      tags: [Server API]
      operationId: createAgentToken
      summary: Create a short-lived token for CRM/agent SDK runtime.
      security:
        - ServerApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAgentTokenRequest"
      responses:
        "201":
          description: Agent token created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SdkTokenEnvelope"

  /server/sdk-token/admin:
    post:
      tags: [Server API]
      operationId: createAdminToken
      summary: Create a short-lived token for admin/reporting SDK runtime.
      security:
        - ServerApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAdminTokenRequest"
      responses:
        "201":
          description: Admin token created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SdkTokenEnvelope"

  /server/contacts:
    get:
      tags: [Server API]
      operationId: listContacts
      summary: List synced customer contacts.
      security:
        - ServerApiKey: []
      parameters:
        - $ref: "#/components/parameters/PageQuery"
        - $ref: "#/components/parameters/LimitQuery"
        - name: search
          in: query
          schema:
            type: string
        - name: tags
          in: query
          description: Comma-separated tag filter.
          schema:
            type: string
      responses:
        "200":
          description: Contact list.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactListEnvelope"
    post:
      tags: [Server API]
      operationId: upsertContact
      summary: Create or update a customer contact.
      security:
        - ServerApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactInput"
      responses:
        "200":
          description: Contact upserted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactEnvelope"

  /server/contacts/{id}:
    parameters:
      - $ref: "#/components/parameters/ContactIdPath"
    get:
      tags: [Server API]
      operationId: getContact
      summary: Read one customer contact.
      security:
        - ServerApiKey: []
      responses:
        "200":
          description: Contact.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactEnvelope"
    patch:
      tags: [Server API]
      operationId: updateContact
      summary: Update one customer contact.
      security:
        - ServerApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ContactPatchInput"
      responses:
        "200":
          description: Contact updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactEnvelope"
    delete:
      tags: [Server API]
      operationId: deleteContact
      summary: Delete one customer contact.
      security:
        - ServerApiKey: []
      responses:
        "200":
          description: Contact deleted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ContactDeletedEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /server/webhooks:
    get:
      tags: [Webhooks]
      operationId: listWebhooks
      summary: List webhook endpoints.
      security:
        - ServerApiKey: []
      responses:
        "200":
          description: Webhook list.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookListEnvelope"
    post:
      tags: [Webhooks]
      operationId: createWebhook
      summary: Create a webhook endpoint.
      security:
        - ServerApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookInput"
      responses:
        "201":
          description: Webhook created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEnvelope"

  /server/webhooks/deliveries:
    get:
      tags: [Webhooks]
      operationId: listWebhookDeliveries
      summary: List webhook delivery attempts.
      security:
        - ServerApiKey: []
      parameters:
        - $ref: "#/components/parameters/LimitQuery"
      responses:
        "200":
          description: Webhook deliveries.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookDeliveryListEnvelope"

  /server/webhooks/test:
    post:
      tags: [Webhooks]
      operationId: testWebhook
      summary: Send a webhook test event.
      security:
        - ServerApiKey: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookTestRequest"
      responses:
        "200":
          description: Webhook test queued or delivered.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookTestEnvelope"

  /server/webhooks/{id}:
    parameters:
      - $ref: "#/components/parameters/WebhookIdPath"
    patch:
      tags: [Webhooks]
      operationId: updateWebhook
      summary: Update a webhook endpoint.
      security:
        - ServerApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookPatchRequest"
      responses:
        "200":
          description: Webhook updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEnvelope"
    delete:
      tags: [Webhooks]
      operationId: disableWebhook
      summary: Disable a webhook endpoint.
      security:
        - ServerApiKey: []
      responses:
        "200":
          description: Webhook disabled.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WebhookEnvelope"

  /agent-sdk/me:
    get:
      tags: [Agent CRM]
      operationId: getAgentProfile
      summary: Read the current agent profile.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Agent profile.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AgentProfileEnvelope"

  /agent-sdk/presence:
    get:
      tags: [Agent CRM]
      operationId: getAgentPresence
      summary: Read the current agent presence.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Agent presence.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PresenceEnvelope"
    patch:
      tags: [Agent CRM]
      operationId: updateAgentPresence
      summary: Update the current agent presence.
      security:
        - RuntimeBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdatePresenceRequest"
      responses:
        "200":
          description: Agent presence updated.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PresenceEnvelope"

  /agent-sdk/conversations:
    get:
      tags: [Agent CRM]
      operationId: listAgentConversations
      summary: List conversations visible to the current agent.
      security:
        - RuntimeBearer: []
      parameters:
        - $ref: "#/components/parameters/LimitQuery"
        - $ref: "#/components/parameters/CursorQuery"
        - name: status
          in: query
          schema:
            type: string
        - name: departmentId
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Conversation list.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationListEnvelope"

  /agent-sdk/conversations/{id}:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
    get:
      tags: [Agent CRM]
      operationId: getAgentConversation
      summary: Read one agent-visible conversation.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Conversation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationEnvelope"

  /agent-sdk/conversations/{id}/messages:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
    get:
      tags: [Agent CRM]
      operationId: listAgentConversationMessages
      summary: List messages in a conversation.
      security:
        - RuntimeBearer: []
      parameters:
        - $ref: "#/components/parameters/LimitQuery"
        - name: before
          in: query
          schema:
            type: string
      responses:
        "200":
          description: Message list.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageListEnvelope"
    post:
      tags: [Agent CRM]
      operationId: sendAgentConversationMessage
      summary: Send an agent message.
      security:
        - RuntimeBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SendMessageRequest"
      responses:
        "201":
          description: Message sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageEnvelope"

  /agent-sdk/conversations/{id}/messages/{messageId}/read:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
      - $ref: "#/components/parameters/MessageIdPath"
    post:
      tags: [Agent CRM]
      operationId: markAgentMessageRead
      summary: Mark an agent conversation message as read.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Message marked read.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /agent-sdk/conversations/{id}/messages/{messageId}/attachment:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
      - $ref: "#/components/parameters/MessageIdPath"
    delete:
      tags: [Agent CRM]
      operationId: deleteAgentConversationAttachment
      summary: Delete an attachment-bearing conversation message.
      description: Soft-deletes the chat message that owns the attachment so the action remains auditable.
      security:
        - RuntimeBearer: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/DeleteMediaRequest"
      responses:
        "200":
          description: Attachment message deleted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttachmentDeleteEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /agent-sdk/conversations/{id}/assign:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
    post:
      tags: [Agent CRM]
      operationId: assignAgentConversation
      summary: Assign or transfer a conversation from the agent SDK.
      security:
        - RuntimeBearer: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AssignConversationRequest"
      responses:
        "200":
          description: Conversation assigned.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AssignmentEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /agent-sdk/conversations/{id}/close:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
    post:
      tags: [Agent CRM]
      operationId: closeAgentConversation
      summary: Close a conversation from the agent SDK.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Conversation closed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationEnvelope"

  /admin-sdk/tenant:
    get:
      tags: [Admin Reports]
      operationId: getAdminTenant
      summary: Read tenant and admin identity for admin/reporting SDKs.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Admin tenant identity.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AdminTenantEnvelope"

  /admin-sdk/readiness:
    get:
      tags: [Admin Reports]
      operationId: getAdminReadiness
      summary: Read tenant readiness for admin/reporting SDKs.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Admin readiness.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReadinessEnvelope"

  /admin-sdk/domains:
    get:
      tags: [Admin Reports]
      operationId: getAdminDomains
      summary: Read allowed SDK domains.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Allowed domains.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/DomainListEnvelope"

  /admin-sdk/services:
    get:
      tags: [Admin Reports]
      operationId: getAdminServices
      summary: Read service capabilities and admin enablement.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Service capabilities.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ServiceListEnvelope"

  /admin-sdk/subscription:
    get:
      tags: [Admin Reports]
      operationId: getAdminSubscription
      summary: Read subscription and plan data.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Subscription.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SubscriptionEnvelope"

  /admin-sdk/ai-readiness:
    get:
      tags: [Admin Reports]
      operationId: getAdminAiReadiness
      summary: Read AI readiness for the tenant.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: AI readiness.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiReadinessEnvelope"

  /admin-sdk/staff-summary:
    get:
      tags: [Admin Reports]
      operationId: getAdminStaffSummary
      summary: Read staff and department summary data.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Staff summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/StaffSummaryEnvelope"

  /admin-sdk/reports/summary:
    get:
      tags: [Admin Reports]
      operationId: getAdminReportSummary
      summary: Read call, chat, satisfaction, and agent productivity summary.
      security:
        - RuntimeBearer: []
      parameters:
        - $ref: "#/components/parameters/ReportPeriodQuery"
        - $ref: "#/components/parameters/ReportStartDateQuery"
        - $ref: "#/components/parameters/ReportEndDateQuery"
        - $ref: "#/components/parameters/ReportAgentIdQuery"
      responses:
        "200":
          description: Report summary.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportSummaryEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /admin-sdk/reports/timeseries:
    get:
      tags: [Admin Reports]
      operationId: getAdminReportTimeseries
      summary: Read call, chat, and message trend data for admin dashboards.
      security:
        - RuntimeBearer: []
      parameters:
        - $ref: "#/components/parameters/ReportPeriodQuery"
        - $ref: "#/components/parameters/ReportStartDateQuery"
        - $ref: "#/components/parameters/ReportEndDateQuery"
        - $ref: "#/components/parameters/ReportAgentIdQuery"
      responses:
        "200":
          description: Report timeseries.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportTimeseriesEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /admin-sdk/reports/export:
    get:
      tags: [Admin Reports]
      operationId: exportAdminReport
      summary: Export admin report data.
      description: Returns a downloadable JSON envelope by default, or CSV when `format=csv`.
      security:
        - RuntimeBearer: []
      parameters:
        - $ref: "#/components/parameters/ReportPeriodQuery"
        - $ref: "#/components/parameters/ReportStartDateQuery"
        - $ref: "#/components/parameters/ReportEndDateQuery"
        - $ref: "#/components/parameters/ReportAgentIdQuery"
        - $ref: "#/components/parameters/ReportFormatQuery"
      responses:
        "200":
          description: Admin report export.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReportExportEnvelope"
            text/csv:
              schema:
                type: string
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /admin-sdk/ai/knowledge/documents:
    post:
      tags: [AI]
      operationId: uploadAdminKnowledgeDocument
      summary: Upload an AI knowledge document for the tenant.
      security:
        - RuntimeBearer: []
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "201":
          description: Knowledge document queued or ingested.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiKnowledgeEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /admin-sdk/ai/knowledge/urls:
    post:
      tags: [AI]
      operationId: ingestAdminKnowledgeUrl
      summary: Ingest an AI knowledge URL for the tenant.
      security:
        - RuntimeBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KnowledgeUrlRequest"
      responses:
        "201":
          description: Knowledge URL queued or ingested.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiKnowledgeEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /admin-sdk/ai/knowledge/{knowledgeItemId}/reindex:
    parameters:
      - $ref: "#/components/parameters/KnowledgeItemIdPath"
    post:
      tags: [AI]
      operationId: reindexAdminKnowledge
      summary: Reindex an AI knowledge item.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Knowledge item reindexed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiKnowledgeEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /admin-sdk/ai/knowledge/{knowledgeItemId}:
    parameters:
      - $ref: "#/components/parameters/KnowledgeItemIdPath"
    delete:
      tags: [AI]
      operationId: deleteAdminKnowledge
      summary: Delete an AI knowledge item.
      security:
        - RuntimeBearer: []
      responses:
        "200":
          description: Knowledge item deleted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiKnowledgeEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /calls/sessions:
    post:
      tags: [Calls]
      operationId: createCallSession
      summary: Create a product-level internet audio or video call session.
      description: Creates a call session attached to a conversation without exposing low-level media transport details.
      security:
        - RuntimeBearer: []
        - DashboardBearer: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateCallSessionRequest"
      responses:
        "201":
          description: Call session created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallSessionEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /calls/sessions/{callSessionId}:
    parameters:
      - $ref: "#/components/parameters/CallSessionIdPath"
    get:
      tags: [Calls]
      operationId: getCallSession
      summary: Read a product-level call session.
      security:
        - RuntimeBearer: []
        - DashboardBearer: []
      responses:
        "200":
          description: Call session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallSessionEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /calls/sessions/{callSessionId}/end:
    parameters:
      - $ref: "#/components/parameters/CallSessionIdPath"
    patch:
      tags: [Calls]
      operationId: endCallSession
      summary: End a product-level call session.
      security:
        - RuntimeBearer: []
        - DashboardBearer: []
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EndCallSessionRequest"
      responses:
        "200":
          description: Call session ended.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallSessionEnvelope"
        "4XX":
          $ref: "#/components/responses/ErrorResponse"

  /chat/visitor/sessions:
    post:
      tags: [Chat]
      operationId: createVisitorChatSession
      summary: Create or resolve a visitor chat session.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VisitorSessionRequest"
      responses:
        "201":
          description: Visitor session created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/VisitorSessionEnvelope"

  /chat/visitor/conversations:
    post:
      tags: [Chat]
      operationId: createVisitorConversation
      summary: Start a visitor chat conversation.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VisitorConversationRequest"
      responses:
        "201":
          description: Conversation created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationEnvelope"

  /chat/visitor/tickets/open:
    get:
      tags: [Chat]
      operationId: getVisitorOpenTicket
      summary: Get the visitor open ticket when one exists.
      security:
        - RuntimeBearer: []
        - {}
      responses:
        "200":
          description: Open ticket.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TicketEnvelope"

  /chat/visitor/availability:
    get:
      tags: [Chat]
      operationId: getVisitorChatAvailability
      summary: Read chat availability for the current visitor context.
      security:
        - RuntimeBearer: []
        - {}
      responses:
        "200":
          description: Chat availability.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AvailabilityEnvelope"

  /chat/visitor/conversations/{conversationId}:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
    get:
      tags: [Chat]
      operationId: getVisitorConversation
      summary: Read a visitor conversation.
      security:
        - RuntimeBearer: []
        - {}
      responses:
        "200":
          description: Conversation.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ConversationEnvelope"

  /chat/visitor/conversations/{conversationId}/messages:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
    post:
      tags: [Chat]
      operationId: sendVisitorConversationMessage
      summary: Send a visitor chat message.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/VisitorMessageRequest"
      responses:
        "201":
          description: Message sent.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageEnvelope"

  /chat/visitor/conversations/{conversationId}/messages/{messageId}:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
      - $ref: "#/components/parameters/MessageIdPath"
    patch:
      tags: [Chat]
      operationId: editVisitorConversationMessage
      summary: Edit a visitor chat message.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/MessagePatchRequest"
      responses:
        "200":
          description: Message edited.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MessageEnvelope"
    delete:
      tags: [Chat]
      operationId: deleteVisitorConversationMessage
      summary: Delete a visitor chat message.
      security:
        - RuntimeBearer: []
        - {}
      responses:
        "200":
          description: Message deleted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"

  /chat/visitor/conversations/{conversationId}/attachments:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
    post:
      tags: [Chat]
      operationId: createVisitorConversationAttachment
      summary: Create attachment metadata for a visitor conversation.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AttachmentRequest"
      responses:
        "201":
          description: Attachment metadata created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttachmentEnvelope"

  /chat/visitor/conversations/{conversationId}/attachments/upload:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
    post:
      tags: [Chat]
      operationId: uploadVisitorConversationAttachment
      summary: Upload a visitor conversation attachment.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              required: [file]
              properties:
                file:
                  type: string
                  format: binary
      responses:
        "201":
          description: Attachment uploaded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AttachmentEnvelope"

  /chat/visitor/conversations/{conversationId}/call-links:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
    post:
      tags: [Chat]
      operationId: createVisitorConversationCallLink
      summary: Create an internet call link for a visitor conversation.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CallLinkRequest"
      responses:
        "201":
          description: Call link created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallLinkEnvelope"

  /chat/conversations/{conversationId}/messages/{messageId}/delivered:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
      - $ref: "#/components/parameters/MessageIdPath"
    post:
      tags: [Chat]
      operationId: markChatMessageDelivered
      summary: Mark a chat message as delivered.
      security:
        - RuntimeBearer: []
        - {}
      responses:
        "200":
          description: Delivery receipt recorded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReceiptEnvelope"

  /chat/conversations/{conversationId}/messages/{messageId}/read:
    parameters:
      - $ref: "#/components/parameters/ConversationIdPath"
      - $ref: "#/components/parameters/MessageIdPath"
    post:
      tags: [Chat]
      operationId: markChatMessageRead
      summary: Mark a chat message as read.
      security:
        - RuntimeBearer: []
        - {}
      responses:
        "200":
          description: Read receipt recorded.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ReceiptEnvelope"

  /ai/visitor/routing:
    get:
      tags: [AI]
      operationId: getAiVisitorRouting
      summary: Read AI routing mode for the visitor context.
      security:
        - RuntimeBearer: []
        - {}
      responses:
        "200":
          description: AI routing.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiRoutingEnvelope"

  /ai/visitor/conversations:
    post:
      tags: [AI]
      operationId: createAiVisitorConversation
      summary: Start an AI visitor conversation.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: false
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AiConversationRequest"
      responses:
        "201":
          description: AI conversation created.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiConversationEnvelope"

  /ai/visitor/chat:
    post:
      tags: [AI]
      operationId: sendAiVisitorMessage
      summary: Send a visitor AI chat message.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/AiChatRequest"
      responses:
        "200":
          description: AI response.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/AiChatEnvelope"

  /ai/visitor/knowledge/search:
    post:
      tags: [AI]
      operationId: searchAiVisitorKnowledge
      summary: Search AI knowledge for the visitor context.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/KnowledgeSearchRequest"
      responses:
        "200":
          description: Knowledge search results.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/KnowledgeSearchEnvelope"

  /ai/visitor/realtime/call:
    post:
      tags: [AI]
      operationId: createAiVisitorRealtimeCall
      summary: Create an AI voice call realtime session.
      description: Low-level transport body is kept behind SDK call objects for normal customers.
      security:
        - RuntimeBearer: []
        - {}
      requestBody:
        required: true
        content:
          application/sdp:
            schema:
              type: string
          text/plain:
            schema:
              type: string
      responses:
        "200":
          description: Realtime answer.
          content:
            application/sdp:
              schema:
                type: string
            text/plain:
              schema:
                type: string

  /.well-known/openid-configuration:
    servers:
      - url: https://api.ringnity.com
        description: Production identity issuer
      - url: http://localhost:8192
        description: Local identity issuer
    get:
      tags: [Login with Ringnity]
      operationId: getOpenIdConfiguration
      summary: Read OpenID Connect discovery metadata.
      description: Root-level discovery document for apps that implement Login with Ringnity.
      responses:
        "200":
          description: OpenID Connect discovery metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenIdConfiguration"

  /oauth/authorize:
    servers:
      - url: https://api.ringnity.com
        description: Production identity issuer
      - url: http://localhost:8192
        description: Local identity issuer
    get:
      tags: [Login with Ringnity]
      operationId: authorizeWithRingnity
      summary: Start Login with Ringnity authorization.
      description: Authorization Code Flow endpoint for CMS, CRM, web apps, and mobile apps using PKCE.
      parameters:
        - name: response_type
          in: query
          required: true
          schema:
            type: string
            enum: [code]
        - name: client_id
          in: query
          required: true
          schema:
            type: string
        - name: redirect_uri
          in: query
          required: true
          schema:
            type: string
            format: uri
        - name: scope
          in: query
          required: true
          schema:
            type: string
            example: openid profile email tenant.read roles.read developer.read
        - name: state
          in: query
          required: true
          schema:
            type: string
        - name: nonce
          in: query
          schema:
            type: string
        - name: code_challenge
          in: query
          schema:
            type: string
        - name: code_challenge_method
          in: query
          schema:
            type: string
            enum: [S256, plain]
      responses:
        "302":
          description: Redirect back to the registered callback URL with code or error parameters.
          headers:
            Location:
              schema:
                type: string
                format: uri
        "4XX":
          $ref: "#/components/responses/OAuthErrorResponse"
        "503":
          $ref: "#/components/responses/OAuthErrorResponse"

  /oauth/token:
    servers:
      - url: https://api.ringnity.com
        description: Production identity issuer
      - url: http://localhost:8192
        description: Local identity issuer
    post:
      tags: [Login with Ringnity]
      operationId: exchangeRingnityOAuthToken
      summary: Exchange authorization code or refresh token.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/OAuthTokenRequest"
      responses:
        "200":
          description: OAuth/OIDC token response.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthTokenResponse"
        "4XX":
          $ref: "#/components/responses/OAuthErrorResponse"
        "503":
          $ref: "#/components/responses/OAuthErrorResponse"

  /oauth/userinfo:
    servers:
      - url: https://api.ringnity.com
        description: Production identity issuer
      - url: http://localhost:8192
        description: Local identity issuer
    get:
      tags: [Login with Ringnity]
      operationId: getRingnityOAuthUserInfo
      summary: Read OIDC user profile claims.
      security:
        - OAuthAccessToken: []
      responses:
        "200":
          description: UserInfo claims for the logged-in Ringnity user.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthUserInfo"
        "4XX":
          $ref: "#/components/responses/OAuthErrorResponse"
        "503":
          $ref: "#/components/responses/OAuthErrorResponse"

  /oauth/tenant/widget-key:
    servers:
      - url: https://api.ringnity.com
        description: Production identity issuer
      - url: http://localhost:8192
        description: Local identity issuer
    get:
      tags: [Login with Ringnity]
      operationId: getRingnityOAuthWidgetKey
      summary: Read the public Website widget install key for the OAuth tenant.
      description: Requires an OAuth access token with `developer.read` or `sdk.keys.read`. The returned key is used for direct visitor app links, WordPress auto widget installs, and the universal widget script.
      security:
        - OAuthAccessToken: []
      responses:
        "200":
          description: Public Website widget install key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthWidgetKeyEnvelope"
        "4XX":
          $ref: "#/components/responses/OAuthErrorResponse"
        "503":
          $ref: "#/components/responses/OAuthErrorResponse"

  /oauth/tenant/allowed-domains:
    servers:
      - url: https://api.ringnity.com
        description: Production identity issuer
      - url: http://localhost:8192
        description: Local identity issuer
    post:
      tags: [Login with Ringnity]
      operationId: addRingnityOAuthAllowedDomain
      summary: Add one allowed Website widget domain for the OAuth tenant.
      description: Requires an OAuth access token with `tenant.domains.write`. The backend enforces the tenant plan quota `allowed_domains`.
      security:
        - OAuthAccessToken: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/OAuthAddAllowedDomainRequest"
      responses:
        "200":
          description: Domain was added or was already allowed.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OAuthWidgetKeyEnvelope"
        "4XX":
          $ref: "#/components/responses/OAuthErrorResponse"
        "409":
          $ref: "#/components/responses/OAuthErrorResponse"
        "503":
          $ref: "#/components/responses/OAuthErrorResponse"

  /oauth/jwks.json:
    servers:
      - url: https://api.ringnity.com
        description: Production identity issuer
      - url: http://localhost:8192
        description: Local identity issuer
    get:
      tags: [Login with Ringnity]
      operationId: getRingnityOAuthJwks
      summary: Read public keys for ID token verification.
      responses:
        "200":
          description: JSON Web Key Set.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Jwks"

  /oauth/revoke:
    servers:
      - url: https://api.ringnity.com
        description: Production identity issuer
      - url: http://localhost:8192
        description: Local identity issuer
    post:
      tags: [Login with Ringnity]
      operationId: revokeRingnityOAuthToken
      summary: Revoke an OAuth refresh token or access token.
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              $ref: "#/components/schemas/OAuthRevokeRequest"
      responses:
        "200":
          description: Token revoke request accepted.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/SuccessEnvelope"
        "4XX":
          $ref: "#/components/responses/OAuthErrorResponse"
        "503":
          $ref: "#/components/responses/OAuthErrorResponse"

components:
  securitySchemes:
    ServerApiKey:
      type: http
      scheme: bearer
      bearerFormat: Ringnity Server API Key
      description: Secret Server API key. Must only be used from customer-owned backend servers.
    RuntimeBearer:
      type: apiKey
      in: header
      name: X-Ringnity-SDK-Session
      description: Short-lived SDK runtime token issued by `/sdk/session`.
    DashboardBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Logged-in Ringnity owner or workspace dashboard JWT.
    OwnerBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Owner JWT with internal `scope: owner`, issued by `/auth/owner-login` or `/auth/owner-refresh`.
    WorkspaceBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Workspace JWT with internal `scope: workspace`, issued by `/auth/workspace-login`, `/auth/workspace-auto-login`, or `/auth/workspace-refresh`.
    PlatformBearer:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: Platform admin JWT issued by `/platform/auth/login`.
    OAuthAccessToken:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: OAuth2 access token issued by Login with Ringnity.

  parameters:
    TenantSlugQuery:
      name: slug
      in: query
      schema:
        type: string
        example: your-tenant-slug
      description: Public tenant slug.
    PublicKeyQuery:
      name: apiKey
      in: query
      schema:
        type: string
      description: Public widget/install key for legacy compatibility.
    DebugQuery:
      name: debug
      in: query
      schema:
        type: boolean
      description: Include blocked-service reasons for developer diagnostics.
    PageQuery:
      name: page
      in: query
      schema:
        type: integer
        minimum: 1
        default: 1
    LimitQuery:
      name: limit
      in: query
      schema:
        type: integer
        minimum: 1
        maximum: 100
        default: 50
    CursorQuery:
      name: cursor
      in: query
      schema:
        type: string
    ReportPeriodQuery:
      name: period
      in: query
      description: Preset reporting range. Use custom together with startDate and endDate.
      schema:
        type: string
        enum: [today, 7days, 30days, custom]
        default: today
    ReportStartDateQuery:
      name: startDate
      in: query
      description: ISO date or date-time. Required when period is custom.
      schema:
        type: string
        format: date-time
    ReportEndDateQuery:
      name: endDate
      in: query
      description: ISO date or date-time. Required when period is custom.
      schema:
        type: string
        format: date-time
    ReportAgentIdQuery:
      name: agentId
      in: query
      description: Optional agent ID filter for productivity reports.
      schema:
        type: string
    ReportFormatQuery:
      name: format
      in: query
      description: Export format.
      schema:
        type: string
        enum: [json, csv]
        default: json
    CredentialIdPath:
      name: credentialId
      in: path
      required: true
      schema:
        type: string
    OAuthClientIdPath:
      name: oauthClientId
      in: path
      required: true
      schema:
        type: string
    ClientIdPath:
      name: clientId
      in: path
      required: true
      schema:
        type: string
    ContactIdPath:
      name: id
      in: path
      required: true
      schema:
        type: string
    WebhookIdPath:
      name: id
      in: path
      required: true
      schema:
        type: string
    ConversationIdPath:
      name: conversationId
      in: path
      required: true
      schema:
        type: string
    MessageIdPath:
      name: messageId
      in: path
      required: true
      schema:
        type: string
    CallSessionIdPath:
      name: callSessionId
      in: path
      required: true
      schema:
        type: string
    KnowledgeItemIdPath:
      name: knowledgeItemId
      in: path
      required: true
      schema:
        type: string

  responses:
    ErrorResponse:
      description: Error response.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ErrorEnvelope"
    OAuthErrorResponse:
      description: OAuth2/OIDC error response.
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/OAuthError"

  schemas:
    AnyObject:
      type: object
      additionalProperties: true

    RefreshTokenRequest:
      type: object
      properties:
        refreshToken:
          type: string

    OwnerLoginRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
        password:
          type: string
          format: password

    OwnerGoogleLoginRequest:
      type: object
      required: [idToken]
      properties:
        idToken:
          type: string
          description: Google Identity Services ID token.

    OwnerClient:
      type: object
      required: [id, email, slug, isActive, onboardingCompleted, apiKey]
      properties:
        id:
          type: string
        name:
          type: string
          nullable: true
        email:
          type: string
          format: email
        slug:
          type: string
        companyName:
          type: string
          nullable: true
        logoUrl:
          type: string
          nullable: true
        phone:
          type: string
          nullable: true
        industry:
          type: string
          nullable: true
        isActive:
          type: boolean
        onboardingCompleted:
          type: boolean
        apiKey:
          type: string

    OwnerAdmin:
      type: object
      required: [agentId, username, name]
      properties:
        agentId:
          type: string
        username:
          type: string
        name:
          type: string

    OwnerSubscription:
      type: object
      required: [planName, planSlug, status]
      properties:
        planName:
          type: string
        planSlug:
          type: string
        status:
          type: string

    OwnerSession:
      type: object
      required: [client, subscription, accessToken, refreshToken, expiresIn, dashboardUrl]
      properties:
        client:
          $ref: "#/components/schemas/OwnerClient"
        admin:
          anyOf:
            - $ref: "#/components/schemas/OwnerAdmin"
            - type: "null"
        subscription:
          $ref: "#/components/schemas/OwnerSubscription"
        accessToken:
          type: string
          description: Owner JWT with internal `scope: owner`.
        refreshToken:
          type: string
        expiresIn:
          type: integer
        sessionToken:
          type: string
          deprecated: true
          description: Deprecated alias of `accessToken`.
        dashboardUrl:
          type: string
          format: uri

    OwnerSessionEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/OwnerSession"

    OwnerMeEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [client, subscription, dashboardUrl]
          properties:
            client:
              $ref: "#/components/schemas/OwnerClient"
            admin:
              anyOf:
                - $ref: "#/components/schemas/OwnerAdmin"
                - type: "null"
            subscription:
              $ref: "#/components/schemas/OwnerSubscription"
            dashboardUrl:
              type: string
              format: uri

    OwnerRefreshEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [accessToken, refreshToken, expiresIn]
          properties:
            accessToken:
              type: string
            refreshToken:
              type: string
            expiresIn:
              type: integer
            sessionToken:
              type: string
              deprecated: true
              description: Deprecated alias of `accessToken`.

    OwnerGoogleLoginEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [requiresRegistration]
          properties:
            requiresRegistration:
              type: boolean
            client:
              $ref: "#/components/schemas/OwnerClient"
            admin:
              anyOf:
                - $ref: "#/components/schemas/OwnerAdmin"
                - type: "null"
            subscription:
              $ref: "#/components/schemas/OwnerSubscription"
            accessToken:
              type: string
            refreshToken:
              type: string
            expiresIn:
              type: integer
            sessionToken:
              type: string
              deprecated: true
            dashboardUrl:
              type: string
              format: uri
            googleProfile:
              $ref: "#/components/schemas/AnyObject"

    WorkspaceLoginRequest:
      type: object
      required: [username, password]
      properties:
        username:
          type: string
        password:
          type: string
          format: password

    WorkspaceRole:
      type: string
      enum: [owner, admin, supervisor, agent]

    WorkspaceAgent:
      type: object
      required: [agentId, username, name, isOwner, isAdmin, role, clientId, clientSlug]
      properties:
        agentId:
          type: string
        username:
          type: string
        name:
          type: string
        isOwner:
          type: boolean
        isAdmin:
          type: boolean
        role:
          $ref: "#/components/schemas/WorkspaceRole"
        clientId:
          type: string
        clientSlug:
          type: string
        profileImage:
          type: string
          nullable: true
        typeCall:
          type: string
          nullable: true
        department:
          $ref: "#/components/schemas/AnyObject"
        position:
          $ref: "#/components/schemas/AnyObject"

    WorkspaceSession:
      type: object
      required: [accessToken, refreshToken, expiresIn]
      properties:
        accessToken:
          type: string
          description: Workspace JWT with internal `scope: workspace`.
        refreshToken:
          type: string
        expiresIn:
          type: integer
        token:
          type: string
          deprecated: true
          description: Deprecated alias of `accessToken`.
        agent:
          $ref: "#/components/schemas/WorkspaceAgent"
        agentId:
          type: string
        username:
          type: string
        name:
          type: string

    WorkspaceSessionEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/WorkspaceSession"

    WorkspaceAutoLoginEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          allOf:
            - $ref: "#/components/schemas/WorkspaceSession"
            - type: object
              required: [agent]

    WorkspaceRefreshEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [accessToken, refreshToken, expiresIn]
          properties:
            accessToken:
              type: string
            refreshToken:
              type: string
            expiresIn:
              type: integer

    WorkspaceEntryTokenEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [autoLoginToken, dashboardUrl, expiresIn]
          properties:
            autoLoginToken:
              type: string
            dashboardUrl:
              type: string
              format: uri
            expiresIn:
              type: integer

    PlatformRole:
      type: string
      enum: [platform_owner, platform_admin, platform_support]

    PlatformLoginRequest:
      type: object
      required: [email, password]
      properties:
        email:
          type: string
          format: email
          example: admin@ringnity.test
        password:
          type: string
          format: password

    PlatformRefreshRequest:
      type: object
      required: [refreshToken]
      properties:
        refreshToken:
          type: string
          description: Refresh token returned by platform login or the previous refresh call.

    PlatformSession:
      type: object
      required: [adminId, email, name, role, accessToken, refreshToken, expiresIn]
      properties:
        adminId:
          type: string
        email:
          type: string
          format: email
        name:
          type: string
        role:
          $ref: "#/components/schemas/PlatformRole"
        accessToken:
          type: string
        refreshToken:
          type: string
        expiresIn:
          type: integer
          description: Access token lifetime in seconds.

    PlatformSessionEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/PlatformSession"

    PlanContractValueType:
      type: string
      enum: [number, unlimited, custom, disabled]

    PlanContractFeatureKey:
      type: object
      required: [featureKey, name, category, description, status, sortOrder]
      properties:
        featureKey:
          type: string
          example: video
        name:
          type: string
          example: Video
        category:
          type: string
          example: communication
        description:
          type: string
          example: Visitor video calls and video escalation.
        status:
          type: string
          example: stable
        sortOrder:
          type: integer
          example: 30

    PlanContractQuotaKey:
      type: object
      required: [quotaKey, name, unit, category, description, sortOrder]
      properties:
        quotaKey:
          type: string
          example: agents
        name:
          type: string
          example: Agents
        unit:
          type: string
          example: agent
        category:
          type: string
          example: core
        description:
          type: string
          example: Non-owner agent accounts allowed by the plan.
        sortOrder:
          type: integer
          example: 10

    PlanFeatureContract:
      type: object
      required: [enabled]
      properties:
        planId:
          type: string
        featureKey:
          type: string
        enabled:
          type: boolean
        config:
          $ref: "#/components/schemas/AnyObject"

    PlanQuotaContract:
      type: object
      required: [valueType]
      properties:
        planId:
          type: string
        quotaKey:
          type: string
        valueType:
          $ref: "#/components/schemas/PlanContractValueType"
        limitValue:
          type: integer
          nullable: true
          description: Numeric limit when `valueType` is `number`; null otherwise.
        resetPeriod:
          type: string
          nullable: true
          example: monthly
        config:
          $ref: "#/components/schemas/AnyObject"

    PlanContractPlan:
      type: object
      required: [planId, slug, name, sortOrder, isActive, features, quotas]
      properties:
        planId:
          type: string
        slug:
          type: string
          example: professional
        name:
          type: string
          example: Professional
        sortOrder:
          type: integer
        isActive:
          type: boolean
        features:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/PlanFeatureContract"
        quotas:
          type: object
          additionalProperties:
            $ref: "#/components/schemas/PlanQuotaContract"

    PlanContractCatalog:
      type: object
      required: [featureKeys, quotaKeys, plans]
      properties:
        featureKeys:
          type: array
          items:
            $ref: "#/components/schemas/PlanContractFeatureKey"
        quotaKeys:
          type: array
          items:
            $ref: "#/components/schemas/PlanContractQuotaKey"
        plans:
          type: array
          items:
            $ref: "#/components/schemas/PlanContractPlan"

    PlanContractCatalogEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/PlanContractCatalog"
        message:
          type: string

    AiPricingSettings:
      type: object
      required:
        - provider
        - currency
        - chatModel
        - voiceModel
        - inputCostPer1mUsd
        - cachedInputCostPer1mUsd
        - outputCostPer1mUsd
        - voiceCostPerMinuteUsd
        - markupMultiplier
        - riskBufferPercent
        - replyUnitInputTokens
        - replyUnitOutputTokens
        - voiceUnitSeconds
        - maxInputTokens
        - maxOutputTokens
        - maxContextMessages
        - maxKnowledgeChunks
        - maxRetries
        - maxToolCalls
        - dailySpendLimitCents
        - monthlySpendLimitCents
        - lowBalanceThresholdCents
      properties:
        provider:
          type: string
          example: openai
        currency:
          type: string
          example: USD
        chatModel:
          type: string
          example: gpt-4.1-mini
        voiceModel:
          type: string
          example: gpt-realtime
        inputCostPer1mUsd:
          type: number
          example: 0.4
        cachedInputCostPer1mUsd:
          type: number
          example: 0.1
        outputCostPer1mUsd:
          type: number
          example: 1.6
        voiceCostPerMinuteUsd:
          type: number
          example: 0.03
        markupMultiplier:
          type: number
          minimum: 1.1
          example: 3
        riskBufferPercent:
          type: number
          minimum: 0
          example: 25
        replyUnitInputTokens:
          type: integer
          minimum: 0
          example: 3000
        replyUnitOutputTokens:
          type: integer
          minimum: 0
          example: 1000
        voiceUnitSeconds:
          type: integer
          minimum: 1
          example: 60
        maxInputTokens:
          type: integer
          minimum: 0
          example: 12000
        maxOutputTokens:
          type: integer
          minimum: 0
          example: 1500
        maxContextMessages:
          type: integer
          minimum: 0
          example: 12
        maxKnowledgeChunks:
          type: integer
          minimum: 0
          example: 6
        maxRetries:
          type: integer
          minimum: 0
          example: 1
        maxToolCalls:
          type: integer
          minimum: 0
          example: 4
        dailySpendLimitCents:
          type: integer
          minimum: 0
          example: 5000
        monthlySpendLimitCents:
          type: integer
          minimum: 0
          example: 50000
        lowBalanceThresholdCents:
          type: integer
          minimum: 0
          example: 1000
        updatedAt:
          type: string
          format: date-time
          nullable: true

    AiPricingSettingsUpdate:
      type: object
      description: Partial AI pricing settings update. Omitted fields keep their current value.
      properties:
        provider:
          type: string
        currency:
          type: string
        chatModel:
          type: string
        voiceModel:
          type: string
        inputCostPer1mUsd:
          type: number
        cachedInputCostPer1mUsd:
          type: number
        outputCostPer1mUsd:
          type: number
        voiceCostPerMinuteUsd:
          type: number
        markupMultiplier:
          type: number
          minimum: 1.1
        riskBufferPercent:
          type: number
          minimum: 0
        replyUnitInputTokens:
          type: integer
          minimum: 0
        replyUnitOutputTokens:
          type: integer
          minimum: 0
        voiceUnitSeconds:
          type: integer
          minimum: 1
        maxInputTokens:
          type: integer
          minimum: 0
        maxOutputTokens:
          type: integer
          minimum: 0
        maxContextMessages:
          type: integer
          minimum: 0
        maxKnowledgeChunks:
          type: integer
          minimum: 0
        maxRetries:
          type: integer
          minimum: 0
        maxToolCalls:
          type: integer
          minimum: 0
        dailySpendLimitCents:
          type: integer
          minimum: 0
        monthlySpendLimitCents:
          type: integer
          minimum: 0
        lowBalanceThresholdCents:
          type: integer
          minimum: 0

    AiPricingDerived:
      type: object
      required:
        - internalReplyUsd
        - retailReplyUsd
        - retailPer1000RepliesUsd
        - internalVoiceMinuteUsd
        - retailVoiceMinuteUsd
        - grossMarginPercent
      properties:
        internalReplyUsd:
          type: number
        retailReplyUsd:
          type: number
        retailPer1000RepliesUsd:
          type: number
        internalVoiceMinuteUsd:
          type: number
        retailVoiceMinuteUsd:
          type: number
        grossMarginPercent:
          type: number
          example: 66.7

    AiPricingCatalog:
      type: object
      required: [settings, derived]
      properties:
        settings:
          $ref: "#/components/schemas/AiPricingSettings"
        derived:
          $ref: "#/components/schemas/AiPricingDerived"

    AiPricingCatalogEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AiPricingCatalog"
        message:
          type: string

    AiBillingAlertLevel:
      type: string
      enum: [ok, low, critical, empty]

    AiBalanceSummary:
      type: object
      required:
        - totalTenants
        - totalBalanceCents
        - totalEstimatedOverageCents
        - totalEstimatedRetailChargeCents
        - totalEstimatedInternalCostCents
        - totalChatReplies
        - totalVoiceMinutes
        - emptyCount
        - criticalCount
        - lowCount
      properties:
        totalTenants:
          type: integer
        totalBalanceCents:
          type: integer
        totalEstimatedOverageCents:
          type: integer
        totalEstimatedRetailChargeCents:
          type: integer
        totalEstimatedInternalCostCents:
          type: integer
        totalChatReplies:
          type: integer
        totalVoiceMinutes:
          type: integer
        emptyCount:
          type: integer
        criticalCount:
          type: integer
        lowCount:
          type: integer

    AiTenantUsage:
      type: object
      required:
        - chatReplies
        - voiceMinutes
        - voiceEvents
        - audioSeconds
        - inputTokens
        - outputTokens
        - totalTokens
      properties:
        chatReplies:
          type: integer
        voiceMinutes:
          type: integer
        voiceEvents:
          type: integer
        audioSeconds:
          type: integer
        inputTokens:
          type: integer
        outputTokens:
          type: integer
        totalTokens:
          type: integer

    AiTenantIncludedQuota:
      type: object
      required: [chatLabel, voiceLabel]
      properties:
        chatReplies:
          type: integer
          nullable: true
          description: Null means custom or unlimited plan handling.
        chatLabel:
          type: string
          example: 5,000 replies
        voiceMinutes:
          type: integer
          nullable: true
          description: Null means custom or unlimited plan handling.
        voiceLabel:
          type: string
          example: 300 minutes

    AiTenantOverage:
      type: object
      required:
        - chatReplies
        - voiceMinutes
        - estimatedInternalCostCents
        - estimatedRetailChargeCents
        - estimatedOverageChargeCents
        - projectedBalanceAfterCents
      properties:
        chatReplies:
          type: integer
        voiceMinutes:
          type: integer
        estimatedInternalCostCents:
          type: integer
        estimatedRetailChargeCents:
          type: integer
        estimatedOverageChargeCents:
          type: integer
        projectedBalanceAfterCents:
          type: integer

    AiTenantBalance:
      type: object
      required:
        - tenantId
        - tenantName
        - tenantEmail
        - tenantSlug
        - planSlug
        - planName
        - currency
        - balanceCents
        - totalTopUpCents
        - totalUsageCents
        - overageEnabled
        - blockOnZero
        - autoTopUpEnabled
        - autoTopUpThresholdCents
        - autoTopUpAmountCents
        - usage
        - included
        - overage
        - alertLevel
      properties:
        tenantId:
          type: string
        tenantName:
          type: string
        tenantEmail:
          type: string
          format: email
        tenantSlug:
          type: string
        planId:
          type: string
          nullable: true
        planSlug:
          type: string
        planName:
          type: string
        currency:
          type: string
          example: USD
        balanceCents:
          type: integer
        totalTopUpCents:
          type: integer
        totalUsageCents:
          type: integer
        overageEnabled:
          type: boolean
        blockOnZero:
          type: boolean
        autoTopUpEnabled:
          type: boolean
        autoTopUpThresholdCents:
          type: integer
        autoTopUpAmountCents:
          type: integer
        lastTopUpAt:
          type: string
          format: date-time
          nullable: true
        usage:
          $ref: "#/components/schemas/AiTenantUsage"
        included:
          $ref: "#/components/schemas/AiTenantIncludedQuota"
        overage:
          $ref: "#/components/schemas/AiTenantOverage"
        alertLevel:
          $ref: "#/components/schemas/AiBillingAlertLevel"

    AiBalanceOverview:
      type: object
      required: [summary, tenants, pricing, checkedAt]
      properties:
        summary:
          $ref: "#/components/schemas/AiBalanceSummary"
        tenants:
          type: array
          items:
            $ref: "#/components/schemas/AiTenantBalance"
        pricing:
          $ref: "#/components/schemas/AiPricingCatalog"
        checkedAt:
          type: string
          format: date-time

    AiBalanceOverviewEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AiBalanceOverview"

    AiBalanceTopUpRequest:
      type: object
      properties:
        amountCents:
          type: integer
          minimum: 1
          description: Top-up amount in cents. Preferred over amountUsd.
          example: 5000
        amountUsd:
          type: number
          minimum: 0
          description: Legacy convenience amount. The server converts it to cents.
          example: 50
        notes:
          type: string
          maxLength: 500

    AiBalanceTopUpResult:
      type: object
      required: [clientId, amountCents, balanceBeforeCents, balanceAfterCents]
      properties:
        clientId:
          type: string
        amountCents:
          type: integer
        balanceBeforeCents:
          type: integer
        balanceAfterCents:
          type: integer

    AiBalanceTopUpEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AiBalanceTopUpResult"
        message:
          type: string

    AiBalanceTenantSettingsRequest:
      type: object
      properties:
        overageEnabled:
          type: boolean
        blockOnZero:
          type: boolean
        autoTopUpEnabled:
          type: boolean
        autoTopUpThresholdCents:
          type: integer
          minimum: 0
        autoTopUpAmountCents:
          type: integer
          minimum: 0

    AiBalanceTenantSettingsResult:
      type: object
      required: [clientId]
      properties:
        clientId:
          type: string

    AiBalanceTenantSettingsEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AiBalanceTenantSettingsResult"
        message:
          type: string

    OAuthScope:
      type: string
      description: Known OAuth scope name. Only scopes marked `available` in `scopeCatalog` can be selected for new OAuth clients.
      enum:
        - openid
        - profile
        - email
        - offline_access
        - user.profile.read
        - user.profile.write
        - user.avatar.read
        - user.avatar.write
        - tenant.read
        - tenant.profile.read
        - tenant.profile.write
        - tenant.branding.read
        - tenant.branding.write
        - tenant.settings.read
        - tenant.settings.write
        - tenant.domains.read
        - tenant.domains.write
        - roles.read
        - staff.read
        - staff.write
        - staff.invite
        - staff.disable
        - runtime.tokens.write
        - runtime.sessions.read
        - runtime.sessions.write
        - agents.read
        - agents.write
        - agents.presence.read
        - agents.presence.write
        - contacts.read
        - contacts.write
        - contacts.delete
        - conversations.read
        - conversations.write
        - conversations.assign
        - conversations.close
        - calls.read
        - calls.write
        - calls.recordings.read
        - calls.recordings.delete
        - ai.read
        - ai.write
        - ai.knowledge.read
        - ai.knowledge.write
        - ai.usage.read
        - reports.read
        - reports.export
        - webhooks.read
        - webhooks.write
        - webhooks.delete
        - webhooks.test
        - developer.read
        - developer.write
        - sdk.keys.read
        - sdk.keys.write
        - oauth.clients.read
        - oauth.clients.write
        - media.read
        - media.write
        - media.delete
        - billing.read
        - billing.write

    OAuthScopeStatus:
      type: string
      enum: [available, planned]

    OAuthScopeCatalogItem:
      type: object
      required: [scope, category, status, sensitive, description]
      properties:
        scope:
          $ref: "#/components/schemas/OAuthScope"
        category:
          type: string
          example: Tenant / Business
        status:
          $ref: "#/components/schemas/OAuthScopeStatus"
        sensitive:
          type: boolean
        description:
          type: string

    OAuthClientType:
      type: string
      enum: [confidential, public]

    OAuthClientStatus:
      type: string
      enum: [active, revoked]

    OAuthClient:
      type: object
      required: [id, clientId, name, type, status, allowedScopes, redirectUris, requirePkce]
      properties:
        id:
          type: string
        clientId:
          type: string
          example: rn_oauth_xxxxxxxxxxxxxxxxxxxxxxxx
        name:
          type: string
        type:
          $ref: "#/components/schemas/OAuthClientType"
        status:
          $ref: "#/components/schemas/OAuthClientStatus"
        allowedScopes:
          type: array
          items:
            $ref: "#/components/schemas/OAuthScope"
        redirectUris:
          type: array
          items:
            type: string
            format: uri
        requirePkce:
          type: boolean
        lastUsedAt:
          type: string
          format: date-time
          nullable: true
        revokedAt:
          type: string
          format: date-time
          nullable: true
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    OAuthClientEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [oauthClient]
          properties:
            oauthClient:
              $ref: "#/components/schemas/OAuthClient"

    OAuthClientListEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [oauthClients, availableScopes, recommendedScopes, scopeCatalog, clientTypes, discoveryUrl]
          properties:
            oauthClients:
              type: array
              items:
                $ref: "#/components/schemas/OAuthClient"
            availableScopes:
              description: Scopes selectable for OAuth clients today.
              type: array
              items:
                $ref: "#/components/schemas/OAuthScope"
            recommendedScopes:
              type: array
              items:
                $ref: "#/components/schemas/OAuthScope"
            scopeCatalog:
              description: Full ideal scope catalog. Planned scopes are shown disabled in UI until matching APIs are ready.
              type: array
              items:
                $ref: "#/components/schemas/OAuthScopeCatalogItem"
            clientTypes:
              type: array
              items:
                $ref: "#/components/schemas/OAuthClientType"
            discoveryUrl:
              type: string
              format: uri

    OAuthClientSecretEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [oauthClient, warning]
          properties:
            oauthClient:
              $ref: "#/components/schemas/OAuthClient"
            clientSecret:
              type: string
              description: Returned once for confidential clients.
            warning:
              type: string

    OAuthClientDeletedEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [oauthClientId, deleted]
          properties:
            oauthClientId:
              type: string
            deleted:
              type: boolean
              const: true

    CreateOAuthClientRequest:
      type: object
      required: [name, redirectUris]
      properties:
        name:
          type: string
          maxLength: 120
        type:
          $ref: "#/components/schemas/OAuthClientType"
        redirectUris:
          type: array
          minItems: 1
          items:
            type: string
            format: uri
        allowedScopes:
          type: array
          items:
            $ref: "#/components/schemas/OAuthScope"
          default: [openid, profile, email, tenant.read, roles.read, developer.read, tenant.domains.read, tenant.domains.write]
        requirePkce:
          type: boolean
          description: Required for public/native clients. Optional for confidential CMS clients.

    UpdateOAuthClientRequest:
      type: object
      properties:
        name:
          type: string
          maxLength: 120
        type:
          $ref: "#/components/schemas/OAuthClientType"
        status:
          $ref: "#/components/schemas/OAuthClientStatus"
        redirectUris:
          type: array
          items:
            type: string
            format: uri
        allowedScopes:
          type: array
          items:
            $ref: "#/components/schemas/OAuthScope"
        requirePkce:
          type: boolean

    OpenIdConfiguration:
      type: object
      required:
        - issuer
        - authorization_endpoint
        - token_endpoint
        - userinfo_endpoint
        - jwks_uri
        - response_types_supported
        - subject_types_supported
        - id_token_signing_alg_values_supported
        - scopes_supported
        - token_endpoint_auth_methods_supported
        - claims_supported
      properties:
        issuer:
          type: string
          format: uri
          example: https://api.ringnity.com
        authorization_endpoint:
          type: string
          format: uri
          example: https://api.ringnity.com/oauth/authorize
        token_endpoint:
          type: string
          format: uri
          example: https://api.ringnity.com/oauth/token
        userinfo_endpoint:
          type: string
          format: uri
          example: https://api.ringnity.com/oauth/userinfo
        jwks_uri:
          type: string
          format: uri
          example: https://api.ringnity.com/oauth/jwks.json
        revocation_endpoint:
          type: string
          format: uri
          example: https://api.ringnity.com/oauth/revoke
        response_types_supported:
          type: array
          items:
            type: string
          example: [code]
        subject_types_supported:
          type: array
          items:
            type: string
          example: [public]
        id_token_signing_alg_values_supported:
          type: array
          items:
            type: string
          example: [RS256]
        scopes_supported:
          type: array
          items:
            $ref: "#/components/schemas/OAuthScope"
          example: [openid, profile, email, tenant.read, roles.read, tenant.domains.read, tenant.domains.write, developer.read, sdk.keys.read]
        token_endpoint_auth_methods_supported:
          type: array
          items:
            type: string
          example: [client_secret_basic, client_secret_post, none]
        code_challenge_methods_supported:
          type: array
          items:
            type: string
          example: [S256]
        claims_supported:
          type: array
          items:
            type: string
          example: [sub, name, email, tenant_id, tenant_slug, roles]

    OAuthTokenRequest:
      type: object
      required: [grant_type, client_id]
      properties:
        grant_type:
          type: string
          enum: [authorization_code, refresh_token]
        client_id:
          type: string
        client_secret:
          type: string
          description: Required for confidential clients. Never used by public/native clients.
        code:
          type: string
          description: Required when grant_type is authorization_code.
        redirect_uri:
          type: string
          format: uri
          description: Must exactly match the authorize request redirect URI.
        code_verifier:
          type: string
          description: Required for PKCE clients.
        refresh_token:
          type: string
          description: Required when grant_type is refresh_token.

    OAuthTokenResponse:
      type: object
      required: [access_token, token_type, expires_in]
      properties:
        access_token:
          type: string
        id_token:
          type: string
          description: OIDC ID token returned when the openid scope is granted.
        refresh_token:
          type: string
        token_type:
          type: string
          example: Bearer
        expires_in:
          type: integer
          example: 900
        scope:
          type: string
          example: openid profile email tenant.read roles.read

    OAuthUserInfo:
      type: object
      required: [sub]
      properties:
        sub:
          type: string
        name:
          type: string
        email:
          type: string
          format: email
        email_verified:
          type: boolean
        tenant_id:
          type: string
        tenant_slug:
          type: string
          example: your-tenant-slug
        roles:
          type: array
          items:
            type: string

    OAuthWidgetKeyEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [tenantId, tenantSlug, publicWidgetKey]
          properties:
            tenantId:
              type: string
            tenantSlug:
              type: string
              example: your-tenant-slug
            publicWidgetKey:
              type: string
              description: Public Website widget/install key safe for direct visitor app links, WordPress auto widget installs, and universal widget bootstrap.
            allowedDomains:
              type: array
              items:
                type: string
            allowedDomainsLimit:
              type: integer
              nullable: true
              description: Maximum domains allowed by the current plan. Null means unlimited.
            allowedDomainsUsed:
              type: integer
              description: Number of configured allowed domains.
            allowedDomainsRemaining:
              type: integer
              nullable: true
              description: Remaining domains before hitting the current plan quota. Null means unlimited.
            dashboardDeveloperUrl:
              type: string
              format: uri
              description: Tenant dashboard URL for manual allowed-domain management.
            domain:
              type: string
              description: Domain submitted to the add-domain endpoint, when present.
            domainAdded:
              type: boolean
              description: True when the submitted domain was added by this request.
            domainAlreadyAllowed:
              type: boolean
              description: True when the submitted domain was already covered by the allowed-domain list.

    OAuthAddAllowedDomainRequest:
      type: object
      required: [domain]
      properties:
        domain:
          type: string
          example: wordpress.example.com

    Jwk:
      type: object
      required: [kty, kid, use, alg, n, e]
      properties:
        kty:
          type: string
          example: RSA
        kid:
          type: string
        use:
          type: string
          example: sig
        alg:
          type: string
          example: RS256
        n:
          type: string
        e:
          type: string

    Jwks:
      type: object
      required: [keys]
      properties:
        keys:
          type: array
          items:
            $ref: "#/components/schemas/Jwk"

    OAuthRevokeRequest:
      type: object
      required: [token, client_id]
      properties:
        token:
          type: string
        token_type_hint:
          type: string
          enum: [access_token, refresh_token]
        client_id:
          type: string
        client_secret:
          type: string
          description: Required for confidential clients.

    OAuthError:
      type: object
      required: [error]
      properties:
        error:
          type: string
          enum:
            - invalid_request
            - invalid_client
            - invalid_grant
            - unauthorized_client
            - unsupported_grant_type
            - invalid_scope
            - temporarily_unavailable
        error_description:
          type: string
        state:
          type: string

    ErrorEnvelope:
      type: object
      required: [success, code, message]
      properties:
        success:
          type: boolean
          const: false
        code:
          $ref: "#/components/schemas/ErrorCode"
        message:
          type: string
        details:
          type: array
          items:
            type: string
        detail:
          $ref: "#/components/schemas/AnyObject"

    ErrorCode:
      type: string
      enum:
        - SDK_CLIENT_NOT_FOUND
        - SDK_CLIENT_INACTIVE
        - SDK_ORIGIN_NOT_ALLOWED
        - SDK_CONTEXT_TOKEN_INVALID
        - SDK_BOOTSTRAP_FAILED
        - SDK_SESSION_FAILED
        - SDK_SESSION_REQUIRED
        - SDK_DEVICE_TOKEN_INVALID
        - SDK_DEVICE_TOKEN_REGISTER_FAILED
        - SDK_DEVICE_TOKEN_UNREGISTER_FAILED
        - SDK_VERSION_UNSUPPORTED
        - FEATURE_NOT_AVAILABLE
        - SERVER_API_KEY_INVALID
        - SERVER_API_CONTEXT_MISSING
        - SERVER_API_SCOPE_DENIED
        - SERVER_CONTACT_VALIDATION_FAILED
        - SERVER_WEBHOOK_VALIDATION_FAILED
        - SERVER_WEBHOOK_NOT_FOUND
        - SERVER_API_REPORTS_FAILED
        - ADMIN_SDK_REPORTS_FAILED
        - REPORT_DATE_RANGE_INVALID
        - TOKEN_EXPIRED
        - RATE_LIMITED
        - NETWORK_ERROR
        - NETWORK_UNAVAILABLE
        - REALTIME_DISCONNECTED
        - CALL_PERMISSION_DENIED
        - CALL_SESSION_CONVERSATION_REQUIRED
        - CALL_SESSION_TYPE_INVALID
        - CALL_SESSION_NOT_FOUND
        - CALL_SESSION_CREATE_FAILED
        - CALL_SESSION_READ_FAILED
        - CALL_SESSION_END_FAILED
        - AI_NOT_READY

    SuccessEnvelope:
      type: object
      required: [success]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AnyObject"

    Tenant:
      type: object
      required: [clientId]
      properties:
        clientId:
          type: string
        slug:
          type: string
        name:
          type: string
        companyName:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string
        timezone:
          type: string
        isActive:
          type: boolean

    TenantEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            tenant:
              $ref: "#/components/schemas/Tenant"

    ServiceStatus:
      type: string
      enum:
        - available
        - disabled_by_admin
        - not_in_plan
        - not_configured
        - ai_not_ready
        - beta

    ServiceKey:
      type: string
      enum: [chat, voice_call, video_call, ai_chat, ai_voice, ai_video]

    ServiceCapability:
      type: object
      required: [key, status]
      properties:
        key:
          $ref: "#/components/schemas/ServiceKey"
        label:
          type: string
        status:
          $ref: "#/components/schemas/ServiceStatus"
        allowed:
          type: boolean
        reason:
          type: string
        requiredFeatures:
          type: array
          items:
            type: string
        adminEnabled:
          type: boolean
        sortOrder:
          type: integer

    RuntimeBootstrap:
      type: object
      required: [tenant, services]
      properties:
        tenant:
          $ref: "#/components/schemas/Tenant"
        branding:
          $ref: "#/components/schemas/AnyObject"
        services:
          type: array
          items:
            $ref: "#/components/schemas/ServiceCapability"
        sdk:
          $ref: "#/components/schemas/AnyObject"

    RuntimeBootstrapEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/RuntimeBootstrap"

    VisitorIdentity:
      type: object
      properties:
        externalId:
          type: string
        name:
          type: string
        email:
          type: string
          format: email
        phone:
          type: string

    CreateRuntimeSessionRequest:
      type: object
      properties:
        slug:
          type: string
        apiKey:
          type: string
        visitor:
          $ref: "#/components/schemas/VisitorIdentity"
        contextToken:
          type: string
        metadata:
          $ref: "#/components/schemas/AnyObject"

    RuntimeSession:
      type: object
      required: [sessionToken, expiresIn]
      properties:
        sessionToken:
          type: string
        expiresIn:
          type: integer
        expiresAt:
          type: string
          format: date-time

    RuntimeSessionEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/RuntimeSession"

    DevicePushPlatform:
      type: string
      enum: [android, ios, flutter, react_native, kotlin, swift, web]

    DevicePushProvider:
      type: string
      enum: [fcm, apns, apns_voip]

    DevicePushAudience:
      type: string
      enum: [visitor, customer, agent, admin]

    SdkCallMode:
      type: string
      enum: [basic, native-phone]

    RegisterDevicePushTokenRequest:
      type: object
      required: [platform, token]
      properties:
        platform:
          $ref: "#/components/schemas/DevicePushPlatform"
        provider:
          $ref: "#/components/schemas/DevicePushProvider"
          default: fcm
        token:
          type: string
          description: FCM, APNs, or APNs VoIP token returned by the client app.
        deviceId:
          type: string
          description: Stable app-generated device identifier. Do not send hardware identifiers.
        appId:
          type: string
          description: Bundle ID, package name, or application ID.
        audience:
          $ref: "#/components/schemas/DevicePushAudience"
          default: visitor
        externalId:
          type: string
          description: Customer-user or visitor ID from the customer's application.
        agentId:
          type: string
          description: Optional Ringnity agent ID when the runtime belongs to an agent surface.
        callMode:
          $ref: "#/components/schemas/SdkCallMode"
          default: basic
        metadata:
          $ref: "#/components/schemas/AnyObject"

    UnregisterDevicePushTokenRequest:
      type: object
      properties:
        deviceId:
          type: string
        provider:
          $ref: "#/components/schemas/DevicePushProvider"
          default: fcm
        token:
          type: string
      anyOf:
        - required: [deviceId]
        - required: [token]

    DevicePushToken:
      type: object
      required: [id, clientId, platform, provider, audience, enabled]
      properties:
        id:
          type: string
        clientId:
          type: string
        platform:
          $ref: "#/components/schemas/DevicePushPlatform"
        provider:
          $ref: "#/components/schemas/DevicePushProvider"
        audience:
          $ref: "#/components/schemas/DevicePushAudience"
        deviceId:
          type: string
          nullable: true
        appId:
          type: string
          nullable: true
        externalId:
          type: string
          nullable: true
        agentId:
          type: string
          nullable: true
        callMode:
          $ref: "#/components/schemas/SdkCallMode"
        enabled:
          type: boolean
        lastSeenAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    DevicePushTokenEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [device]
          properties:
            device:
              $ref: "#/components/schemas/DevicePushToken"

    DevicePushTokenDisabledEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [disabled]
          properties:
            disabled:
              type: integer
              minimum: 0

    DeveloperSettings:
      type: object
      properties:
        tenant:
          $ref: "#/components/schemas/Tenant"
        allowedDomains:
          description: Domain list is limited by the tenant plan contract quota `allowed_domains`.
          type: array
          items:
            type: string
        publicKey:
          type: string
        services:
          type: array
          items:
            $ref: "#/components/schemas/ServiceCapability"

    DeveloperSettingsEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/DeveloperSettings"

    UpdateAllowedDomainsRequest:
      type: object
      required: [allowedDomains]
      properties:
        allowedDomains:
          type: array
          items:
            type: string

    DeveloperTestRequest:
      type: object
      properties:
        origin:
          type: string
        slug:
          type: string
        service:
          $ref: "#/components/schemas/ServiceKey"

    ServerApiScope:
      type: string
      enum:
        - server:health
        - server:tenant:read
        - server:visitor-context:write
        - server:agent-token:write
        - server:admin-token:write
        - server:reports:read
        - server:webhook:read
        - server:webhook:write
        - server:contacts:read
        - server:contacts:write

    ServerApiCredential:
      type: object
      required: [id, name, scopes]
      properties:
        id:
          type: string
        name:
          type: string
        environment:
          type: string
          enum: [test, live]
        scopes:
          type: array
          items:
            $ref: "#/components/schemas/ServerApiScope"
        isActive:
          type: boolean
        secretPreview:
          type: string
        lastUsedAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    ServerApiCredentialEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            credential:
              $ref: "#/components/schemas/ServerApiCredential"

    ServerApiCredentialListEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            credentials:
              type: array
              items:
                $ref: "#/components/schemas/ServerApiCredential"

    ServerApiCredentialSecretEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            credential:
              $ref: "#/components/schemas/ServerApiCredential"
            secret:
              type: string
              description: Returned once. Store only on customer backend servers.

    CreateServerApiCredentialRequest:
      type: object
      required: [name, scopes]
      properties:
        name:
          type: string
        environment:
          type: string
          enum: [test, live]
          default: live
        scopes:
          type: array
          items:
            $ref: "#/components/schemas/ServerApiScope"

    UpdateServerApiCredentialRequest:
      type: object
      properties:
        name:
          type: string
        environment:
          type: string
          enum: [test, live]
        scopes:
          type: array
          items:
            $ref: "#/components/schemas/ServerApiScope"
        isActive:
          type: boolean

    CreateVisitorContextTokenRequest:
      type: object
      properties:
        visitor:
          $ref: "#/components/schemas/VisitorIdentity"
        metadata:
          $ref: "#/components/schemas/AnyObject"
        expiresIn:
          type: integer
          default: 900

    CreateAgentTokenRequest:
      type: object
      properties:
        agentId:
          type: string
        username:
          type: string
        name:
          type: string
        departmentId:
          type: string
        scopes:
          type: array
          items:
            type: string
        expiresIn:
          type: integer
          default: 900

    CreateAdminTokenRequest:
      type: object
      properties:
        adminActorId:
          type: string
        username:
          type: string
        name:
          type: string
        role:
          type: string
        scopes:
          type: array
          items:
            type: string
        expiresIn:
          type: integer
          default: 900

    SdkToken:
      type: object
      required: [token, expiresIn]
      properties:
        token:
          type: string
        tokenType:
          type: string
          default: Bearer
        expiresIn:
          type: integer
        expiresAt:
          type: string
          format: date-time
        scopes:
          type: array
          items:
            type: string

    SdkTokenEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/SdkToken"

    ContactInput:
      type: object
      required: [name, phone]
      properties:
        externalId:
          type: string
        name:
          type: string
        phone:
          type: string
        email:
          type: string
          format: email
        company:
          type: string
        tags:
          type: array
          items:
            type: string
        notes:
          type: string

    ContactPatchInput:
      type: object
      properties:
        externalId:
          type: string
        name:
          type: string
        phone:
          type: string
        email:
          type: string
          format: email
        company:
          type: string
        tags:
          type: array
          items:
            type: string
        notes:
          type: string

    Contact:
      allOf:
        - $ref: "#/components/schemas/ContactInput"
        - type: object
          required: [id]
          properties:
            id:
              type: string
            source:
              type: string
            createdAt:
              type: string
              format: date-time
            updatedAt:
              type: string
              format: date-time

    ContactEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            contact:
              $ref: "#/components/schemas/Contact"

    ContactDeletedEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [deleted, id]
          properties:
            deleted:
              type: boolean
              const: true
            id:
              type: string

    Pagination:
      type: object
      properties:
        total:
          type: integer
        page:
          type: integer
        limit:
          type: integer
        hasMore:
          type: boolean

    ContactListEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [contacts]
          properties:
            contacts:
              type: array
              items:
                $ref: "#/components/schemas/Contact"
            pagination:
              $ref: "#/components/schemas/Pagination"

    WebhookInput:
      type: object
      required: [url, events]
      properties:
        name:
          type: string
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
        secret:
          type: string

    WebhookPatchRequest:
      type: object
      properties:
        name:
          type: string
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
        isActive:
          type: boolean
        secret:
          type: string

    Webhook:
      type: object
      properties:
        id:
          type: string
        name:
          type: string
        url:
          type: string
        secretPreview:
          type: string
        events:
          type: array
          items:
            type: string
        isActive:
          type: boolean
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    WebhookEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            webhook:
              $ref: "#/components/schemas/Webhook"
            secret:
              type: string

    WebhookListEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            webhooks:
              type: array
              items:
                $ref: "#/components/schemas/Webhook"
            availableEvents:
              type: array
              items:
                type: string

    WebhookDelivery:
      type: object
      properties:
        id:
          type: string
        webhookId:
          type: string
        event:
          type: string
        deliveryId:
          type: string
        status:
          type: string
        url:
          type: string
        responseStatus:
          type: integer
        responseBody:
          type: string
        errorMessage:
          type: string
        durationMs:
          type: integer
        attempt:
          type: integer
        createdAt:
          type: string
          format: date-time
        deliveredAt:
          type: string
          format: date-time

    WebhookDeliveryListEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            deliveries:
              type: array
              items:
                $ref: "#/components/schemas/WebhookDelivery"

    WebhookTestRequest:
      type: object
      properties:
        webhookId:
          type: string
        event:
          type: string

    WebhookTestEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AnyObject"

    AgentProfile:
      type: object
      required: [agentId]
      properties:
        agentId:
          type: string
        username:
          type: string
        name:
          type: string
        role:
          type: string
        departmentId:
          type: string
        departmentName:
          type: string
        scopes:
          type: array
          items:
            type: string

    AgentProfileEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            agent:
              $ref: "#/components/schemas/AgentProfile"
            tenant:
              $ref: "#/components/schemas/Tenant"

    Presence:
      type: object
      properties:
        agentId:
          type: string
        status:
          type: string
          enum: [available, busy, offline]
        online:
          type: boolean
        lastActivityAt:
          type: string
          format: date-time
        currentCallWith:
          type: string

    PresenceEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/Presence"

    UpdatePresenceRequest:
      type: object
      required: [status]
      properties:
        status:
          type: string
          enum: [available, busy, offline, online, unavailable]

    Conversation:
      type: object
      required: [conversationId, status]
      properties:
        conversationId:
          type: string
        conversationNumber:
          type: string
        ticketId:
          type: string
        contactId:
          type: string
        visitorId:
          type: string
        departmentId:
          type: string
        assignedAgentId:
          type: string
        channel:
          type: string
        status:
          type: string
        priority:
          type: string
        subject:
          type: string
        lastMessageAt:
          type: string
          format: date-time
        createdAt:
          type: string
          format: date-time
        updatedAt:
          type: string
          format: date-time

    ConversationEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            conversation:
              $ref: "#/components/schemas/Conversation"

    ConversationListEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [conversations]
          properties:
            conversations:
              type: array
              items:
                $ref: "#/components/schemas/Conversation"
            nextCursor:
              type: string

    AssignConversationRequest:
      type: object
      properties:
        assignedAgentId:
          type: string
          nullable: true
          description: Target agent ID. Omit to let the current agent accept the conversation.
        assignedDepartmentId:
          type: string
          nullable: true
          description: Target department ID when transferring back to a team queue.
        reason:
          type: string
          maxLength: 500
        isTransfer:
          type: boolean
          default: false

    Assignment:
      type: object
      properties:
        assignmentId:
          type: string
        conversationId:
          type: string
        clientId:
          type: string
        assignedAgentId:
          type: string
          nullable: true
        assignedDepartmentId:
          type: string
          nullable: true
        assignedByAgentId:
          type: string
          nullable: true
        reason:
          type: string
          nullable: true
        createdAt:
          type: string
          format: date-time

    AssignmentEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [assignment]
          properties:
            assignment:
              $ref: "#/components/schemas/Assignment"
            conversation:
              $ref: "#/components/schemas/Conversation"

    Message:
      type: object
      properties:
        messageId:
          type: string
        conversationId:
          type: string
        senderType:
          type: string
        senderAgentId:
          type: string
        senderContactId:
          type: string
        body:
          type: string
        messageType:
          type: string
        metadata:
          $ref: "#/components/schemas/AnyObject"
        clientMessageId:
          type: string
        sentAt:
          type: string
          format: date-time
        editedAt:
          type: string
          format: date-time
        deletedAt:
          type: string
          format: date-time

    MessageEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            message:
              $ref: "#/components/schemas/Message"

    MessageListEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            messages:
              type: array
              items:
                $ref: "#/components/schemas/Message"
            nextCursor:
              type: string

    SendMessageRequest:
      type: object
      required: [body]
      properties:
        body:
          type: string
        messageType:
          type: string
          default: text
        metadata:
          $ref: "#/components/schemas/AnyObject"
        clientMessageId:
          type: string

    VisitorMessageRequest:
      allOf:
        - $ref: "#/components/schemas/SendMessageRequest"
        - type: object
          properties:
            visitor:
              $ref: "#/components/schemas/VisitorIdentity"

    MessagePatchRequest:
      type: object
      properties:
        body:
          type: string
        metadata:
          $ref: "#/components/schemas/AnyObject"

    ReportPeriod:
      type: object
      required: [type, start, end]
      properties:
        type:
          type: string
          enum: [today, 7days, 30days, custom]
        start:
          type: string
          format: date-time
        end:
          type: string
          format: date-time

    ReportCallSummary:
      type: object
      properties:
        totalIncoming:
          type: integer
        totalAnswered:
          type: integer
        totalMissed:
          type: integer
        answerRate:
          type: integer
          minimum: 0
          maximum: 100
        avgDurationSeconds:
          type: integer
        totalHandled:
          type: integer
        currentQueueCount:
          type: integer

    ReportChatSummary:
      type: object
      properties:
        totalConversations:
          type: integer
        openConversations:
          type: integer
        pendingConversations:
          type: integer
        resolvedConversations:
          type: integer
        closedConversations:
          type: integer
        totalMessages:
          type: integer
        agentMessages:
          type: integer
        visitorMessages:
          type: integer
        conversationResolutionRate:
          type: integer
          minimum: 0
          maximum: 100

    ReportSatisfactionSummary:
      type: object
      properties:
        avgRatingScore:
          type: number
        totalRatings:
          type: integer
        satisfactionRate:
          type: integer
          minimum: 0
          maximum: 100

    ReportSummary:
      allOf:
        - $ref: "#/components/schemas/ReportCallSummary"
        - $ref: "#/components/schemas/ReportChatSummary"
        - $ref: "#/components/schemas/ReportSatisfactionSummary"
        - type: object
          properties:
            calls:
              $ref: "#/components/schemas/ReportCallSummary"
            chat:
              $ref: "#/components/schemas/ReportChatSummary"
            satisfaction:
              $ref: "#/components/schemas/ReportSatisfactionSummary"

    ReportAgentBreakdownItem:
      type: object
      properties:
        agentId:
          type: string
        agentName:
          type: string
        username:
          type: string
        callsHandled:
          type: integer
        avgDurationSeconds:
          type: integer
        avgRating:
          type: number
        totalRatings:
          type: integer
        conversationsAssigned:
          type: integer

    ReportTimeseriesPoint:
      type: object
      required: [date]
      properties:
        date:
          type: string
          format: date
        callsTotal:
          type: integer
        callsAnswered:
          type: integer
        callsMissed:
          type: integer
        conversations:
          type: integer
        conversationsResolved:
          type: integer
        messages:
          type: integer

    Report:
      type: object
      required: [period, summary, agentBreakdown, timeseries]
      properties:
        period:
          $ref: "#/components/schemas/ReportPeriod"
        summary:
          $ref: "#/components/schemas/ReportSummary"
        agentBreakdown:
          type: array
          items:
            $ref: "#/components/schemas/ReportAgentBreakdownItem"
        timeseries:
          type: array
          items:
            $ref: "#/components/schemas/ReportTimeseriesPoint"

    ReportSummaryEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/Report"

    ReportTimeseriesEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [period, timeseries]
          properties:
            period:
              $ref: "#/components/schemas/ReportPeriod"
            timeseries:
              type: array
              items:
                $ref: "#/components/schemas/ReportTimeseriesPoint"

    ReportExportEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [report, exportedAt, format]
          properties:
            report:
              $ref: "#/components/schemas/Report"
            exportedAt:
              type: string
              format: date-time
            format:
              type: string
              enum: [json, csv]

    AdminTenantEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            tenant:
              $ref: "#/components/schemas/Tenant"
            admin:
              $ref: "#/components/schemas/AnyObject"

    DomainListEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            allowedDomains:
              type: array
              items:
                type: string

    ServiceListEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            capabilities:
              $ref: "#/components/schemas/AnyObject"
            services:
              type: array
              items:
                $ref: "#/components/schemas/ServiceCapability"

    SubscriptionEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            subscription:
              $ref: "#/components/schemas/AnyObject"

    AiReadinessEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            ai:
              $ref: "#/components/schemas/AnyObject"
            canUseAiFeature:
              type: boolean

    StaffSummaryEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AnyObject"

    Readiness:
      type: object
      properties:
        services:
          type: array
          items:
            $ref: "#/components/schemas/ServiceCapability"
        ai:
          $ref: "#/components/schemas/AnyObject"
        reports:
          $ref: "#/components/schemas/AnyObject"

    ReadinessEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/Readiness"

    CallSessionType:
      type: string
      enum: [audio, video]

    CallSessionDirection:
      type: string
      enum: [inbound, outbound]

    CallSessionStatus:
      type: string
      enum: [started, answered, ended, missed, failed]

    CallSession:
      type: object
      required: [id, conversationId, type, direction, status]
      properties:
        id:
          type: string
        conversationId:
          type: string
        type:
          $ref: "#/components/schemas/CallSessionType"
        direction:
          $ref: "#/components/schemas/CallSessionDirection"
        status:
          $ref: "#/components/schemas/CallSessionStatus"
        startedAt:
          type: string
          format: date-time
        answeredAt:
          type: string
          format: date-time
        endedAt:
          type: string
          format: date-time
        durationSeconds:
          type: integer
          nullable: true
        endReason:
          type: string
          nullable: true
        metadata:
          $ref: "#/components/schemas/AnyObject"

    CreateCallSessionRequest:
      type: object
      required: [conversationId, type]
      properties:
        conversationId:
          type: string
        type:
          $ref: "#/components/schemas/CallSessionType"
        direction:
          $ref: "#/components/schemas/CallSessionDirection"
          default: inbound
        status:
          $ref: "#/components/schemas/CallSessionStatus"
          default: started
        sessionToken:
          type: string
          description: Visitor chat session token when called from public visitor runtime.
        startedAt:
          type: string
          format: date-time
        metadata:
          $ref: "#/components/schemas/AnyObject"

    EndCallSessionRequest:
      type: object
      properties:
        reason:
          type: string
          maxLength: 255
        sessionToken:
          type: string
          description: Visitor chat session token when called from public visitor runtime.

    CallSessionEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [callSession]
          properties:
            callSession:
              $ref: "#/components/schemas/CallSession"

    VisitorSessionRequest:
      type: object
      properties:
        visitor:
          $ref: "#/components/schemas/VisitorIdentity"
        metadata:
          $ref: "#/components/schemas/AnyObject"

    VisitorSessionEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AnyObject"

    VisitorConversationRequest:
      type: object
      properties:
        subject:
          type: string
        visitor:
          $ref: "#/components/schemas/VisitorIdentity"
        metadata:
          $ref: "#/components/schemas/AnyObject"

    TicketEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AnyObject"

    AvailabilityEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            available:
              type: boolean
            status:
              $ref: "#/components/schemas/ServiceStatus"
            reason:
              type: string

    AttachmentRequest:
      type: object
      properties:
        filename:
          type: string
        contentType:
          type: string
        size:
          type: integer
        metadata:
          $ref: "#/components/schemas/AnyObject"

    AttachmentEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AnyObject"

    DeleteMediaRequest:
      type: object
      properties:
        reason:
          type: string
          maxLength: 500

    AttachmentDeleteEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          required: [message, deleted]
          properties:
            message:
              $ref: "#/components/schemas/Message"
            attachment:
              $ref: "#/components/schemas/AnyObject"
            deleted:
              type: boolean
              const: true

    CallLinkRequest:
      type: object
      properties:
        service:
          type: string
          enum: [voice_call, video_call]
        metadata:
          $ref: "#/components/schemas/AnyObject"

    CallLinkEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AnyObject"

    ReceiptEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          $ref: "#/components/schemas/AnyObject"

    AiRoutingEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            mode:
              type: string
              enum: [human_only, ai_first, ai_assisted]
            canUseAi:
              type: boolean
            humanFallbackAvailable:
              type: boolean

    AiConversationRequest:
      type: object
      properties:
        visitor:
          $ref: "#/components/schemas/VisitorIdentity"
        metadata:
          $ref: "#/components/schemas/AnyObject"

    AiConversationEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            aiConversationId:
              type: string
            conversationId:
              type: string

    AiChatRequest:
      type: object
      required: [message]
      properties:
        aiConversationId:
          type: string
        conversationId:
          type: string
        message:
          type: string
        stream:
          type: boolean
          description: When true, SDKs should use the realtime streaming contract for response deltas.
        metadata:
          $ref: "#/components/schemas/AnyObject"

    AiChatEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            aiConversationId:
              type: string
            conversationId:
              type: string
            message:
              type: string
            handoffRecommended:
              type: boolean
            metadata:
              $ref: "#/components/schemas/AnyObject"

    KnowledgeSearchRequest:
      type: object
      required: [query]
      properties:
        query:
          type: string
        limit:
          type: integer
          default: 5

    KnowledgeSearchEnvelope:
      type: object
      properties:
        success:
          type: boolean
          const: true
        data:
          type: object
          properties:
            results:
              type: array
              items:
                $ref: "#/components/schemas/AnyObject"

    KnowledgeUrlRequest:
      type: object
      required: [url]
      properties:
        url:
          type: string
          format: uri

    AiKnowledgeEnvelope:
      type: object
      properties:
        success:
          type: boolean
        data:
          $ref: "#/components/schemas/AnyObject"
      additionalProperties: true
