openapi: 3.1.0
info:
  title: Owari Robo Public API
  version: 0.1.0-draft
  summary: Public chat, widget, and OpenAI-compatible runtime API for Owari Robo.
  description: Public-facing Owari Robo API contract. Dashboard console and
    internal control-plane routes are intentionally omitted.
servers:
  - url: https://api.owarilabs.com/robo
paths:
  /health/live:
    get:
      responses:
        "200":
          description: Live.
  /health/ready:
    get:
      responses:
        "200":
          description: Ready.
  /v1/public/robots/{slug}/config:
    get:
      summary: Return public website chat configuration for a registry-resolved Robo
        slug.
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Public config.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicChatConfig"
        "429":
          description: Too many public requests from this client.
          headers:
            Retry-After:
              $ref: "#/components/headers/RetryAfter"
  /v1/public/robots/{slug}/sessions:
    post:
      summary: Create a public chat session.
      description: Terms-enabled public sessions are created only after the visitor
        accepts the localized Robo terms. Refusing terms is handled entirely in
        the browser and does not create a session, message, finish event, expand
        token, audit event, or Forms relay payload.
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        "201":
          description: Public session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicChatSession"
        "400":
          description: Terms consent is required before session creation when terms are
            enabled.
        "429":
          description: Too many public requests from this client.
  /v1/public/widgets/{slug}/config:
    get:
      summary: Return widget-safe public chat configuration for an allowed site origin.
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Widget public config.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicChatConfig"
        "403":
          description: Widget origin is not allowed or plan lacks widget access.
        "404":
          description: Widget is disabled.
        "429":
          description: Too many public requests from this client.
  /v1/public/widgets/{slug}/sessions:
    post:
      summary: Create a widget public chat session for an allowed site origin.
      description: Terms-enabled widget sessions are created only after the visitor
        accepts the localized Robo terms. Empty or declined chats close
        client-side without session, finish, expand-token, audit, or Forms relay
        side effects.
      parameters:
        - name: slug
          in: path
          required: true
          schema:
            type: string
      responses:
        "201":
          description: Widget public session.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicChatSession"
        "400":
          description: Terms consent is required before session creation when terms are
            enabled.
        "403":
          description: Widget origin is not allowed or a protocol-derived abuse block is
            active for this client.
        "429":
          description: Too many public requests from this client.
  /v1/public/sessions/{sessionId}/messages:
    post:
      summary: Send a public chat message and receive a Robo answer.
      description: Closed public sessions are readable for transcript and feedback
        rendering, but cannot receive new messages.
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - message
              properties:
                message:
                  type: string
                history:
                  type: array
                  maxItems: 20
                  description: Bounded active browser transcript used for current-session
                    continuity. Public responses omit citations even when
                    internal chat retains them.
                  items:
                    $ref: "#/components/schemas/PublicChatTranscriptMessage"
                attachments:
                  type: array
                  description: Paid public/widget attachment uploads. Robo converts files through
                    Owari Markdown, stores separate document context for the
                    session, returns attachment summaries, and relays consented
                    attachments to Forms when the session finishes.
                  maxItems: 5
                  items:
                    $ref: "#/components/schemas/PublicChatAttachmentUpload"
                consent_accepted:
                  type: boolean
                  description: Accepts the localized Robo terms for the session and allows the
                    first answer when terms are enabled.
      responses:
        "200":
          description: "Public answer without citations. Internal console chat keeps
            citations. Send `Accept: text/event-stream` to receive
            `robo.progress` events before the final `robo.answer` event."
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicChatAnswer"
            text/event-stream:
              schema:
                type: string
                description: "Server-sent events emitted only when the request includes `Accept:
                  text/event-stream`. Progress events use `event: robo.progress`
                  with `data` matching `RoboChatProgressEvent`. The final answer
                  uses `event: robo.answer` with `data` matching
                  `PublicChatAnswer`. If an error occurs after streaming starts,
                  Robo emits `event: robo.error` with `data` matching
                  `RoboChatErrorEvent` and closes the stream."
              examples:
                publicChatProgress:
                  summary: Public chat attachment, knowledge, and answer generation progress.
                  value: |-
                    event: robo.progress
                    data: {"stage":"reading_attachments"}

                    event: robo.progress
                    data: {"stage":"searching_knowledge"}

                    event: robo.progress
                    data: {"stage":"generating_answer"}

                    event: robo.answer
                    data: {"answer":"Here is the answer.","message_id":"msg_123","user_message_id":"msg_122","question_count":1,"remaining_questions":4}
        "403":
          description: Session is closed or public/widget runtime access is no longer
            allowed.
        "429":
          description: Too many public requests from this client.
  /v1/public/sessions/{sessionId}/expand-tokens:
    post:
      summary: Create a one-time token for opening a widget conversation in the full
        public chat.
      description: Expand tokens are available only for active widget/public sessions.
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                history:
                  type: array
                  maxItems: 40
                  description: Bounded widget transcript to hydrate into the full public chat
                    after one-time token redemption.
                  items:
                    $ref: "#/components/schemas/PublicChatTranscriptMessage"
      responses:
        "201":
          description: One-time expand token. Only a token hash is stored server-side and
            the token expires after five minutes.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicChatExpandToken"
        "403":
          description: Session is closed, or public/widget runtime access is not allowed.
        "429":
          description: Too many public requests from this client.
  /v1/public/sessions/{sessionId}/finish:
    post:
      summary: Finish a public or widget session and trigger summary/transcript
        delivery.
      description: Idempotently closes an active public or widget session with
        `user_closed`, `follow_up_requested`, or `question_limit`. When the UI
        reaches the question limit it can collect one final follow-up note and
        attachments, then finish the session so Forms receives the bounded
        transcript. Forms relay delivery is attempted once when notification
        emails are configured. Stored public answer feedback is matched by
        assistant `message_id` and may be included as localized lines in the
        existing Forms `full_transcript`; the finish request shape is unchanged.
        Idle cleanup still finishes abandoned sessions after the configured
        timeout.
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: false
        content:
          application/json:
            schema:
              type: object
              properties:
                reason:
                  type: string
                  enum:
                    - user_closed
                    - follow_up_requested
                    - question_limit
                  default: user_closed
                history:
                  type: array
                  maxItems: 100
                  description: Bounded browser transcript used when server retention is `none` or
                    incomplete.
                  items:
                    $ref: "#/components/schemas/PublicChatTranscriptMessage"
      responses:
        "200":
          description: Existing or newly finished public session metadata.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/FinishedPublicSession"
        "403":
          description: Public or widget runtime access is not allowed.
        "429":
          description: Too many public requests from this client.
  /v1/public/expand-tokens/{token}/redeem:
    post:
      summary: Redeem a one-time widget expand token and hydrate the full public chat
        transcript.
      parameters:
        - name: token
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Redeemed widget session and bounded transcript.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicExpandedSession"
        "410":
          description: Token expired or already redeemed.
        "429":
          description: Too many public requests from this client.
  /v1/public/sessions/{sessionId}/feedback:
    post:
      summary: Record public thumbs-up or thumbs-down feedback.
      description: Thumbs-up feedback is recorded immediately. Thumbs-down feedback
        with `feedback_reason` creates a pending internal training review;
        public details are context and require internal approval or correction
        before indexing. If the session is later relayed to notification emails,
        stored feedback may be appended to the matching assistant answer in the
        localized Forms `full_transcript`.
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - rating
              properties:
                rating:
                  type: string
                  enum:
                    - up
                    - down
                message_id:
                  type: string
                  description: Assistant answer id that the feedback applies to. Notification
                    relay uses this id to place feedback after the matching
                    answer.
                feedback_reason:
                  type: string
                  enum:
                    - Incorrect or incomplete
                    - Not what I asked
                    - Slow or buggy
                    - Tone or style
                    - Safety or legal
                    - Other
                feedback_details:
                  type: string
                expected_answer:
                  type: string
                  description: Internal/legacy correction text. Public thumbs-down UI sends
                    reason/details instead.
      responses:
        "201":
          description: Feedback.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/PublicFeedback"
        "429":
          description: Too many public requests from this client.
  /v1/public/sessions/{sessionId}/contact-handoffs:
    post:
      summary: Create a contact handoff from public chat and finish the session when
        consent is accepted.
      parameters:
        - name: sessionId
          in: path
          required: true
          schema:
            type: string
      responses:
        "201":
          description: Handoff.
          content:
            application/json:
              schema:
                type: object
                required:
                  - tenant_id
                  - session_id
                  - summary
                  - contact_id
                  - status
                  - created_at
                properties:
                  tenant_id:
                    type: string
                  session_id:
                    type: string
                  summary:
                    type:
                      - string
                      - "null"
                  contact_id:
                    type:
                      - string
                      - "null"
                  status:
                    type: string
                    enum:
                      - contact_created
                      - consent_declined
                  created_at:
                    type: string
        "429":
          description: Too many public requests from this client.
  /v1/responses:
    post:
      summary: OpenAI-compatible Responses surface for Robo API keys.
      security:
        - roboApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - conversation_channel
                - input
              properties:
                conversation_channel:
                  type: string
                  description: Required Robo conversation prompt channel id. Public visibility
                    uses public-facing instructions; private visibility uses
                    internal/team behavior.
                model:
                  type: string
                  default: owari-default
                input:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: object
                attachments:
                  type: array
                  description: Request-local files converted through Owari Markdown and passed as
                    document context for this answer only. Original bytes are
                    not persisted by Robo.
                  items:
                    $ref: "#/components/schemas/PublicChatAttachmentUpload"
                stream:
                  type: boolean
                  default: false
                tools:
                  type: array
                  description: Not implemented; requests that include tools are rejected.
                tool_choice:
                  oneOf:
                    - type: string
                    - type: object
                  description: Not implemented; requests that include tool_choice are rejected.
                max_output_tokens:
                  type: integer
                  minimum: 1
                response_format:
                  type: object
                  description: Not implemented; requests that include response_format are
                    rejected.
                temperature:
                  type: number
                  minimum: 0
                  maximum: 2
                top_p:
                  type: number
                  minimum: 0
                  maximum: 1
                presence_penalty:
                  type: number
                  minimum: -2
                  maximum: 2
                frequency_penalty:
                  type: number
                  minimum: -2
                  maximum: 2
                stop:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
                  description: Not implemented; requests that include stop are rejected.
                logit_bias:
                  type: object
                  description: Not implemented; requests that include logit_bias are rejected.
                seed:
                  type: integer
                  description: Not implemented; requests that include seed are rejected.
                metadata:
                  type: object
                  description: Not implemented; requests that include metadata are rejected.
                store:
                  type: boolean
                  description: Not implemented; requests that include store are rejected.
      responses:
        "200":
          description: OpenAI-compatible response.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAiResponse"
        "400":
          description: Invalid OpenAI-compatible request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAiCompatibleError"
        "401":
          description: Missing, invalid, or revoked Robo API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAiCompatibleError"
        "403":
          description: Robo API key does not have the required scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAiCompatibleError"
        "429":
          description: Too many OpenAI-compatible requests from this client or API key.
          headers:
            Retry-After:
              $ref: "#/components/headers/RetryAfter"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAiCompatibleError"
  /v1/chat/completions:
    post:
      summary: OpenAI-compatible Chat Completions surface for Robo API keys.
      security:
        - roboApiKey: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - conversation_channel
                - messages
              properties:
                conversation_channel:
                  type: string
                  description: Required Robo conversation prompt channel id. Public visibility
                    uses public-facing instructions; private visibility uses
                    internal/team behavior.
                model:
                  type: string
                  default: owari-default
                messages:
                  type: array
                  items:
                    type: object
                attachments:
                  type: array
                  description: Request-local files converted through Owari Markdown and passed as
                    document context for this answer only. Original bytes are
                    not persisted by Robo.
                  items:
                    $ref: "#/components/schemas/PublicChatAttachmentUpload"
                stream:
                  type: boolean
                  default: false
                tools:
                  type: array
                  description: Not implemented; requests that include tools are rejected.
                tool_choice:
                  oneOf:
                    - type: string
                    - type: object
                  description: Not implemented; requests that include tool_choice are rejected.
                max_tokens:
                  type: integer
                  minimum: 1
                response_format:
                  type: object
                  description: Not implemented; requests that include response_format are
                    rejected.
                temperature:
                  type: number
                  minimum: 0
                  maximum: 2
                top_p:
                  type: number
                  minimum: 0
                  maximum: 1
                presence_penalty:
                  type: number
                  minimum: -2
                  maximum: 2
                frequency_penalty:
                  type: number
                  minimum: -2
                  maximum: 2
                stop:
                  oneOf:
                    - type: string
                    - type: array
                      items:
                        type: string
                  description: Not implemented; requests that include stop are rejected.
                logit_bias:
                  type: object
                  description: Not implemented; requests that include logit_bias are rejected.
                seed:
                  type: integer
                  description: Not implemented; requests that include seed are rejected.
                metadata:
                  type: object
                  description: Not implemented; requests that include metadata are rejected.
                store:
                  type: boolean
                  description: Not implemented; requests that include store are rejected.
      responses:
        "200":
          description: OpenAI-compatible chat completion.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAiChatCompletion"
        "400":
          description: Invalid OpenAI-compatible request.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAiCompatibleError"
        "401":
          description: Missing, invalid, or revoked Robo API key.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAiCompatibleError"
        "403":
          description: Robo API key does not have the required scope.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAiCompatibleError"
        "404":
          description: Robo tenant was not found.
        "429":
          description: Too many OpenAI-compatible requests from this client or API key.
          headers:
            Retry-After:
              $ref: "#/components/headers/RetryAfter"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/OpenAiCompatibleError"
components:
  schemas:
    PublicChatConfig:
      type: object
      description: Runtime public/widget chat configuration, including localized
        terms, no-avatar layout, and optional HTTPS image URLs configured in the
        console.
      properties:
        slug:
          type: string
        assistant_name:
          type: string
        company_name:
          type: string
        website_enabled:
          type: boolean
        locale:
          $ref: "#/components/schemas/RoboLocale"
        terms_url:
          type: string
        terms_enabled:
          type: boolean
          description: Always true. Owari Robo terms cannot be disabled.
        retention_mode:
          type: string
          enum:
            - none
            - summary
            - full
        max_questions_per_session:
          type:
            - integer
            - "null"
        session_timeout_minutes:
          type: integer
        welcome_message:
          type:
            - string
            - "null"
        custom_terms:
          type:
            - string
            - "null"
          description: Optional tenant-defined Markdown terms shown through a custom terms
            link and modal.
        widget_image_url:
          type:
            - string
            - "null"
          format: uri
          description: HTTPS image URL used by widget.js as the floating launcher image.
        public_header_image_url:
          type:
            - string
            - "null"
          format: uri
          description: HTTPS image URL rendered beside the full public chat title; ignored
            in widget mode.
    PublicChatSession:
      type: object
      required:
        - session_id
        - tenant_id
        - source
        - question_count
        - remaining_questions
        - status
        - protocol_number
      properties:
        session_id:
          type: string
        tenant_id:
          type: string
        source:
          type: string
          enum:
            - public_chat
            - widget
        question_count:
          type: integer
        remaining_questions:
          type:
            - integer
            - "null"
        status:
          type: string
        protocol_number:
          type: string
          pattern: ^ROB-[0-9]{8}-[A-Z0-9]{6}$
    PublicChatTranscriptMessage:
      type: object
      required:
        - message_id
        - role
        - text
        - created_at
      properties:
        message_id:
          type: string
        role:
          type: string
          enum:
            - user
            - assistant
        text:
          type: string
        created_at:
          type: string
          format: date-time
        attachments:
          type: array
          items:
            $ref: "#/components/schemas/PublicChatAttachmentContext"
    PublicChatAttachmentUpload:
      type: object
      required:
        - file_name
        - content_type
        - size_bytes
        - content_base64
      properties:
        file_name:
          type: string
        content_type:
          type: string
        size_bytes:
          type: integer
        content_base64:
          type: string
          format: byte
    PublicChatAnswer:
      type: object
      description: Public chat answer payload. Citations are intentionally omitted
        from public and widget responses; internal console chat keeps citation
        payloads.
      required:
        - answer
        - message_id
        - user_message_id
        - question_count
        - remaining_questions
      properties:
        answer:
          type: string
        message_id:
          type: string
        user_message_id:
          type: string
        question_count:
          type: integer
        remaining_questions:
          type:
            - integer
            - "null"
        attachments:
          type: array
          items:
            $ref: "#/components/schemas/PublicChatAttachmentContext"
    PublicChatExpandToken:
      type: object
      required:
        - token
        - expires_at
      properties:
        token:
          type: string
        expires_at:
          type: string
          format: date-time
    FinishedPublicSession:
      type: object
      required:
        - session_id
        - tenant_id
        - source
        - status
        - close_reason
        - closed_at
        - summary
        - forms_relay_status
        - forms_relay_submission_id
        - forms_relay_error
        - forms_relay_attempted_at
      properties:
        session_id:
          type: string
        tenant_id:
          type: string
        source:
          type: string
          enum:
            - public_chat
            - widget
        protocol_number:
          type:
            - string
            - "null"
          pattern: ^ROB-[0-9]{8}-[A-Z0-9]{6}$
        status:
          type: string
          enum:
            - closed
        close_reason:
          type: string
          enum:
            - user_closed
            - follow_up_requested
            - question_limit
            - idle_timeout
            - retention_cleanup
        closed_at:
          type: string
          format: date-time
        summary:
          type:
            - string
            - "null"
        forms_relay_status:
          type: string
          enum:
            - sent
            - failed
            - not_configured
            - skipped
        forms_relay_submission_id:
          type:
            - string
            - "null"
        forms_relay_error:
          type:
            - string
            - "null"
        forms_relay_attempted_at:
          type:
            - string
            - "null"
          format: date-time
    PublicExpandedSession:
      type: object
      required:
        - session_id
        - tenant_id
        - slug
        - source
        - widget_origin
        - question_count
        - transcript
      properties:
        session_id:
          type: string
        tenant_id:
          type: string
        slug:
          type: string
        source:
          type: string
          enum:
            - public_chat
            - widget
        widget_origin:
          type:
            - string
            - "null"
        question_count:
          type: integer
        protocol_number:
          type:
            - string
            - "null"
          pattern: ^ROB-[0-9]{8}-[A-Z0-9]{6}$
        transcript:
          type: array
          items:
            $ref: "#/components/schemas/PublicChatTranscriptMessage"
    PublicFeedback:
      type: object
      required:
        - feedback_id
        - status
      properties:
        feedback_id:
          type: string
        status:
          type: string
          enum:
            - recorded
            - pending_training_review
    OpenAiResponse:
      type: object
      required:
        - id
        - object
        - status
        - created_at
        - model
        - incomplete_details
        - output_text
        - output
        - usage
        - parallel_tool_calls
      properties:
        id:
          type: string
        object:
          type: string
          const: response
        status:
          type: string
          const: completed
        created_at:
          type: integer
        model:
          type: string
        incomplete_details:
          type:
            - object
            - "null"
        output_text:
          type: string
        output:
          type: array
          items:
            type: object
            required:
              - type
              - role
              - content
            properties:
              type:
                type: string
                const: message
              role:
                type: string
                const: assistant
              content:
                type: array
                items:
                  type: object
        usage:
          type: object
          required:
            - input_tokens
            - output_tokens
            - total_tokens
          properties:
            input_tokens:
              type: integer
            output_tokens:
              type: integer
            total_tokens:
              type: integer
        parallel_tool_calls:
          type: boolean
    OpenAiCompatibleError:
      type: object
      required:
        - error
      properties:
        error:
          type: object
          required:
            - code
            - message
            - param
            - type
          properties:
            code:
              type: string
            message:
              type: string
            param:
              type:
                - string
                - "null"
            type:
              type: string
              enum:
                - api_error
                - authentication_error
                - invalid_request_error
                - permission_error
                - rate_limit_error
    OpenAiChatCompletion:
      type: object
      required:
        - id
        - object
        - created
        - model
        - service_tier
        - system_fingerprint
        - choices
        - usage
      properties:
        id:
          type: string
        object:
          type: string
          const: chat.completion
        created:
          type: integer
        model:
          type: string
        service_tier:
          type: string
        system_fingerprint:
          type:
            - string
            - "null"
        choices:
          type: array
          items:
            type: object
            required:
              - index
              - message
              - finish_reason
              - logprobs
            properties:
              index:
                type: integer
              message:
                type: object
              finish_reason:
                type: string
              logprobs:
                type:
                  - object
                  - "null"
        usage:
          type: object
          required:
            - prompt_tokens
            - completion_tokens
            - total_tokens
          properties:
            prompt_tokens:
              type: integer
            completion_tokens:
              type: integer
            total_tokens:
              type: integer
    RoboLocale:
      type: string
      enum:
        - en
        - pt-BR
        - ja
        - it
    PublicChatAttachmentContext:
      type: object
      required:
        - file_name
        - content_type
        - size_bytes
      properties:
        file_name:
          type: string
        content_type:
          type: string
        size_bytes:
          type: integer
        extracted_text:
          type: string
          description: Bounded extracted readable text or summary used as model context.
        content_base64:
          type: string
          format: byte
          description: Included by the browser only for consented Forms relay at session
            finish.
  headers:
    RetryAfter:
      description: Seconds the client should wait before retrying a rate-limited request.
      schema:
        type: integer
        minimum: 1
  securitySchemes:
    roboApiKey:
      type: http
      scheme: bearer
      bearerFormat: Robo API key
