openapi: 3.1.2
info:
  title: Salesbud API
  version: 0.1.0
  description: >-
    Company-scoped API for server-to-server Salesbud integrations. OAuth client credentials
    authorize access to completed meetings owned by one company.
  contact:
    name: Salesbud
servers:
  - url: https://api.salesbud.com.br
    description: Production
tags:
  - name: Authentication
    description: OAuth 2.0 client credentials token issuance.
  - name: Context
    description: Authenticated API client and company context.
  - name: Meetings
    description: Completed meetings visible to the authenticated company.
  - name: Calls
    description: Completed VoIP calls visible to the authenticated company.
  - name: Transcripts
    description: Sanitized transcripts for completed company meetings.
  - name: Emails
    description: >-
      Deduplicated email conversations from every mailbox connected to the authenticated company,
      with their messages behind a second scope.
  - name: WhatsApp
    description: >-
      WhatsApp conversations of the company's sellers, captured from WhatsApp Web or RD Conversas,
      with their messages behind a second scope.
  - name: Health
    description: Container liveness and readiness probes.
paths:
  /oauth/token:
    post:
      operationId: issueAccessToken
      summary: Issue an access token
      tags: [Authentication]
      security: []
      requestBody:
        required: true
        content:
          application/x-www-form-urlencoded:
            schema:
              type: object
              additionalProperties: false
              required: [grant_type]
              properties:
                grant_type:
                  type: string
                  const: client_credentials
                client_id:
                  type: string
                  description: May instead be sent through HTTP Basic authentication.
                client_secret:
                  type: string
                  format: password
                  description: May instead be sent through HTTP Basic authentication.
                scope:
                  type: string
                  example: meetings.read calls.read
      responses:
        "200":
          description: Access token issued.
          headers:
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/TokenResponse"
        "400":
          $ref: "#/components/responses/OAuthBadRequest"
        "401":
          $ref: "#/components/responses/OAuthUnauthorized"
        "413":
          $ref: "#/components/responses/PayloadTooLarge"
        "415":
          $ref: "#/components/responses/UnsupportedMediaType"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/context:
    get:
      operationId: getApiContext
      summary: Get the authenticated integration context
      tags: [Context]
      security:
        - bearerAuth: []
      responses:
        "200":
          description: API client, company, scopes and rate-limit context.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiContextResponse"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/meetings:
    get:
      operationId: listMeetings
      summary: List completed meetings
      tags: [Meetings]
      security:
        - bearerAuth: [meetings.read]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/CreatedAfter"
        - $ref: "#/components/parameters/CreatedBefore"
        - $ref: "#/components/parameters/MeetingAfter"
        - $ref: "#/components/parameters/MeetingBefore"
        - $ref: "#/components/parameters/OwnerEmail"
        - $ref: "#/components/parameters/MediaType"
        - $ref: "#/components/parameters/Audience"
        - $ref: "#/components/parameters/HasTranscript"
      responses:
        "200":
          description: Company-scoped meeting page.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeetingListResponse"
              examples:
                default:
                  $ref: "#/components/examples/MeetingPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/meetings/{meeting_id}:
    get:
      operationId: getMeeting
      summary: Get one completed meeting
      tags: [Meetings]
      security:
        - bearerAuth: [meetings.read]
      parameters:
        - $ref: "#/components/parameters/MeetingId"
      responses:
        "200":
          description: Company-scoped meeting.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeetingResponse"
              examples:
                default:
                  $ref: "#/components/examples/MeetingResource"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/meetings/{meeting_id}/evaluations/overall:
    get:
      operationId: getOverallMeetingEvaluation
      summary: Get the overall meeting evaluation
      tags: [Meetings]
      security:
        - bearerAuth: [meetings.read]
      parameters:
        - $ref: "#/components/parameters/MeetingId"
      responses:
        "200":
          description: Overall evaluation state and result.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OverallMeetingEvaluationResponse"
              examples:
                default:
                  $ref: "#/components/examples/MeetingEvaluation"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/meetings/{meeting_id}/answers:
    get:
      operationId: getMeetingAnswers
      summary: List the default template answers for a meeting
      tags: [Meetings]
      security:
        - bearerAuth: [meetings.read]
      parameters:
        - $ref: "#/components/parameters/MeetingId"
      responses:
        "200":
          description: Answers generated for the meeting owner context.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeetingAnswersResponse"
              examples:
                default:
                  $ref: "#/components/examples/MeetingAnswers"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/meetings/{meeting_id}/transcript:
    get:
      operationId: getMeetingTranscript
      summary: Get a meeting transcript
      description: >-
        Returns the enhanced transcript when available, otherwise the original transcript. An
        unavailable transcript is represented by a successful response with available set to false
        and its current processing status.
      tags: [Transcripts]
      security:
        - bearerAuth: [meetings.read, transcriptions.read]
      parameters:
        - $ref: "#/components/parameters/MeetingId"
      responses:
        "200":
          description: Company-scoped meeting transcript.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeetingTranscriptResponse"
              examples:
                default:
                  $ref: "#/components/examples/MeetingTranscript"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/calls:
    get:
      operationId: listCalls
      summary: List completed calls
      description: >-
        Calls are the records captured from a VoIP integration, addressed by their own `call_`
        identifiers. They answer the same five routes as meetings and carry the same fields except
        `bot_history`: a VoIP capture has no recording bot, so the field is absent, not empty.
      tags: [Calls]
      security:
        - bearerAuth: [calls.read]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/CreatedAfter"
        - $ref: "#/components/parameters/CreatedBefore"
        - $ref: "#/components/parameters/MeetingAfter"
        - $ref: "#/components/parameters/MeetingBefore"
        - $ref: "#/components/parameters/OwnerEmail"
        - $ref: "#/components/parameters/MediaType"
        - $ref: "#/components/parameters/Audience"
        - $ref: "#/components/parameters/HasTranscript"
      responses:
        "200":
          description: Company-scoped call page.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallListResponse"
              examples:
                default:
                  $ref: "#/components/examples/CallPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/calls/{call_id}:
    get:
      operationId: getCall
      summary: Get one completed call
      tags: [Calls]
      security:
        - bearerAuth: [calls.read]
      parameters:
        - $ref: "#/components/parameters/CallId"
      responses:
        "200":
          description: Company-scoped call.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CallResponse"
              examples:
                default:
                  $ref: "#/components/examples/CallResource"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/calls/{call_id}/evaluations/overall:
    get:
      operationId: getOverallCallEvaluation
      summary: Get the overall call evaluation
      tags: [Calls]
      security:
        - bearerAuth: [calls.read]
      parameters:
        - $ref: "#/components/parameters/CallId"
      responses:
        "200":
          description: Overall evaluation state and result.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OverallMeetingEvaluationResponse"
              examples:
                default:
                  $ref: "#/components/examples/CallEvaluation"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/calls/{call_id}/answers:
    get:
      operationId: getCallAnswers
      summary: List the default template answers for a call
      tags: [Calls]
      security:
        - bearerAuth: [calls.read]
      parameters:
        - $ref: "#/components/parameters/CallId"
      responses:
        "200":
          description: Answers generated for the call owner context.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeetingAnswersResponse"
              examples:
                default:
                  $ref: "#/components/examples/CallAnswers"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/calls/{call_id}/transcript:
    get:
      operationId: getCallTranscript
      summary: Get a call transcript
      description: >-
        Returns the enhanced transcript when available, otherwise the original transcript. An
        unavailable transcript is represented by a successful response with available set to false
        and its current processing status.
      tags: [Transcripts, Calls]
      security:
        - bearerAuth: [calls.read, transcriptions.read]
      parameters:
        - $ref: "#/components/parameters/CallId"
      responses:
        "200":
          description: Company-scoped call transcript.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/MeetingTranscriptResponse"
              examples:
                default:
                  $ref: "#/components/examples/CallTranscript"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/emails:
    get:
      operationId: listEmails
      summary: List email conversations
      description: >-
        Conversations are deduplicated across every mailbox connected to the company: two sellers on
        the same thread yield one conversation with two mailboxes. Ordered by `last_message_at`
        ascending (oldest activity first); pages forward with a signed keyset cursor. Message bodies
        live under `/v1/emails/{email_id}/messages` and need `emails.content.read`.
      tags: [Emails]
      security:
        - bearerAuth: [emails.read]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/LastMessageAfter"
        - $ref: "#/components/parameters/LastMessageBefore"
      responses:
        "200":
          description: Company-scoped email conversation page.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailListResponse"
              examples:
                default:
                  $ref: "#/components/examples/EmailPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/emails/{email_id}:
    get:
      operationId: getEmail
      summary: Get an email conversation
      description: >-
        Conversation metadata only. An `eml_` id issued to another company, or any id with a
        different prefix, is a 404, not a hint.
      tags: [Emails]
      security:
        - bearerAuth: [emails.read]
      parameters:
        - $ref: "#/components/parameters/EmailId"
      responses:
        "200":
          description: Company-scoped email conversation.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailResponse"
              examples:
                default:
                  $ref: "#/components/examples/EmailConversation"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/emails/{email_id}/messages:
    get:
      operationId: listEmailMessages
      summary: List the messages of an email conversation
      description: >-
        Messages in the order they were sent, oldest first, with plain-text bodies and attachment
        metadata (no attachment content). Governed by the `email_content` rate-limit policy in
        addition to the client and company policies. The cursor is bound to the conversation.
      tags: [Emails]
      security:
        - bearerAuth: [emails.read, emails.content.read]
      parameters:
        - $ref: "#/components/parameters/EmailId"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Company-scoped email message page.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/EmailMessageListResponse"
              examples:
                default:
                  $ref: "#/components/examples/EmailMessagePage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/whatsapp:
    get:
      operationId: listWhatsAppConversations
      summary: List WhatsApp conversations
      description: >-
        One conversation per chat of a company seller, from WhatsApp Web or RD Conversas. Ordered by
        `last_message_at` ascending (oldest activity first); pages forward with a signed keyset
        cursor. `last_message_at` moves only when a message arrives: an edit or a deletion does not
        move the conversation, so re-read messages to observe them. Message content lives under
        `/v1/whatsapp/{whatsapp_id}/messages` and needs `whatsapp.content.read`.
      tags: [WhatsApp]
      security:
        - bearerAuth: [whatsapp.read]
      parameters:
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
        - $ref: "#/components/parameters/LastMessageAfter"
        - $ref: "#/components/parameters/LastMessageBefore"
      responses:
        "200":
          description: Company-scoped WhatsApp conversation page.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WhatsAppListResponse"
              examples:
                default:
                  $ref: "#/components/examples/WhatsAppPage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/whatsapp/{whatsapp_id}:
    get:
      operationId: getWhatsAppConversation
      summary: Get a WhatsApp conversation
      description: >-
        Conversation metadata only: contact, seller, linked accounts and activity instants. A `wa_`
        id issued to another company, or any id with a different prefix, is a 404, not a hint. A
        deleted chat keeps answering, with `deleted_at` and `deleted_reason` set.
      tags: [WhatsApp]
      security:
        - bearerAuth: [whatsapp.read]
      parameters:
        - $ref: "#/components/parameters/WhatsAppId"
      responses:
        "200":
          description: Company-scoped WhatsApp conversation.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WhatsAppResponse"
              examples:
                default:
                  $ref: "#/components/examples/WhatsAppConversation"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /v1/whatsapp/{whatsapp_id}/messages:
    get:
      operationId: listWhatsAppMessages
      summary: List the messages of a WhatsApp conversation
      description: >-
        Messages in the order they were sent, oldest first, with sanitized text, media metadata (no
        media content) and the audio transcription when one exists. A message deleted for everyone
        stays in place as a tombstone with `deleted_at` set and every content field null; an edited
        message carries its current text and `edited_at`. Governed by the `whatsapp_content`
        rate-limit policy in addition to the client and company policies. The cursor is a keyset
        over `(sent_at, id)` bound to the conversation.
      tags: [WhatsApp]
      security:
        - bearerAuth: [whatsapp.read, whatsapp.content.read]
      parameters:
        - $ref: "#/components/parameters/WhatsAppId"
        - $ref: "#/components/parameters/Limit"
        - $ref: "#/components/parameters/Cursor"
      responses:
        "200":
          description: Company-scoped WhatsApp message page.
          headers:
            X-Request-Id:
              $ref: "#/components/headers/RequestId"
            RateLimit-Policy:
              $ref: "#/components/headers/RateLimitPolicy"
            RateLimit:
              $ref: "#/components/headers/RateLimit"
            Cache-Control:
              $ref: "#/components/headers/CacheControlNoStore"
            Pragma:
              $ref: "#/components/headers/PragmaNoCache"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/WhatsAppMessageListResponse"
              examples:
                default:
                  $ref: "#/components/examples/WhatsAppMessagePage"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
components:
  examples:
    MeetingPage:
      summary: A page of meetings.
      value:
        data:
          - id: mtg_ExampleMeeting01
            object: meeting
            title: Discovery - Acme
            status: completed
            type: video
            audience: external
            meeting_at: "2026-01-14T13:00:00.000Z"
            duration_seconds: 2840
            no_show: false
            participants:
              - display_name: Example Buyer
                email: buyer@example.com
                kind: external
              - email: procurement@example.com
                kind: external
            owner:
              id: usr_ExampleOwner01
              name: Example Rep
              email: rep@example.com
              team:
                id: team_ExampleTeam01
                name: Inside Sales
                role: participant
                is_default: true
            template:
              id: tpl_ExampleTemplate01
              name: Discovery script
              answers:
                - field: qst_ExampleQuestion01
                  question: What pains did the customer describe?
                  answer: Manual CRM entry after every call, about two hours a day.
            tags:
              - id: tag_ExampleTag01
                name: Enterprise
            customer_questions:
              - question: Does it integrate with our CRM?
                category: integrations
            competitors:
              - id: ctr_ExampleCompetitor01
                name: Example CRM
            enablement:
              next_steps: Send the proposal by Friday covering 5,400 seats.
              meeting_type: qualification
              speaking_duration:
                users:
                  - user:
                      id: usr_ExampleOwner01
                      name: Example Rep
                      email: rep@example.com
                    duration_seconds: 1760
                others_seconds: 1080
            accounts:
              - id: acc_ExampleAccount01
                domain: acme.example.com
                cnpj: "12345678000199"
            overall_evaluation:
              status: completed
              justification: Strong discovery, weak on next steps.
            skill_scores: []
            bot_history: []
            transcript:
              available: true
              status: completed
            created_at: "2026-01-14T12:58:47.000Z"
            updated_at: "2026-01-14T14:10:43.000Z"
        pagination:
          limit: 50
          has_more: true
          next_cursor: cur_ExampleCursor01
        request_id: req_ExampleRequestId01
    MeetingResource:
      summary: One completed meeting.
      value:
        data:
          id: mtg_ExampleMeeting01
          object: meeting
          title: Discovery - Acme
          status: completed
          type: video
          audience: external
          meeting_at: "2026-01-14T13:00:00.000Z"
          duration_seconds: 2840
          no_show: false
          participants:
            - display_name: Example Buyer
              email: buyer@example.com
              kind: external
            - email: procurement@example.com
              kind: external
          owner:
            id: usr_ExampleOwner01
            name: Example Rep
            email: rep@example.com
            team:
              id: team_ExampleTeam01
              name: Inside Sales
              role: participant
              is_default: true
          template:
            id: tpl_ExampleTemplate01
            name: Discovery script
            answers:
              - field: qst_ExampleQuestion01
                question: What pains did the customer describe?
                answer: Manual CRM entry after every call, about two hours a day.
          tags:
            - id: tag_ExampleTag01
              name: Enterprise
          customer_questions:
            - question: Does it integrate with our CRM?
              category: integrations
          competitors:
            - id: ctr_ExampleCompetitor01
              name: Example CRM
          enablement:
            next_steps: Send the proposal by Friday covering 5,400 seats.
            meeting_type: qualification
            speaking_duration:
              users:
                - user:
                    id: usr_ExampleOwner01
                    name: Example Rep
                    email: rep@example.com
                  duration_seconds: 1760
              others_seconds: 1080
          accounts:
            - id: acc_ExampleAccount01
              domain: acme.example.com
              cnpj: "12345678000199"
          overall_evaluation:
            status: completed
            justification: Strong discovery, weak on next steps.
          skill_scores: []
          bot_history: []
          transcript:
            available: true
            status: completed
          created_at: "2026-01-14T12:58:47.000Z"
          updated_at: "2026-01-14T14:10:43.000Z"
        request_id: req_ExampleRequestId01
    MeetingTranscript:
      summary: An available meeting transcript.
      value:
        data:
          object: transcript
          meeting_id: mtg_ExampleMeeting01
          available: true
          status: completed
          variant: enhanced
          utterances:
            - speaker: Example Rep
              text: Walk me through how the team logs a call today.
              start_ms: 12400
              end_ms: 16800
            - speaker: buyer@example.com
              text: Manually, right after. It costs us about two hours a day.
              start_ms: 17200
              end_ms: 23900
          created_at: "2026-01-14T14:02:00.000Z"
          updated_at: "2026-01-14T14:09:12.000Z"
        request_id: req_ExampleRequestId01
    MeetingAnswers:
      summary: Template answers for a meeting.
      value:
        data:
          - id: ans_ExampleAnswer01
            object: meeting_answer
            question:
              id: qst_ExampleQuestion01
              text: What pains did the customer describe?
              type: text
              order: 1
            answer: Manual CRM entry after every call, about two hours a day.
            updated_at: "2026-01-14T14:10:43.000Z"
        request_id: req_ExampleRequestId01
    MeetingEvaluation:
      summary: A finished meeting evaluation.
      value:
        data:
          id: evl_ExampleEvaluation01
          object: overall_meeting_evaluation
          meeting_id: mtg_ExampleMeeting01
          status: completed
          score: 8
          justification: Strong discovery, weak on next steps.
          created_at: "2026-01-14T14:05:00.000Z"
          updated_at: "2026-01-14T14:10:43.000Z"
        request_id: req_ExampleRequestId01
    CallPage:
      summary: A page of calls.
      value:
        data:
          - id: call_ExampleCall01
            object: call
            title: Call to +55 11 99999-0000
            status: completed
            type: audio
            audience: external
            meeting_at: "2026-01-15T18:28:01.000Z"
            duration_seconds: 714
            no_show: false
            participants:
              - phone: "+5511999990000"
                kind: external
            owner:
              id: usr_ExampleOwner01
              name: Example Rep
              email: rep@example.com
              team:
                id: team_ExampleTeam01
                name: Inside Sales
                role: participant
                is_default: true
            template:
              id: tpl_ExampleTemplate01
              name: Discovery script
              answers:
                - field: qst_ExampleQuestion01
                  question: What pains did the customer describe?
                  answer: Wants a demo before renewing the current contract.
            tags: []
            customer_questions: []
            competitors: []
            enablement:
              next_steps: Online meeting booked for Thursday at 13:30; send the invite today.
              meeting_type: null
              speaking_duration:
                users: []
                others_seconds: 677
            accounts: []
            overall_evaluation:
              status: not_started
              justification: null
            skill_scores: []
            transcript:
              available: false
              status: not_started
            created_at: "2026-01-15T19:22:08.000Z"
            updated_at: "2026-01-15T19:22:53.000Z"
        pagination:
          limit: 50
          has_more: true
          next_cursor: cur_ExampleCursor02
        request_id: req_ExampleRequestId01
    CallResource:
      summary: One completed call.
      value:
        data:
          id: call_ExampleCall01
          object: call
          title: Call to +55 11 99999-0000
          status: completed
          type: audio
          audience: external
          meeting_at: "2026-01-15T18:28:01.000Z"
          duration_seconds: 714
          no_show: false
          participants:
            - phone: "+5511999990000"
              kind: external
          owner:
            id: usr_ExampleOwner01
            name: Example Rep
            email: rep@example.com
            team:
              id: team_ExampleTeam01
              name: Inside Sales
              role: participant
              is_default: true
          template:
            id: tpl_ExampleTemplate01
            name: Discovery script
            answers:
              - field: qst_ExampleQuestion01
                question: What pains did the customer describe?
                answer: Wants a demo before renewing the current contract.
          tags: []
          customer_questions: []
          competitors: []
          enablement:
            next_steps: Online meeting booked for Thursday at 13:30; send the invite today.
            meeting_type: null
            speaking_duration:
              users: []
              others_seconds: 677
          accounts: []
          overall_evaluation:
            status: not_started
            justification: null
          skill_scores: []
          transcript:
            available: false
            status: not_started
          created_at: "2026-01-15T19:22:08.000Z"
          updated_at: "2026-01-15T19:22:53.000Z"
        request_id: req_ExampleRequestId01
    CallTranscript:
      summary: A call transcript that has not been produced yet.
      value:
        data:
          object: transcript
          meeting_id: call_ExampleCall01
          available: false
          status: processing
          variant: null
          utterances: []
          created_at: null
          updated_at: null
        request_id: req_ExampleRequestId01
    CallAnswers:
      summary: Template answers for a call.
      value:
        data:
          - id: ans_ExampleAnswer01
            object: meeting_answer
            question:
              id: qst_ExampleQuestion01
              text: What pains did the customer describe?
              type: text
              order: 1
            answer: Manual CRM entry after every call, about two hours a day.
            updated_at: "2026-01-14T14:10:43.000Z"
        request_id: req_ExampleRequestId01
    CallEvaluation:
      summary: A call with no evaluation yet.
      value:
        data:
          id: null
          object: overall_meeting_evaluation
          meeting_id: call_ExampleCall01
          status: not_started
          score: null
          justification: null
          created_at: null
          updated_at: null
        request_id: req_ExampleRequestId01
    EmailPage:
      summary: A page of email conversations.
      value:
        data:
          - id: eml_01JEXAMPLE
            object: email_conversation
            subject: Assunto de exemplo
            first_message_at: "2026-01-10T12:00:00.000Z"
            last_message_at: "2026-01-12T15:30:00.000Z"
            message_count: 3
            has_attachments: true
            last_message_direction: inbound
            participants:
              - address: vendedora@example.com
                name: Vendedora Exemplo
                internal: true
              - address: cliente@example.net
                name: Cliente Exemplo
                internal: false
            mailboxes:
              - address: vendedora@example.com
                owner:
                  id: usr_01JEXAMPLE
                  name: Vendedora Exemplo
                  email: vendedora@example.com
              - address: vendas@example.com
                owner: null
            accounts:
              - id: acc_01JEXAMPLE
                domain: example.net
                cnpj: "00.000.000/0001-00"
        pagination:
          limit: 50
          has_more: true
          next_cursor: cur_ExampleCursor03
        request_id: req_ExampleRequestId01
    EmailConversation:
      summary: One email conversation.
      value:
        data:
          id: eml_01JEXAMPLE
          object: email_conversation
          subject: Assunto de exemplo
          first_message_at: "2026-01-10T12:00:00.000Z"
          last_message_at: "2026-01-12T15:30:00.000Z"
          message_count: 3
          has_attachments: true
          last_message_direction: inbound
          participants:
            - address: vendedora@example.com
              name: Vendedora Exemplo
              internal: true
            - address: cliente@example.net
              name: Cliente Exemplo
              internal: false
          mailboxes:
            - address: vendedora@example.com
              owner:
                id: usr_01JEXAMPLE
                name: Vendedora Exemplo
                email: vendedora@example.com
            - address: vendas@example.com
              owner: null
          accounts:
            - id: acc_01JEXAMPLE
              domain: example.net
              cnpj: "00.000.000/0001-00"
        request_id: req_ExampleRequestId01
    EmailMessagePage:
      summary: A page of email messages, oldest first.
      value:
        data:
          - id: emsg_01JEXAMPLE
            object: email_message
            conversation_id: eml_01JEXAMPLE
            sent_at: "2026-01-12T15:30:00.000Z"
            direction: inbound
            subject: "Re: Assunto de exemplo"
            snippet: Trecho de exemplo da mensagem
            body_text: "Corpo de exemplo da mensagem.


              Atenciosamente,

              Cliente Exemplo"
            from:
              address: cliente@example.net
              name: Cliente Exemplo
              internal: false
            to:
              - address: vendedora@example.com
                name: Vendedora Exemplo
                internal: true
            cc:
              - address: financeiro@example.net
                name: null
                internal: false
            attachments:
              - filename: exemplo.pdf
                mime_type: application/pdf
                size_bytes: 182400
        pagination:
          limit: 50
          has_more: false
          next_cursor: null
        request_id: req_ExampleRequestId01
    WhatsAppPage:
      summary: A page of WhatsApp conversations, oldest activity first.
      value:
        data:
          - id: wa_01JEXAMPLE
            object: whatsapp_conversation
            source: whatsapp_web
            is_group: false
            contact:
              phone: "+5511999990001"
              name: Cliente Exemplo
            seller:
              user:
                id: usr_01JEXAMPLE
                name: Vendedora Exemplo
                email: vendedora@example.com
              phone: "+5511988880001"
            accounts:
              - id: acc_01JEXAMPLE
                domain: example.net
                cnpj: "00.000.000/0001-00"
            first_message_at: "2026-09-10T12:00:00.000Z"
            last_message_at: "2026-09-12T15:30:00.000Z"
            last_message_direction: inbound
            deleted_at: null
            deleted_reason: null
          - id: wa_01JGROUP
            object: whatsapp_conversation
            source: whatsapp_web
            is_group: true
            contact:
              phone: null
              name: Grupo Vendas Exemplo
            seller:
              user:
                id: usr_01JEXAMPLE
                name: Vendedora Exemplo
                email: vendedora@example.com
              phone: "+5511988880001"
            accounts: []
            first_message_at: "2026-09-11T09:00:00.000Z"
            last_message_at: "2026-09-12T18:00:00.000Z"
            last_message_direction: outbound
            deleted_at: null
            deleted_reason: null
        pagination:
          limit: 50
          has_more: true
          next_cursor: cur_ExampleCursor04
        request_id: req_ExampleRequestId01
    WhatsAppConversation:
      summary: One WhatsApp conversation.
      value:
        data:
          id: wa_01JEXAMPLE
          object: whatsapp_conversation
          source: whatsapp_web
          is_group: false
          contact:
            phone: "+5511999990001"
            name: Cliente Exemplo
          seller:
            user:
              id: usr_01JEXAMPLE
              name: Vendedora Exemplo
              email: vendedora@example.com
            phone: "+5511988880001"
          accounts:
            - id: acc_01JEXAMPLE
              domain: example.net
              cnpj: "00.000.000/0001-00"
          first_message_at: "2026-09-10T12:00:00.000Z"
          last_message_at: "2026-09-12T15:30:00.000Z"
          last_message_direction: inbound
          deleted_at: null
          deleted_reason: null
        request_id: req_ExampleRequestId01
    WhatsAppMessagePage:
      summary: A page of WhatsApp messages, oldest first, with an audio and a tombstone.
      value:
        data:
          - id: wamsg_01JEXAMPLE1
            object: whatsapp_message
            conversation_id: wa_01JEXAMPLE
            sent_at: "2026-09-12T15:30:00.000Z"
            direction: inbound
            author:
              phone: "+5511999990001"
              name: Cliente Exemplo
            type: text
            text: Pode me mandar a proposta?
            media: null
            transcription: null
            edited_at: null
            deleted_at: null
            deleted_reason: null
          - id: wamsg_01JEXAMPLE2
            object: whatsapp_message
            conversation_id: wa_01JEXAMPLE
            sent_at: "2026-09-12T15:32:00.000Z"
            direction: outbound
            author:
              phone: "+5511988880001"
              name: Vendedora Exemplo
            type: audio
            text: null
            media:
              file_name: null
              mime_type: audio/ogg; codecs=opus
              size_bytes: 48213
            transcription: Claro, envio ainda hoje por e-mail.
            edited_at: null
            deleted_at: null
            deleted_reason: null
          - id: wamsg_01JEXAMPLE3
            object: whatsapp_message
            conversation_id: wa_01JEXAMPLE
            sent_at: "2026-09-12T15:35:00.000Z"
            direction: inbound
            author:
              phone: "+5511999990001"
              name: Cliente Exemplo
            type: text
            text: null
            media: null
            transcription: null
            edited_at: null
            deleted_at: "2026-09-12T15:36:00.000Z"
            deleted_reason: revoked_for_everyone
        pagination:
          limit: 50
          has_more: false
          next_cursor: null
        request_id: req_ExampleRequestId01
  parameters:
    Limit:
      name: limit
      in: query
      description: >-
        Records per page, 1 to 100. A page may return fewer than this while has_more is still true;
        drive the loop by has_more, not by the size of data.
      schema: {type: integer, minimum: 1, maximum: 100, default: 50}
    Cursor:
      name: cursor
      in: query
      description: Signed cursor returned by the previous page.
      schema: {type: string}
    CreatedAfter:
      name: created_after
      in: query
      description: Return records created after this instant.
      schema: {type: string, format: date-time}
    CreatedBefore:
      name: created_before
      in: query
      description: Return records created before this instant.
      schema: {type: string, format: date-time}
    MeetingAfter:
      name: meeting_after
      in: query
      description: Return records whose event instant is after this value.
      schema: {type: string, format: date-time}
    MeetingBefore:
      name: meeting_before
      in: query
      description: Return records whose event instant is before this value.
      schema: {type: string, format: date-time}
    OwnerEmail:
      name: owner_email
      in: query
      description: Exact case-insensitive owner email match.
      schema: {type: string, format: email}
    MediaType:
      name: type
      in: query
      description: >-
        Media the record was captured as. Independent of the resource kind: a call can be video and
        a meeting can be audio.
      schema: {type: string, enum: [video, audio]}
    Audience:
      name: audience
      in: query
      description: >-
        Whether the record had participants from outside the company (external) or only internal
        ones.
      schema: {type: string, enum: [internal, external]}
    HasTranscript:
      name: has_transcript
      in: query
      description: Filter by existence of a transcript resource.
      schema: {type: boolean}
    MeetingId:
      name: meeting_id
      in: path
      required: true
      schema:
        type: string
        pattern: "^mtg_[A-Za-z0-9_-]+$"
    CallId:
      name: call_id
      in: path
      required: true
      schema:
        type: string
        pattern: "^call_[A-Za-z0-9_-]+$"
    EmailId:
      name: email_id
      in: path
      required: true
      schema:
        type: string
        pattern: "^eml_[A-Za-z0-9_-]+$"
    WhatsAppId:
      name: whatsapp_id
      in: path
      required: true
      schema:
        type: string
        pattern: "^wa_[A-Za-z0-9_-]+$"
    LastMessageAfter:
      name: last_message_after
      in: query
      description: >-
        Return conversations whose latest message is after this instant. Filters on activity, not on
        record changes: a conversation that gains a message moves forward and will be seen again.
      schema: {type: string, format: date-time}
    LastMessageBefore:
      name: last_message_before
      in: query
      description: Return conversations whose latest message is before this instant.
      schema: {type: string, format: date-time}
  securitySchemes:
    bearerAuth:
      type: oauth2
      flows:
        clientCredentials:
          tokenUrl: /oauth/token
          scopes:
            meetings.read: Read completed company meetings, including their template answers and overall
              evaluations.
            calls.read: Read completed company calls, including their template answers and overall evaluations.
            transcriptions.read: Read transcripts. Combined with meetings.read or calls.read, which decides
              whose transcripts the credential reads.
            emails.read: Read email conversation metadata (participants, mailboxes, linked accounts, timestamps)
              for the company's connected mailboxes.
            emails.content.read: Read email message bodies, snippets and attachment metadata. Requires
              emails.read on the same credential.
            whatsapp.read: Read WhatsApp conversation metadata (contact, seller, linked accounts, activity
              instants) for the company's sellers.
            whatsapp.content.read: Read WhatsApp message text, media metadata and audio transcriptions. Requires
              whatsapp.read on the same credential.
  responses:
    OAuthBadRequest:
      description: OAuth client-credentials request rejected.
      content:
        application/json:
          schema: {$ref: "#/components/schemas/OAuthErrorResponse"}
    OAuthUnauthorized:
      description: OAuth client authentication failed.
      headers:
        WWW-Authenticate:
          schema: {type: string}
          example: 'Basic realm="oauth/token"'
      content:
        application/json:
          schema: {$ref: "#/components/schemas/OAuthErrorResponse"}
    BadRequest:
      description: Invalid request.
      content:
        application/json:
          schema: {$ref: "#/components/schemas/ErrorResponse"}
    PayloadTooLarge:
      description: The request body is larger than the accepted limit.
      content:
        application/json:
          schema: {$ref: "#/components/schemas/ErrorResponse"}
    UnsupportedMediaType:
      description: The request media type is not supported by this endpoint.
      content:
        application/json:
          schema: {$ref: "#/components/schemas/ErrorResponse"}
    Unauthorized:
      description: Missing or invalid credentials.
      content:
        application/json:
          schema: {$ref: "#/components/schemas/ErrorResponse"}
    Forbidden:
      description: The token does not contain the required scope.
      content:
        application/json:
          schema: {$ref: "#/components/schemas/ErrorResponse"}
    NotFound:
      description: Resource not found in the authenticated company.
      content:
        application/json:
          schema: {$ref: "#/components/schemas/ErrorResponse"}
    RateLimited:
      description: Client rate limit exceeded.
      headers:
        Retry-After:
          schema: {type: integer}
        RateLimit-Policy:
          $ref: "#/components/headers/RateLimitPolicy"
        RateLimit:
          $ref: "#/components/headers/RateLimit"
        Cache-Control:
          $ref: "#/components/headers/CacheControlNoStore"
        Pragma:
          $ref: "#/components/headers/PragmaNoCache"
      content:
        application/json:
          schema: {$ref: "#/components/schemas/ErrorResponse"}
    ServiceUnavailable:
      description: A required audit or rate-limit dependency is unavailable.
      content:
        application/json:
          schema: {$ref: "#/components/schemas/ErrorResponse"}
  headers:
    RequestId:
      description: Correlation identifier for the request.
      schema:
        type: string
        pattern: "^req_[A-Za-z0-9_-]{8,100}$"
      example: req_01JPUBLICAPI
    RateLimitPolicy:
      description: >-
        Every token-bucket policy the request consumed, comma separated, each with its quota and
        window in seconds. A data plane read consumes the per-credential `client` bucket and the
        per-company `company` bucket; a transcript read adds a stricter `transcript` bucket. All of
        them apply at once, so pace against the lowest quota your traffic reaches.
      schema:
        type: string
      example: '"client";q=120;w=60, "company";q=600;w=60'
    RateLimit:
      description: >-
        The single policy closest to exhaustion, with its remaining quota and the seconds until
        another token. Which policy this names changes as the buckets drain, so read the ceilings
        from RateLimit-Policy rather than from here. Remaining is capped by the bucket burst, which
        is lower than the quota, so it does not count down from the quota.
      schema:
        type: string
      example: '"client";r=119;t=1'
    CacheControlNoStore:
      description: Prevents shared or client-side caches from storing sensitive responses.
      schema:
        type: string
        const: no-store
      example: no-store
    PragmaNoCache:
      description: Backward-compatible no-cache directive.
      schema:
        type: string
        const: no-cache
      example: no-cache
  schemas:
    OAuthErrorResponse:
      type: object
      additionalProperties: false
      required: [error, error_description]
      properties:
        error:
          type: string
          enum: [invalid_request, invalid_client, invalid_scope, unsupported_grant_type]
        error_description:
          type: string
    ApiContextResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          $ref: "#/components/schemas/ApiContextV1"
        request_id:
          type: string
      example:
        data:
          object: api_context
          client:
            id: sb_client_ExampleClient01
            name: acme-sync
          company:
            id: cmp_ExampleCompany01
            name: Example Company
          scopes:
            - meetings.read
            - calls.read
            - transcriptions.read
          rate_limit:
            requests_per_minute: 120
        request_id: req_ExampleRequestId01
    ApiContextV1:
      type: object
      additionalProperties: false
      required: [object, client, company, scopes, rate_limit]
      properties:
        object:
          type: string
          const: api_context
        client:
          type: object
          additionalProperties: false
          required: [id, name]
          properties:
            id: {type: string, pattern: "^sb_client_[A-Za-z0-9_-]+$"}
            name: {type: string}
        company:
          type: object
          additionalProperties: false
          required: [id, name]
          properties:
            id: {type: string, pattern: "^cmp_[A-Za-z0-9_-]+$"}
            name: {type: string}
        scopes:
          type: array
          items: {type: string}
        rate_limit:
          type: object
          additionalProperties: false
          required: [requests_per_minute]
          properties:
            requests_per_minute: {type: integer, minimum: 1}
    TokenResponse:
      type: object
      additionalProperties: false
      required: [access_token, token_type, expires_in, scope]
      properties:
        access_token:
          type: string
        token_type:
          type: string
          const: Bearer
        expires_in:
          type: integer
          minimum: 1
          example: 3600
        scope:
          type: string
          example: meetings.read calls.read transcriptions.read
      example:
        access_token: eyJhbGciOiJSUzI1NiJ9.EXAMPLE_PAYLOAD.EXAMPLE_SIGNATURE
        token_type: Bearer
        expires_in: 3600
        scope: meetings.read calls.read transcriptions.read
    MeetingListResponse:
      type: object
      additionalProperties: false
      required: [data, pagination, request_id]
      properties:
        data:
          type: array
          items: {$ref: "#/components/schemas/MeetingV1"}
        pagination:
          $ref: "#/components/schemas/PaginationV1"
        request_id:
          type: string
    CallListResponse:
      type: object
      additionalProperties: false
      required: [data, pagination, request_id]
      properties:
        data:
          type: array
          items: {$ref: "#/components/schemas/CallV1"}
        pagination:
          $ref: "#/components/schemas/PaginationV1"
        request_id:
          type: string
    CallResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          $ref: "#/components/schemas/CallV1"
        request_id:
          type: string
    MeetingResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          $ref: "#/components/schemas/MeetingV1"
        request_id:
          type: string
    OverallMeetingEvaluationResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          $ref: "#/components/schemas/OverallMeetingEvaluationV1"
        request_id:
          type: string
    MeetingAnswersResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          type: array
          items:
            $ref: "#/components/schemas/MeetingAnswerV1"
        request_id:
          type: string
    MeetingTranscriptResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          $ref: "#/components/schemas/MeetingTranscriptV1"
        request_id:
          type: string
    HealthResponse:
      type: object
      additionalProperties: false
      required: [status, service, environment, timestamp, request_id]
      properties:
        status:
          type: string
          const: ok
          description: Current probe status.
        service:
          type: string
          description: Service identifier.
          example: salesbud-api
        environment:
          type: string
          description: Runtime environment.
          example: prod
        timestamp:
          type: string
          format: date-time
          description: UTC instant at which the probe was evaluated.
        request_id:
          type: string
          description: Correlation identifier for this request.
          pattern: "^req_[A-Za-z0-9_-]{8,100}$"
    ErrorResponse:
      type: object
      additionalProperties: false
      required: [error]
      properties:
        error:
          type: object
          additionalProperties: false
          required: [type, title, detail, code, request_id]
          properties:
            type:
              type: string
              description: Error class, shared with the other Salesbud services.
              example: BadRequestError
            title:
              type: string
              description: HTTP reason phrase for the status.
              example: Bad Request
            detail:
              type: string
              description: Human-readable message without internal details.
              example: Request validation failed.
            code:
              type: string
              description: >-
                Machine-readable error code. This is the field to branch on: it is stable across
                versions, while title and detail are wording.
              example: INVALID_REQUEST
            request_id:
              type: string
              description: Correlation identifier for support.
              example: req_01JPUBLICAPI
      example:
        error:
          type: ForbiddenError
          title: Forbidden
          detail: The transcriptions.read scope is required.
          code: INSUFFICIENT_SCOPE
          request_id: req_ExampleRequestId01
    PaginationV1:
      type: object
      additionalProperties: false
      required: [limit, has_more, next_cursor]
      properties:
        limit:
          type: integer
          minimum: 1
          maximum: 100
          description: Number of resources requested.
          example: 50
        has_more:
          type: boolean
          description: >-
            Whether another page is available. A page can be short or empty while this is true, so
            always continue while it is true instead of relying on the size of data.
        next_cursor:
          type: [string, "null"]
          description: Signed opaque cursor for the next page.
          example: eyJ2IjoyLCJhZnRlcklkIjo0Mn0
    MeetingParticipantV1:
      type: object
      additionalProperties: false
      required: [kind]
      minProperties: 2
      properties:
        display_name:
          type: string
          minLength: 1
          description: Sanitized external participant name when available.
          example: Marina Costa
        email:
          type: string
          format: email
          description: Normalized lowercase email when available.
          example: buyer@example.com
        phone:
          type: string
          description: Normalized phone number when available.
          example: "+5547999990000"
        kind:
          type: string
          const: external
          description: Participant classification in V1.
    TemplateReferenceV1:
      type: object
      additionalProperties: false
      required: [id, name, answers]
      properties:
        id:
          type: string
          pattern: "^tpl_[A-Za-z0-9_-]+$"
          description: Opaque public template identifier.
          example: tpl_01JDISCOVERY
        name:
          type: string
          minLength: 1
          description: Public display name.
          example: Discovery
        answers:
          type: array
          description: Sanitized template questions and meeting answers.
          items:
            $ref: "#/components/schemas/MeetingTemplateAnswerV1"
    MeetingTemplateAnswerV1:
      type: object
      additionalProperties: false
      required: [field, question, answer]
      properties:
        field:
          type: string
          pattern: "^qst_[A-Za-z0-9_-]+$"
          description: Opaque public template question field identifier.
        question:
          type: string
          minLength: 1
          description: Question text as it was asked, sanitized.
        answer:
          type: string
          minLength: 1
          description: >-
            Answer generated for this question. A question that produced none is left out of the
            list rather than returned with a null answer.
    TagReferenceV1:
      type: object
      additionalProperties: false
      required: [id, name]
      properties:
        id:
          type: string
          pattern: "^tag_[A-Za-z0-9_-]+$"
          description: Opaque public tag identifier.
          example: tag_01JENTERPRISE
        name:
          type: string
          minLength: 1
          description: Public display name.
          example: Enterprise
    MeetingOwnerTeamV1:
      type: object
      additionalProperties: false
      required: [id, name, role, is_default]
      properties:
        id:
          type: string
          pattern: "^team_[A-Za-z0-9_-]+$"
          description: Opaque team identifier.
        name:
          type: string
          minLength: 1
          description: Team display name.
        role:
          type: string
          enum: [participant, leadership, observer]
          description: >-
            Role of the owner in the team. `participant` sees only their own meetings; `leadership`
            sees the team's and is visible to it; `observer` sees the team's without appearing to
            the other members.
          example: participant
        is_default:
          type: boolean
          description: >-
            Whether this is the owner's default team in the company. False when the owner has no
            default team there and the API reported another one instead, which is why the field
            exists rather than being implied.
    MeetingOwnerV1:
      type: object
      additionalProperties: false
      required: [id, name, email, team]
      properties:
        id:
          type: string
          pattern: "^usr_[A-Za-z0-9_-]+$"
          description: Opaque owner identifier.
          example: usr_01JOWNER
        name:
          type: string
          minLength: 1
          description: Public owner display name.
          example: Example Rep
        email:
          type: [string, "null"]
          format: email
          description: Normalized owner email, or null when legacy data is malformed.
          example: rep@example.com
        team:
          oneOf:
            - $ref: "#/components/schemas/MeetingOwnerTeamV1"
            - type: "null"
          description: Owner default team inside the authenticated company.
    AccountReferenceV1:
      type: object
      additionalProperties: false
      required: [id, domain, cnpj]
      properties:
        id:
          type: string
          pattern: "^acc_[A-Za-z0-9_-]+$"
          description: Opaque account identifier.
        domain:
          type: string
          minLength: 1
          description: Account email domain, lowercased.
        cnpj:
          type: [string, "null"]
          minLength: 1
          description: Brazilian company registration number as stored, with or without punctuation, or null
            when unknown.
    MeetingCustomerQuestionV1:
      type: object
      additionalProperties: false
      required: [question, category]
      properties:
        question:
          type: string
          minLength: 1
          description: Question the customer asked during the conversation.
        category:
          type: string
          minLength: 1
          description: Category the question was classified into.
    MeetingCompetitorV1:
      type: object
      additionalProperties: false
      required: [id, name]
      properties:
        id:
          type: string
          pattern: "^ctr_[A-Za-z0-9_-]+$"
          description: Opaque competitor identifier.
        name:
          type: string
          minLength: 1
          description: Competitor name as registered by the company, not as spoken.
    MeetingEnablementV1:
      type: object
      additionalProperties: false
      required: [next_steps, meeting_type, speaking_duration]
      properties:
        next_steps:
          type: [string, "null"]
          minLength: 1
          description: Next steps agreed in the conversation, or null when none were identified.
        meeting_type:
          type: [string, "null"]
          enum:
            - qualification
            - negotiation
            - proposal
            - closing
            - technical_meeting
            - questions_clarifications
            - null
          description: >-
            Sales stage the conversation was classified as, or null. Often null on calls: the
            classifier was built for sales meetings.
        speaking_duration:
          type: object
          additionalProperties: false
          required: [users, others_seconds]
          properties:
            users:
              type: array
              description: >-
                One entry per company user who spoke. A user who did not speak is left out rather
                than reported with zero.
              items:
                type: object
                additionalProperties: false
                required: [user, duration_seconds]
                properties:
                  user:
                    $ref: "#/components/schemas/PublicUserReferenceV1"
                  duration_seconds:
                    type: integer
                    minimum: 0
                    description: How long this user spoke, in seconds.
            others_seconds:
              type: [integer, "null"]
              minimum: 0
              description: >-
                How long everyone outside the company spoke, in seconds, or null when the split was
                not measured. Not broken down per person: the other side is not identified by user.
          description: How long each side spoke.
    PublicUserReferenceV1:
      type: object
      description: >-
        A company user as the API exposes them anywhere other than `owner`: the same identifier,
        without the team.
      additionalProperties: false
      required: [id, name, email]
      properties:
        id:
          type: string
          pattern: "^usr_[A-Za-z0-9_-]+$"
          description: Opaque user identifier, the same one `owner.id` carries.
        name:
          type: string
          minLength: 1
          description: Public user display name.
        email:
          type: [string, "null"]
          format: email
          description: Normalized lowercase user email, or null when legacy data is malformed.
    MeetingSkillScoreV1:
      type: object
      additionalProperties: false
      required: [user, skill, score, justification]
      properties:
        user:
          $ref: "#/components/schemas/PublicUserReferenceV1"
          description: The user the score refers to.
        skill:
          type: object
          additionalProperties: false
          required: [id, name]
          properties:
            id:
              type: string
              pattern: "^skl_[A-Za-z0-9_-]+$"
              description: Opaque skill identifier.
            name:
              type: string
              minLength: 1
              description: Skill name as the company registered it.
          description: The skill that was scored.
        score:
          type: integer
          minimum: 0
          maximum: 10
          description: Score from 0 to 10.
        justification:
          type: [string, "null"]
          minLength: 1
          description: Why the score was given, or null when the evaluation produced none.
    MeetingBotEventV1:
      type: object
      additionalProperties: false
      required: [code, subcode, occurred_at]
      properties:
        code:
          type: [string, "null"]
          minLength: 1
          description: What happened to the recording bot, as a stable code.
        subcode:
          type: [string, "null"]
          minLength: 1
          description: >-
            Further detail on the event, or null when the code carries none.
        occurred_at:
          type: string
          format: date-time
          description: When the event happened.
    OverallMeetingEvaluationV1:
      type: object
      additionalProperties: false
      required:
        - id
        - object
        - meeting_id
        - status
        - score
        - justification
        - created_at
        - updated_at
      properties:
        id:
          type: [string, "null"]
          pattern: "^evl_[A-Za-z0-9_-]+$"
          description: Opaque evaluation ID, null while no evaluation exists.
        object:
          const: overall_meeting_evaluation
          description: Resource type. Always overall_meeting_evaluation, including for calls.
        meeting_id:
          type: string
          pattern: "^(mtg|call)_[A-Za-z0-9_-]+$"
          description: >-
            The record this evaluation belongs to. Carries the prefix of its collection: mtg_ or
            call_.
        status:
          $ref: "#/components/schemas/ProcessingStatusV1"
        score:
          type: [integer, "null"]
          minimum: 0
          maximum: 10
          description: Score from 0 to 10, or null until the evaluation completes.
        justification:
          type: [string, "null"]
          minLength: 1
          description: Why the score was given, or null until the evaluation completes.
        created_at:
          type: [string, "null"]
          format: date-time
          description: When the evaluation was created, or null when none exists yet.
        updated_at:
          type: [string, "null"]
          format: date-time
          description: When the evaluation last changed, or null when none exists yet.
    MeetingAnswerV1:
      type: object
      additionalProperties: false
      required: [id, object, question, answer, updated_at]
      properties:
        id:
          type: string
          pattern: "^ans_[A-Za-z0-9_-]+$"
          description: Opaque answer identifier.
        object:
          const: meeting_answer
          description: Resource type. Always meeting_answer, including for calls.
        question:
          type: object
          additionalProperties: false
          required: [id, text, type, order]
          properties:
            id:
              type: string
              pattern: "^qst_[A-Za-z0-9_-]+$"
              description: >-
                Opaque question identifier, stable across records that share the template. It is the
                field to key on, not the text, which the company can reword.
            text:
              type: string
              minLength: 1
              description: Question text as the template asks it, sanitized.
            type:
              type: string
              enum: [text, option, multi_select, boolean, number]
              description: >-
                What the template expects as an answer. The answer itself always arrives as a
                string, so this is what says how to parse it.
            order:
              type: integer
              minimum: 0
              description: Position of the question in the template, starting at zero.
          description: The question this answers.
        answer:
          type: string
          minLength: 1
          description: >-
            Generated answer. A question that produced none is left out of the list rather than
            returned with a null answer.
        updated_at:
          type: string
          format: date-time
          description: When the answer last changed.
    ProcessingStatusV1:
      type: string
      description: Public processing state.
      enum: [not_started, processing, completed, failed]
    MeetingTranscriptUtteranceV1:
      type: object
      additionalProperties: false
      required: [speaker, text, start_ms, end_ms]
      properties:
        speaker:
          type: [string, "null"]
          minLength: 1
          description: Sanitized speaker label when available.
        text:
          type: string
          minLength: 1
          description: Sanitized utterance text.
        start_ms:
          type: integer
          minimum: 0
          description: Utterance start offset in milliseconds.
        end_ms:
          type: integer
          minimum: 0
          description: Utterance end offset in milliseconds.
    MeetingTranscriptV1:
      oneOf:
        - type: object
          additionalProperties: false
          required:
            - object
            - meeting_id
            - available
            - status
            - variant
            - utterances
            - created_at
            - updated_at
          properties:
            object:
              const: transcript
              description: >-
                Resource type. Always `transcript`, including for a call.
            meeting_id:
              type: string
              pattern: "^(mtg|call)_[A-Za-z0-9_-]+$"
              description: >-
                The record this transcript belongs to. Carries the prefix of its collection: `mtg_`
                or `call_`.
            available:
              const: true
              description: >-
                Whether there is a transcript to read. `false` is a successful response carrying the
                reason in `status`, not an error.
            status:
              const: completed
              description: >-
                Processing state. Always `completed` while `available` is true; otherwise why the
                transcript is not there yet.
            variant:
              type: string
              enum: [enhanced, original]
              description: >-
                Which transcript was returned: `enhanced` when the improved version exists,
                `original` otherwise. Null while none is available.
            utterances:
              type: array
              items:
                $ref: "#/components/schemas/MeetingTranscriptUtteranceV1"
              description: >-
                The transcript itself, in chronological order. Always empty while the transcript is
                unavailable.
            created_at:
              type: string
              format: date-time
              description: >-
                When the transcript was first stored, or null when none exists yet.
            updated_at:
              type: string
              format: date-time
              description: >-
                When the transcript last changed, or null when none exists yet.
        - type: object
          additionalProperties: false
          required:
            - object
            - meeting_id
            - available
            - status
            - variant
            - utterances
            - created_at
            - updated_at
          properties:
            object:
              const: transcript
              description: >-
                Resource type. Always `transcript`, including for a call.
            meeting_id:
              type: string
              pattern: "^(mtg|call)_[A-Za-z0-9_-]+$"
              description: >-
                The record this transcript belongs to. Carries the prefix of its collection: `mtg_`
                or `call_`.
            available:
              const: false
              description: >-
                Whether there is a transcript to read. `false` is a successful response carrying the
                reason in `status`, not an error.
            status:
              type: string
              enum: [not_started, processing, failed]
              description: >-
                Processing state. Always `completed` while `available` is true; otherwise why the
                transcript is not there yet.
            variant:
              type: "null"
              description: >-
                Which transcript was returned: `enhanced` when the improved version exists,
                `original` otherwise. Null while none is available.
            utterances:
              type: array
              maxItems: 0
              items:
                $ref: "#/components/schemas/MeetingTranscriptUtteranceV1"
              description: >-
                The transcript itself, in chronological order. Always empty while the transcript is
                unavailable.
            created_at:
              type: [string, "null"]
              format: date-time
              description: >-
                When the transcript was first stored, or null when none exists yet.
            updated_at:
              type: [string, "null"]
              format: date-time
              description: >-
                When the transcript last changed, or null when none exists yet.
    RecordV1:
      type: object
      description: >-
        Fields both collections carry. Not returned on its own: MeetingV1 and CallV1 compose it and
        each seals itself with unevaluatedProperties, which allOf plus additionalProperties cannot
        do — the inner schema would reject the field the outer one adds.
      required:
        - id
        - object
        - title
        - status
        - type
        - audience
        - meeting_at
        - duration_seconds
        - no_show
        - participants
        - owner
        - template
        - tags
        - customer_questions
        - competitors
        - enablement
        - accounts
        - overall_evaluation
        - skill_scores
        - transcript
        - created_at
        - updated_at
      properties:
        id:
          type: string
          pattern: "^(mtg|call)_[A-Za-z0-9_-]+$"
          description: >-
            Opaque record identifier. Meetings are addressed as `mtg_...` under /v1/meetings and
            calls as `call_...` under /v1/calls.
          example: mtg_01JABC
        object:
          type: string
          enum: [meeting, call]
          description: >-
            Resource type. A record captured from a VoIP integration is a `call`; everything else is
            a `meeting`. Independent of `type`, which describes the media: a call can be video and a
            meeting can be audio.
        title:
          type: string
          minLength: 1
          description: Sanitized meeting title.
          example: Discovery - Acme
        status:
          type: string
          const: completed
          description: Only semantically completed meetings are public in V1.
        type:
          type: string
          enum: [video, audio]
          description: Meeting media type.
        audience:
          type: string
          enum: [internal, external]
          description: Meeting audience classification.
        meeting_at:
          type: string
          format: date-time
          description: Meeting instant in UTC.
        duration_seconds:
          type: integer
          minimum: 0
          description: Non-negative meeting duration.
        no_show:
          type: boolean
          description: Whether the completed meeting had no attendee.
        participants:
          type: array
          description: Normalized external participants.
          items:
            $ref: "#/components/schemas/MeetingParticipantV1"
        owner:
          $ref: "#/components/schemas/MeetingOwnerV1"
          description: >-
            The user the record belongs to. Company scoping follows this user's default company.
        template:
          oneOf:
            - $ref: "#/components/schemas/TemplateReferenceV1"
            - type: "null"
          description: Public meeting template reference when available.
        tags:
          type: array
          description: Public tag references.
          items:
            $ref: "#/components/schemas/TagReferenceV1"
        customer_questions:
          type: array
          description: Customer questions detected in the call.
          items:
            $ref: "#/components/schemas/MeetingCustomerQuestionV1"
        competitors:
          type: array
          description: Competitors mentioned in the call.
          items:
            $ref: "#/components/schemas/MeetingCompetitorV1"
        enablement:
          oneOf:
            - $ref: "#/components/schemas/MeetingEnablementV1"
            - type: "null"
          description: Enablement summary when available.
        accounts:
          type: array
          description: Company-scoped accounts linked to the meeting.
          items:
            $ref: "#/components/schemas/AccountReferenceV1"
        overall_evaluation:
          oneOf:
            - type: object
              additionalProperties: false
              required: [status, justification]
              properties:
                status:
                  const: completed
                score:
                  type: integer
                  minimum: 0
                  maximum: 10
                  description: Available when the completed evaluation has a score.
                  example: 8
                justification:
                  type: [string, "null"]
                  minLength: 1
            - type: object
              additionalProperties: false
              required: [status, justification]
              properties:
                status:
                  type: string
                  enum: [not_started, processing, failed]
                justification:
                  type: "null"
          description: Overall evaluation readiness and score summary.
        skill_scores:
          type: array
          description: Per-skill scores; empty when no skill evaluation exists.
          items:
            $ref: "#/components/schemas/MeetingSkillScoreV1"
        transcript:
          oneOf:
            - type: object
              additionalProperties: false
              required: [available, status]
              properties:
                available:
                  const: true
                status:
                  const: completed
            - type: object
              additionalProperties: false
              required: [available, status]
              properties:
                available:
                  const: false
                status:
                  type: string
                  enum: [not_started, processing, failed]
          description: Transcript availability and its consistent processing state.
        created_at:
          type: string
          format: date-time
          description: UTC instant when the meeting entered Salesbud.
        updated_at:
          type: string
          format: date-time
          description: >-
            UTC instant of the last change to the meeting record itself. Informational only: it does
            not advance when a dependent resource such as a transcript, tag or answer changes
            without touching the meeting row, so it is not a reliable incremental-sync marker.
    MeetingV1:
      unevaluatedProperties: false
      allOf:
        - $ref: "#/components/schemas/RecordV1"
        - type: object
          required: [object, bot_history]
          properties:
            object:
              const: meeting
            id:
              type: string
              pattern: "^mtg_[A-Za-z0-9_-]+$"
              description: >-
                Always prefixed `mtg_`. A `call_` id does not address a meeting: the API refuses it
                rather than resolving it in the other collection.
            bot_history:
              type: array
              description: >-
                Recording bot events. Present only on a meeting: a VoIP capture has no bot, so a
                call does not carry this field at all.
              items:
                $ref: "#/components/schemas/MeetingBotEventV1"
    CallV1:
      unevaluatedProperties: false
      allOf:
        - $ref: "#/components/schemas/RecordV1"
        - type: object
          required: [object]
          properties:
            object:
              const: call
            id:
              type: string
              pattern: "^call_[A-Za-z0-9_-]+$"
              description: >-
                Always prefixed `call_`. A `mtg_` id does not address a call: the API refuses it
                rather than resolving it in the other collection.
    EmailListResponse:
      type: object
      additionalProperties: false
      required: [data, pagination, request_id]
      properties:
        data:
          type: array
          items: {$ref: "#/components/schemas/EmailConversationV1"}
        pagination:
          $ref: "#/components/schemas/PaginationV1"
        request_id:
          type: string
    EmailResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          $ref: "#/components/schemas/EmailConversationV1"
        request_id:
          type: string
    EmailMessageListResponse:
      type: object
      additionalProperties: false
      required: [data, pagination, request_id]
      properties:
        data:
          type: array
          items: {$ref: "#/components/schemas/EmailMessageV1"}
        pagination:
          $ref: "#/components/schemas/PaginationV1"
        request_id:
          type: string
    EmailParticipantV1:
      type: object
      additionalProperties: false
      required: [address, name, internal]
      properties:
        address:
          type: string
          format: email
          description: Normalized lowercase address.
        name:
          type: [string, "null"]
          minLength: 1
          description: Display name as it appeared in the headers, or null.
        internal:
          type: boolean
          description: True when the address's domain matches the company's domain. Applied consistently to
            senders and recipients, independent of which mailboxes are connected.
    EmailMailboxV1:
      type: object
      description: A connected company mailbox that holds a copy of the conversation.
      additionalProperties: false
      required: [address, owner]
      properties:
        address:
          type: string
          format: email
        owner:
          oneOf:
            - $ref: "#/components/schemas/PublicUserReferenceV1"
            - type: "null"
          description: >-
            The company user who connected the mailbox, or null for a shared mailbox or a user no
            longer in the company.
    EmailAttachmentV1:
      type: object
      description: Attachment metadata. The API never serves attachment content or extracted text.
      additionalProperties: false
      required: [filename, mime_type, size_bytes]
      properties:
        filename:
          type: string
          minLength: 1
        mime_type:
          type: [string, "null"]
          minLength: 1
        size_bytes:
          type: [integer, "null"]
          minimum: 0
    EmailConversationV1:
      type: object
      description: >-
        A deduplicated email thread as seen by the company. Never carries bodies, snippets, unread
        counters, labels or provider identifiers.
      additionalProperties: false
      required:
        - id
        - object
        - subject
        - first_message_at
        - last_message_at
        - message_count
        - has_attachments
        - last_message_direction
        - participants
        - mailboxes
        - accounts
      properties:
        id:
          type: string
          pattern: "^eml_[A-Za-z0-9_-]+$"
          description: Opaque conversation identifier, stable across mailboxes.
        object:
          const: email_conversation
        subject:
          type: [string, "null"]
          minLength: 1
          description: Sanitized subject of the first message, or null when empty.
        first_message_at:
          type: string
          format: date-time
        last_message_at:
          type: string
          format: date-time
          description: Instant of the latest message; the list is ordered by it.
        message_count:
          type: integer
          minimum: 1
        has_attachments:
          type: boolean
        last_message_direction:
          type: [string, "null"]
          enum: [inbound, outbound, null]
        participants:
          type: array
          items: {$ref: "#/components/schemas/EmailParticipantV1"}
        mailboxes:
          type: array
          items: {$ref: "#/components/schemas/EmailMailboxV1"}
        accounts:
          type: array
          description: CRM accounts the conversation is linked to, when the company keeps that link.
          items: {$ref: "#/components/schemas/AccountReferenceV1"}
    EmailMessageV1:
      type: object
      description: >-
        One message of a conversation. Requires emails.content.read. `bcc`, HTML bodies, labels,
        read state and attachment content are never exposed.
      additionalProperties: false
      required:
        - id
        - object
        - conversation_id
        - sent_at
        - direction
        - subject
        - snippet
        - body_text
        - from
        - to
        - cc
        - attachments
      properties:
        id:
          type: string
          pattern: "^emsg_[A-Za-z0-9_-]+$"
          description: Opaque message identifier.
        object:
          const: email_message
        conversation_id:
          type: string
          pattern: "^eml_[A-Za-z0-9_-]+$"
        sent_at:
          type: string
          format: date-time
        direction:
          type: string
          enum: [inbound, outbound]
        subject:
          type: [string, "null"]
          minLength: 1
        snippet:
          type: [string, "null"]
          minLength: 1
          description: Provider-generated short preview, sanitized.
        body_text:
          type: string
          description: Plain-text body, sanitized; empty string when the message had no text part.
        from:
          $ref: "#/components/schemas/EmailParticipantV1"
        to:
          type: array
          items: {$ref: "#/components/schemas/EmailParticipantV1"}
        cc:
          type: array
          items: {$ref: "#/components/schemas/EmailParticipantV1"}
        attachments:
          type: array
          items: {$ref: "#/components/schemas/EmailAttachmentV1"}
    WhatsAppListResponse:
      type: object
      additionalProperties: false
      required: [data, pagination, request_id]
      properties:
        data:
          type: array
          items: {$ref: "#/components/schemas/WhatsAppConversationV1"}
        pagination:
          $ref: "#/components/schemas/PaginationV1"
        request_id:
          type: string
    WhatsAppResponse:
      type: object
      additionalProperties: false
      required: [data, request_id]
      properties:
        data:
          $ref: "#/components/schemas/WhatsAppConversationV1"
        request_id:
          type: string
    WhatsAppMessageListResponse:
      type: object
      additionalProperties: false
      required: [data, pagination, request_id]
      properties:
        data:
          type: array
          items: {$ref: "#/components/schemas/WhatsAppMessageV1"}
        pagination:
          $ref: "#/components/schemas/PaginationV1"
        request_id:
          type: string
    WhatsAppContactV1:
      type: object
      description: >-
        A person on WhatsApp as the API names them: an E.164 phone when the provider exposes one,
        and a display name. Provider identifiers (jid, lid) are never served.
      additionalProperties: false
      required: [phone, name]
      properties:
        phone:
          type: [string, "null"]
          pattern: "^\\+[1-9][0-9]{6,14}$"
          description: E.164 with the plus sign, or null when the provider hides the number (privacy lid) or
            for a group.
        name:
          type: [string, "null"]
          minLength: 1
          description: Sanitized display name (contact, group or participant), or null when unknown.
    WhatsAppSellerV1:
      type: object
      description: The company user whose WhatsApp holds the conversation.
      additionalProperties: false
      required: [user, phone]
      properties:
        user:
          oneOf:
            - $ref: "#/components/schemas/PublicUserReferenceV1"
            - type: "null"
          description: The seller as a company user, or null when the user is no longer in the company.
        phone:
          type: [string, "null"]
          pattern: "^\\+[1-9][0-9]{6,14}$"
          description: E.164 number of the seller's WhatsApp, or null when the session does not expose it.
    WhatsAppMediaV1:
      type: object
      description: Media metadata. The API never serves media content, thumbnails or download URLs.
      additionalProperties: false
      required: [file_name, mime_type, size_bytes]
      properties:
        file_name:
          type: [string, "null"]
          minLength: 1
        mime_type:
          type: [string, "null"]
          minLength: 1
        size_bytes:
          type: [integer, "null"]
          minimum: 0
    WhatsAppConversationV1:
      type: object
      description: >-
        One WhatsApp chat of a company seller as the company sees it. Never carries provider
        identifiers, session ids, unread counters, archive or pin state, reactions or media content.
      additionalProperties: false
      required:
        - id
        - object
        - source
        - is_group
        - contact
        - seller
        - accounts
        - first_message_at
        - last_message_at
        - last_message_direction
        - deleted_at
        - deleted_reason
      properties:
        id:
          type: string
          pattern: "^wa_[A-Za-z0-9_-]+$"
          description: Opaque conversation identifier.
        object:
          const: whatsapp_conversation
        source:
          type: string
          enum: [whatsapp_web, rd_conversas]
          description: Where the chat is captured — the seller's WhatsApp Web session or RD Conversas.
        is_group:
          type: boolean
        contact:
          allOf:
            - $ref: "#/components/schemas/WhatsAppContactV1"
          description: The other side of the chat. For a group, `phone` is null and `name` is the group name.
        seller:
          $ref: "#/components/schemas/WhatsAppSellerV1"
        accounts:
          type: array
          description: CRM accounts the conversation is linked to, when the company keeps that link.
          items: {$ref: "#/components/schemas/AccountReferenceV1"}
        first_message_at:
          type: [string, "null"]
          format: date-time
          description: Instant of the oldest visible message, or null when the chat has none.
        last_message_at:
          type: string
          format: date-time
          description: >-
            Instant of the latest message; the list is ordered by it. Only a new message moves it —
            an edit or a deletion does not.
        last_message_direction:
          type: [string, "null"]
          enum: [inbound, outbound, null]
        deleted_at:
          type: [string, "null"]
          format: date-time
          description: When the seller deleted the chat, or null.
        deleted_reason:
          type: [string, "null"]
          enum: [chat_deleted, null]
          description: >-
            The only member today is `chat_deleted` (the seller deleted the chat). Kept as a union
            of one on purpose, matching WhatsAppConversationDeletedReasonV1 in the code, so a future
            deletion reason can join it without a breaking change.
    WhatsAppMessageV1:
      type: object
      description: >-
        One message of a conversation. Requires whatsapp.content.read. Provider keys, raw protocol
        payloads, media content, reactions, edit history, delivery status and read state are never
        exposed. A deleted message stays as a tombstone: identity and instants kept, `text`, `media`
        and `transcription` null.
      additionalProperties: false
      required:
        - id
        - object
        - conversation_id
        - sent_at
        - direction
        - author
        - type
        - text
        - media
        - transcription
        - edited_at
        - deleted_at
        - deleted_reason
      properties:
        id:
          type: string
          pattern: "^wamsg_[A-Za-z0-9_-]+$"
          description: Opaque message identifier.
        object:
          const: whatsapp_message
        conversation_id:
          type: string
          pattern: "^wa_[A-Za-z0-9_-]+$"
        sent_at:
          type: string
          format: date-time
        direction:
          type: string
          enum: [inbound, outbound]
        author:
          allOf:
            - $ref: "#/components/schemas/WhatsAppContactV1"
          description: >-
            Who wrote it: the seller for outbound, the contact for inbound in an individual chat,
            the participant for inbound in a group.
        type:
          type: string
          enum:
            - text
            - image
            - video
            - audio
            - document
            - sticker
            - location
            - contact
            - poll
            - interactive
            - unknown
        text:
          type: [string, "null"]
          minLength: 1
          description: Sanitized message text or media caption; null when absent or on a tombstone.
        media:
          oneOf:
            - $ref: "#/components/schemas/WhatsAppMediaV1"
            - type: "null"
        transcription:
          type: [string, "null"]
          minLength: 1
          description: Sanitized transcription, only for `audio` and only when one exists.
        edited_at:
          type: [string, "null"]
          format: date-time
          description: When the author last edited the message; `text` is the current version.
        deleted_at:
          type: [string, "null"]
          format: date-time
        deleted_reason:
          type: [string, "null"]
          enum: [revoked_for_everyone, deleted_locally, chat_deleted, null]
