> ## Documentation Index
> Fetch the complete documentation index at: https://docs.flowyte.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Send an outbound text message from an SMS-enabled number

> Send ONE text message to a person from a number your workspace owns. This is the plain, programmatic send: no AI agent composes it, no teammate has to be signed in, and no external-agent handoff is opened. The text lands on that person's ordinary conversation for the sending number — if they reply, the reply is handled exactly like any inbound text to that number (by its assigned agent), with this message in the conversation history. Every send goes through the same compliance chokepoint as every other text the platform sends, so none of the rules below are optional or configurable per request. COMPLIANCE (all enforced server-side):
* `from` must be a number this workspace owns that is SMS-enabled (`smsStatus: active` on getSMSRegistration) and assigned to an agent. A number you do not own is 422 `from_not_owned`; one not yet enabled (or still pending) for SMS is 422 `from_not_sms_active`; one with no assigned agent is 422 `from_has_no_agent` — the assigned agent is what keeps the customer's reply on the same conversation.
* `to` must be a US or Canada (+1) number. The platform's numbers and its A2P 10DLC registration are North American, so any other destination is refused 422 `unsupported_country` — nothing is sent. `to` must also be a customer: one of this workspace's own numbers (active or not) is refused 422 `invalid_to`, so two of your agents can never text each other in a loop.
* The recipient must have a recorded consent basis: `conversational` (they texted one of your numbers first) or `express` (consent your business captured — record it with PUT /sms/contacts/{e164}/consent before sending). Otherwise 422 `consent_required`. There is no per-request override; recording the consent you hold is the sanctioned path.
* A recipient who replied STOP is refused 409 `sms_suppressed` until they text START (or you re-enable them with fresh consent via DELETE /sms/suppressions/{e164}).
* The message is business-initiated, so it is held to the recipient's legal texting window (8am–9pm in their local time). Outside it the answer is 409 `quiet_hours` with `details.nextOpen` — retry after that instant.
* Send the message ONLY. On the first text to a person (and again after 30 days without one) the platform prefixes your registered brand name and appends the required "Msg & data rates may apply. Reply HELP for help, STOP to opt out." block. Every body is converted to plain ASCII: smart punctuation is mapped to its plain equivalent; accented letters, emoji and other non-ASCII characters are removed. A message that is empty after that conversion is refused (400). The body is then capped at two segments, truncating at a sentence boundary. The receipt's `text` is the body exactly as sent, and `segments` is what was billed.
* Volume is capped per workspace: a rolling 24-hour ceiling on API sends (500 by default). Past it, 429 `daily_send_cap` carries `details.cap`, `details.windowHours`, `details.nextOpen` and `Retry-After`. An idempotent replay is answered from the original receipt and never counts against the cap.
* A send that did NOT happen is always an explicit error, never a 201 with `delivered: false`.
* A send refused by a send-time compliance check — an opt-out (or an opt-out status that could not be confirmed), quiet hours, an inactive registration or sending number — is also recorded as an `sms_blocked` event in the workspace's compliance trail, so the refusal is on record even though nothing was sent. IDEMPOTENCY. `client_message_id` is required and names ONE message. The key is (workspace, `client_message_id`), so a replay of the same request returns the original 201 receipt byte for byte and never sends a second text — including against a concurrent duplicate of itself. Reusing an id for a DIFFERENT message is 409 `client_message_id_reused`. Use a fresh UUID per message. WHEN THE OUTCOME IS UNKNOWN. If the carrier request fails in a way that does not prove the message stayed home (a timeout, a transport failure, a carrier 5xx), the answer is 409 `send_state_unknown` carrying `details.clientMessageId` and `details.conversationId`. It is the ONLY refusal here that does not promise the recipient's phone stayed silent, so do not retry with a new id: replay the SAME `client_message_id` — a replay resolves to the same answer, or to the receipt once the state settles, and never sends a second text. If the carrier ANSWERED and rejected the request, the answer is 502 `upstream_error` instead: nothing was sent and the id has been released, so a corrected retry is safe. TRANSIENT REFUSALS. 503 `opt_out_check_unavailable` means the recipient's opt-out status could not be confirmed, and 503 `try_again` means the platform is busy. Neither is an opt-out or a verdict on the message: nothing was sent and the `client_message_id` is not spent, so retry the same request shortly. OWNERSHIP. If someone else is already handling that person's conversation — an inbound message is being answered right now, a teammate has taken the thread over, or an external-agent handoff owns it — the send is refused 409 `conversation_busy` with `details.reason` (`inbound_turn_in_flight`, `seat_owns_thread` or `live_escalation`) rather than talking over them.




## OpenAPI

````yaml /openapi.yaml post /sms/messages
openapi: 3.1.0
info:
  title: Flowyte V2 Control-Plane REST API
  version: 1.0.0
  description: >-
    The single REST API for the Flowyte platform (base path `/api/v1`).
    Authenticate every request with a secret API key — `Authorization: Bearer
    flowyte_sk_…` — and each operation lists the scope the key must hold.
    Successful responses use the `ApiResponse<T>` envelope; list responses use
    cursor-based `PaginatedResponse<T>`. Errors are RFC 9457 problem+json.
    Streaming endpoints return Server-Sent Events
    (`event:<type>\ndata:<json>\n\n`, terminating with `event: done`).
  contact:
    name: Flowyte Platform
  license:
    name: Proprietary
servers:
  - url: /api/v1
    description: Flowyte control-plane (versioned URI; additive in v1).
security:
  - apiKey: []
tags:
  - name: Agents
    description: The single user-facing entity.
  - name: Support
    description: In-app "Get help" form → support inbox email.
  - name: Skills
    description: >
      Agent capabilities / tools. A skill is ONE atomic action — typically a
      single API call the agent makes in one step (look up an order, create a
      record, send a message, transfer the call). Use a skill when the task is a
      single step. When a task needs several details gathered across turns
      BEFORE acting, build a Playbook (which gathers the inputs and then calls
      skills) — see the Playbooks tag.
  - name: Integrations
    description: Native OAuth integrations.
  - name: Knowledge
    description: RAG knowledge sources & preview.
  - name: Playbooks
    description: >
      Multi-turn conversation scripts — a node graph (gather → confirm → branch)
      the agent follows to collect several inputs IN ORDER across turns before
      acting. Build a playbook when a single skill call isn't enough because the
      agent must gather MANY details first, or run a SEQUENCE of skills, before
      it can finish (e.g. take a full service request: gather the problem,
      address, and time, confirm, THEN file it; qualify a lead; a multi-step
      intake). A playbook does NOT call an integration itself — it owns the
      conversation and holds the state across turns; the actual action is
      performed by the SKILL(s) it gathers the inputs for. So a playbook
      ORCHESTRATES skills. Rule of thumb — one API call → a Skill; "gather N
      things in order, then submit" → a Playbook that drives the conversation
      and calls the skill(s) at the end.
  - name: Variables
    description: >
      The agent-wide interaction-variable registry — DERIVED at read time from
      the agent's playbooks and skills (collect slots, skill output bindings,
      {var} placeholders) and merged with a thin annotation overlay (notes,
      declared type hints, manual declarations). Read-only observation, NEVER a
      gate: it never validates a reference and never affects authoring, publish,
      or runtime behaviour.
  - name: Guardrails
    description: Deterministic guardrail policies & caller verification.
  - name: Numbers
    description: Phone numbers / DIDs.
  - name: SMS
    description: >
      SMS Hub — A2P 10DLC self-serve registration (brand + campaign), the free
      AI compliance review, per-number SMS enablement, the consent/opt-out
      surface (suppressions, contacts, recorded consent, TCPA proof export), and
      sending a text from the API. Sending numbers are US-only; a number must be
      SMS-enabled and the org's 10DLC registration active before it can send.
  - name: Test
    description: Test, simulate, talk-token, probe.
  - name: Observe
    description: Post-call analytics, conversations, receipts, transcripts.
  - name: Billing
    description: Plans, wallet, usage, fixed phrases.
  - name: Voices
    description: Voice catalog.
  - name: ApiKeys
    description: Developer / API keys.
  - name: Webhooks
    description: Webhook endpoints & deliveries.
  - name: Records
    description: >
      Caller Context Store — pre-synced external records the agent greets a
      caller from, plus the learned object-type field registry. Fed by the
      Zapier app's Create/Update Record action and directly by this REST API;
      read at call time by the context_lookup skill (a sub-100ms local lookup,
      no Zapier round-trip).
  - name: AuditLogs
    description: API/key activity logs.
  - name: Chat
    description: Chat channel — sessions, messages, OpenAI-compatible, widget.
  - name: PublishableKeys
    description: Browser-safe, one-agent publishable keys.
  - name: Widget
    description: Embed widget config + snippet.
  - name: Uploads
    description: Multipart file uploads backing file_id params
  - name: Meta
    description: Platform metadata (language/SKU/tier capabilities).
  - name: Outbound
    description: >
      Outbound voice — contact lists (import + scrub) and campaigns (create,
      launch, pause/resume/cancel). Launch schedules one call attempt per valid
      contact; a background worker dials them. This release dials informational
      campaigns only.
  - name: Team
    description: >
      Team Members — the general "invite a teammate" surface (roster, the
      platform invitations, role changes/removals, and the domain-discovery
      join-request inbox). Distinct from the Flowyte Phone SEAT surface: a team
      invite mints NO softphone seat. Reads → team:read; mutations → team:write
      (owner/admin; staff is role-gated out). Owner-only guards protect owner
      grants/edits and the org's last owner; no self-role-change or
      self-removal.
  - name: Onboarding
    description: >
      Pre-org domain discovery — the signup-flow seam a just-signed-in user hits
      BEFORE they have a workspace, to learn "your coworkers may already be
      here" and ask to join. Engine-root, authenticated by a the platform
      session JWT that may have no active org; tenancy is derived SERVER-SIDE
      from the caller's verified email (anti-enumeration), never from input.
  - name: OAuth2
    description: >
      Flowyte's minimal OAuth2 authorization server. The Flowyte Zapier app is
      the first registered client. The authorization-code grant (confidential
      client, optional PKCE) issues org-scoped SERVICE tokens (flowyte_oat_…)
      that ride the SAME scope matrix as an sk key. The public
      authorize/token/revoke endpoints are served at the ORIGIN ROOT (NOT under
      /api/v1 — see each path's `servers` override); consent + disconnect are
      /api/v1.
paths:
  /sms/messages:
    post:
      tags:
        - SMS
      summary: Send an outbound text message from an SMS-enabled number
      description: >
        Send ONE text message to a person from a number your workspace owns.
        This is the plain, programmatic send: no AI agent composes it, no
        teammate has to be signed in, and no external-agent handoff is opened.
        The text lands on that person's ordinary conversation for the sending
        number — if they reply, the reply is handled exactly like any inbound
        text to that number (by its assigned agent), with this message in the
        conversation history. Every send goes through the same compliance
        chokepoint as every other text the platform sends, so none of the rules
        below are optional or configurable per request. COMPLIANCE (all enforced
        server-side):

        * `from` must be a number this workspace owns that is SMS-enabled
        (`smsStatus: active` on getSMSRegistration) and assigned to an agent. A
        number you do not own is 422 `from_not_owned`; one not yet enabled (or
        still pending) for SMS is 422 `from_not_sms_active`; one with no
        assigned agent is 422 `from_has_no_agent` — the assigned agent is what
        keeps the customer's reply on the same conversation.

        * `to` must be a US or Canada (+1) number. The platform's numbers and
        its A2P 10DLC registration are North American, so any other destination
        is refused 422 `unsupported_country` — nothing is sent. `to` must also
        be a customer: one of this workspace's own numbers (active or not) is
        refused 422 `invalid_to`, so two of your agents can never text each
        other in a loop.

        * The recipient must have a recorded consent basis: `conversational`
        (they texted one of your numbers first) or `express` (consent your
        business captured — record it with PUT /sms/contacts/{e164}/consent
        before sending). Otherwise 422 `consent_required`. There is no
        per-request override; recording the consent you hold is the sanctioned
        path.

        * A recipient who replied STOP is refused 409 `sms_suppressed` until
        they text START (or you re-enable them with fresh consent via DELETE
        /sms/suppressions/{e164}).

        * The message is business-initiated, so it is held to the recipient's
        legal texting window (8am–9pm in their local time). Outside it the
        answer is 409 `quiet_hours` with `details.nextOpen` — retry after that
        instant.

        * Send the message ONLY. On the first text to a person (and again after
        30 days without one) the platform prefixes your registered brand name
        and appends the required "Msg & data rates may apply. Reply HELP for
        help, STOP to opt out." block. Every body is converted to plain ASCII:
        smart punctuation is mapped to its plain equivalent; accented letters,
        emoji and other non-ASCII characters are removed. A message that is
        empty after that conversion is refused (400). The body is then capped at
        two segments, truncating at a sentence boundary. The receipt's `text` is
        the body exactly as sent, and `segments` is what was billed.

        * Volume is capped per workspace: a rolling 24-hour ceiling on API sends
        (500 by default). Past it, 429 `daily_send_cap` carries `details.cap`,
        `details.windowHours`, `details.nextOpen` and `Retry-After`. An
        idempotent replay is answered from the original receipt and never counts
        against the cap.

        * A send that did NOT happen is always an explicit error, never a 201
        with `delivered: false`.

        * A send refused by a send-time compliance check — an opt-out (or an
        opt-out status that could not be confirmed), quiet hours, an inactive
        registration or sending number — is also recorded as an `sms_blocked`
        event in the workspace's compliance trail, so the refusal is on record
        even though nothing was sent. IDEMPOTENCY. `client_message_id` is
        required and names ONE message. The key is (workspace,
        `client_message_id`), so a replay of the same request returns the
        original 201 receipt byte for byte and never sends a second text —
        including against a concurrent duplicate of itself. Reusing an id for a
        DIFFERENT message is 409 `client_message_id_reused`. Use a fresh UUID
        per message. WHEN THE OUTCOME IS UNKNOWN. If the carrier request fails
        in a way that does not prove the message stayed home (a timeout, a
        transport failure, a carrier 5xx), the answer is 409
        `send_state_unknown` carrying `details.clientMessageId` and
        `details.conversationId`. It is the ONLY refusal here that does not
        promise the recipient's phone stayed silent, so do not retry with a new
        id: replay the SAME `client_message_id` — a replay resolves to the same
        answer, or to the receipt once the state settles, and never sends a
        second text. If the carrier ANSWERED and rejected the request, the
        answer is 502 `upstream_error` instead: nothing was sent and the id has
        been released, so a corrected retry is safe. TRANSIENT REFUSALS. 503
        `opt_out_check_unavailable` means the recipient's opt-out status could
        not be confirmed, and 503 `try_again` means the platform is busy.
        Neither is an opt-out or a verdict on the message: nothing was sent and
        the `client_message_id` is not spent, so retry the same request shortly.
        OWNERSHIP. If someone else is already handling that person's
        conversation — an inbound message is being answered right now, a
        teammate has taken the thread over, or an external-agent handoff owns it
        — the send is refused 409 `conversation_busy` with `details.reason`
        (`inbound_turn_in_flight`, `seat_owns_thread` or `live_escalation`)
        rather than talking over them.
      operationId: sendSMSMessage
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/SendSMSMessageRequest'
            example:
              from: '+14155550100'
              to: '+14155551234'
              text: Hi Dana — your cleaning is confirmed for Tue 9am.
              client_message_id: 6f1c2d3e-4a5b-6789-abcd-ef0123456789
      responses:
        '201':
          description: >-
            The text was sent (also returned, byte for byte, on an idempotent
            replay of the same `client_message_id`).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiResponse_SMSMessageReceipt'
              example:
                success: true
                data:
                  messageId: 8c1f4e2a-3b5d-4c6e-9f70-812a3b4c5d6e
                  conversationId: conv_9d2f01
                  seq: 4
                  from: '+14155550100'
                  to: '+14155551234'
                  text: >-
                    Kiwi Cleaning: Hi Dana - your cleaning is confirmed for Tue
                    9am. Msg & data rates may apply. Reply HELP for help, STOP
                    to opt out.
                  segments: 1
                  delivered: true
                  clientMessageId: 6f1c2d3e-4a5b-6789-abcd-ef0123456789
        '400':
          $ref: '#/components/responses/ValidationError'
          description: >-
            `validation_error` — the body is not valid JSON of this shape;
            `from`, `text` or `client_message_id` is missing; `from` is not in
            E.164 form; `text` is longer than 4,000 characters or has nothing
            left after conversion to plain ASCII (a message made only of emoji,
            for example); or `client_message_id` is longer than 200 characters.
            Nothing was sent.
        '401':
          $ref: '#/components/responses/Unauthorized'
        '403':
          $ref: '#/components/responses/Forbidden'
          description: >-
            Refused: the API key lacks the `sms:send` scope
            (`insufficient_scope`), or the workspace's A2P 10DLC campaign is not
            active (`sms_not_enabled`). Nothing was sent.
        '409':
          $ref: '#/components/responses/Conflict'
          description: >-
            Refused with an actionable state: `quiet_hours` (outside the
            recipient's legal texting window — `details.nextOpen` carries the
            retry instant), `sms_suppressed` (the recipient replied STOP),
            `conversation_busy` (the conversation is already being handled —
            `details.reason` is `inbound_turn_in_flight`, `seat_owns_thread` or
            `live_escalation`, with `details.conversationId` and, for a handoff,
            `details.escalationId` when known), `client_message_id_reused` (that
            id already named a different message), or `send_state_unknown` (the
            send outcome is indeterminate — the message MAY have been delivered;
            carries `details.clientMessageId` and `details.conversationId`; do
            not retry with a new id, replay the same `client_message_id`). Every
            code except `send_state_unknown` guarantees nothing was sent.
        '422':
          $ref: '#/components/responses/Unprocessable'
          description: >-
            Refused before sending: `invalid_to` (`to` is not E.164, equals
            `from`, or is one of this workspace's own numbers),
            `unsupported_country` (`to` is outside US/Canada), `from_not_owned`
            (`from` is not an active number of this workspace),
            `from_not_sms_active` (`from` is not enabled for SMS yet),
            `from_has_no_agent` (`from` has no assigned agent), or
            `consent_required` (no recorded consent for that recipient — record
            it with PUT /sms/contacts/{e164}/consent; a customer who texts you
            first grants conversational consent automatically).
        '429':
          $ref: '#/components/responses/RateLimited'
          description: >-
            `daily_send_cap` — the workspace has sent its maximum number of API
            texts in the last 24 hours. Nothing was sent. `details.cap`,
            `details.windowHours`, `details.nextOpen` (RFC 3339) and
            `Retry-After` give the instant the next slot frees.
        '502':
          description: >-
            `upstream_error` — the carrier ANSWERED and rejected the request (a
            4xx). Nothing was sent and the `client_message_id` has been
            released, so a corrected retry is safe. An unanswered or ambiguous
            carrier failure is 409 `send_state_unknown` instead, never this.
          content:
            application/problem+json:
              schema:
                $ref: '#/components/schemas/ProblemDetails'
              example:
                success: false
                code: upstream_error
                message: >-
                  the carrier rejected the message; nothing was sent — check the
                  number and try again
        '503':
          $ref: '#/components/responses/ServiceUnavailable'
          description: >-
            Nothing was sent and the `client_message_id` was not spent:
            `sms_not_configured` (SMS sending is not available on this
            deployment), `opt_out_check_unavailable` (the recipient's opt-out
            status could not be confirmed — never reported as an opt-out; retry
            shortly), or `try_again` (the platform is busy; retry shortly).
      security:
        - apiKey:
            - sms:send
components:
  schemas:
    SendSMSMessageRequest:
      type: object
      required:
        - from
        - to
        - text
        - client_message_id
      description: >
        One outbound text. Every field is required and whitespace-trimmed before
        validation. Unlike initiateEscalation there is no agent or destination
        to name: the conversation is keyed by the `from` number's assigned
        agent, so the recipient's reply lands on the same thread.
      properties:
        from:
          type: string
          description: >-
            The sending number in E.164. Must be a number this workspace owns
            that is SMS-enabled (`smsStatus: active`) and assigned to an agent.
          example: '+14155550100'
        to:
          type: string
          description: >-
            The recipient in E.164. Must be a US or Canada (+1) number,
            different from `from` and not one of this workspace's own numbers,
            with a recorded consent basis (conversational or express).
          example: '+14155551234'
        text:
          type: string
          maxLength: 4000
          description: >-
            The message. Send the message ONLY — on a first contact the platform
            prefixes your brand name and appends the "Msg & data rates may
            apply. Reply HELP for help, STOP to opt out." block. The text is
            converted to plain ASCII: smart punctuation is mapped to its plain
            equivalent; accented letters, emoji and other non-ASCII characters
            are removed. A message that is empty after that conversion is
            refused (400). The result is capped at two segments (truncating at a
            sentence boundary).
          example: Hi Dana — your cleaning is confirmed for Tue 9am.
        client_message_id:
          type: string
          maxLength: 200
          description: >-
            Idempotency key naming this one message (at most 200 characters; a
            UUID is ideal). A replay returns the original receipt and never
            sends a second text; reusing it for a different message is 409
            `client_message_id_reused`.
          example: 6f1c2d3e-4a5b-6789-abcd-ef0123456789
    ApiResponse_SMSMessageReceipt:
      allOf:
        - $ref: '#/components/schemas/ApiResponseBase'
        - type: object
          required:
            - data
          properties:
            data:
              $ref: '#/components/schemas/SMSMessageReceipt'
    ProblemDetails:
      description: RFC 9457 problem+json.
      type: object
      properties:
        type:
          type: string
          format: uri
          default: about:blank
        title:
          type: string
        status:
          type: integer
        detail:
          type: string
        instance:
          type: string
        code:
          type: string
        errors:
          type: array
          items:
            type: object
            properties:
              field:
                type: string
              message:
                type: string
    ApiResponseBase:
      type: object
      required:
        - success
      properties:
        success:
          type: boolean
        message:
          type: string
        errors:
          type: array
          items:
            type: object
            required:
              - field
              - message
            properties:
              field:
                type: string
              message:
                type: string
    SMSMessageReceipt:
      type: object
      required:
        - conversationId
        - from
        - to
        - text
        - segments
        - delivered
        - clientMessageId
      description: The receipt for a sent text.
      properties:
        messageId:
          type: string
          description: >-
            The conversation-history id of the sent message. Absent (together
            with `seq`) only in the rare case the message could not be recorded
            on the conversation after it was sent — the text still went out
            exactly once, and a replay never sends it again.
        conversationId:
          type: string
          description: >-
            The conversation the message was sent on — the same conversation the
            recipient's reply joins.
        seq:
          type: integer
          description: >-
            The message's sequence number within the conversation (present
            alongside `messageId`).
        from:
          type: string
          description: The sending number (E.164).
        to:
          type: string
          description: The recipient (E.164).
        text:
          type: string
          description: >-
            The body exactly as sent — after conversion to plain ASCII, the
            segment cap and any brand/disclosure block the platform added.
        segments:
          type: integer
          description: The number of billable message segments sent.
        delivered:
          type: boolean
          description: >-
            Always true on a 201 — the carrier accepted the message. A send that
            did not happen is an error, never a 201 with delivered false.
        clientMessageId:
          type: string
          description: The `client_message_id` this receipt answers.
    ApiResponse_Void:
      allOf:
        - $ref: '#/components/schemas/ApiResponseBase'
        - type: object
          properties:
            data:
              type:
                - object
                - 'null'
  responses:
    ValidationError:
      description: Validation failure (errors[] populated on the envelope).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
        application/json:
          schema:
            $ref: '#/components/schemas/ApiResponse_Void'
    Unauthorized:
      description: Missing or invalid API key.
      headers:
        WWW-Authenticate:
          schema:
            type: string
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
    Forbidden:
      description: Org mismatch / RLS / insufficient scope / origin not allowed.
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
    Conflict:
      description: Conflict (duplicate name, publish race, version mismatch, no diff).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
    Unprocessable:
      description: Semantically invalid (e.g. language not in SKU's STT set).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
    RateLimited:
      description: Rate-limited (Redis limiter).
      headers:
        RateLimit-Limit:
          schema:
            type: integer
        RateLimit-Remaining:
          schema:
            type: integer
        RateLimit-Reset:
          schema:
            type: integer
        Retry-After:
          schema:
            type: integer
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
    ServiceUnavailable:
      description: A required dependency is not configured/available (fails closed).
      content:
        application/problem+json:
          schema:
            $ref: '#/components/schemas/ProblemDetails'
  securitySchemes:
    apiKey:
      type: oauth2
      description: >
        Flowyte secret API key (`Authorization: Bearer flowyte_sk_live_…`).
        Scope-gated; is scoped to your organization — a key can never reach
        another tenant. The listed scopes in each operation's `apiKey`
        requirement are the scopes that key must hold. The `tokenUrl` is
        nominal: keys are minted in the dashboard.
      flows:
        clientCredentials:
          tokenUrl: /api/v1/api-keys
          scopes:
            agents:read: Read agents.
            agents:write: Create/update/delete agents, publish, rollback.
            knowledge:read: Read knowledge sources & preview.
            knowledge:write: Add/remove knowledge sources, uploads.
            skills:read: Read skills & skill-types.
            skills:write: Create/update/delete skills.
            playbooks:read: Read playbooks & graphs.
            playbooks:write: Create/update/delete playbooks & graphs.
            guardrails:read: Read guardrail policies & caller-verification.
            guardrails:write: Update guardrail policies & caller-verification.
            numbers:read: Read phone numbers / search availability.
            numbers:write: Purchase / assign / release numbers.
            sms:read: Read the org's SMS (10DLC) registration status and numbers.
            sms:write: >-
              Save/submit the 10DLC registration, toggle numbers for SMS, and
              record or revoke a contact's SMS consent.
            sms:send: >-
              Send outbound text messages from the org's SMS-enabled numbers
              (consent, opt-out and quiet hours are enforced server-side).
            outbound:read: Read outbound contact lists and campaigns.
            outbound:write: >-
              Create/import contact lists, create/launch/pause/resume/cancel
              outbound campaigns, and enqueue single outbound calls.
            integrations:read: Read connected native integrations (status only — never tokens).
            integrations:write: Discover schemas, set data scoping, and disconnect a connection.
            integrations:connect: >-
              Connect a data source (submit credentials / begin OAuth) — a
              SEPARATE, higher-privilege scope because connecting INGESTS
              credentials and opens a new egress path; a discover/scope/author
              key need not carry it.
            records:read: >-
              Read the pre-synced Caller Context Store records and object-type
              field registry.
            records:write: >-
              Upsert / delete / bulk-import / purge caller-context records and
              edit type metadata.
            calls:read: Read conversations, receipts, transcripts, analytics.
            analytics:read: >-
              Read the Observe history list, per-agent analytics (the
              answer-rate summary), the raw knowledge-gap list, and the metric
              catalog + per-metric queries + metric drill-downs.
            analytics:write: >-
              Curate knowledge gaps (dismiss / mark in-progress). [M4 —
              reserved]
            dashboards:read: List and read saved Observe reporting dashboards.
            dashboards:write: >-
              Create, update (full-document replace), and delete Observe
              reporting dashboards.
            reports:read: List and read Observe scheduled reports (report schedules).
            reports:write: >-
              Create, update, and delete Observe scheduled reports (a delete
              stops a recurring digest).
            billing:read: Read plans, wallet, usage.
            audit:read: Read API/key activity logs.
            webhooks:write: Manage webhook endpoints.
            keys:write: Manage secret API keys.
            chat:read: Read chat sessions & messages.
            chat:write: Create chat sessions & send messages (server-side).
            widgets:read: Read widget config & embed snippet.
            widgets:write: Update widget config.
            pubkeys:read: Read publishable keys.
            pubkeys:write: Manage publishable keys.
            phone:read: >-
              Read Flowyte Phone config — settings, ring targets/queues +
              rosters, contacts, block-list, own presence.
            phone:write: >-
              Update Flowyte Phone config — settings, ring targets/queues +
              members, contacts, block-list.
            presence:write: Set your own softphone presence (available/away/dnd/…).
            team:read: Read the team — softphone seats and the team presence roster.
            team:write: >-
              Manage softphone seats — invite, update, and deactivate team
              members.
            escalation_destinations:read: Read external-agent (AI Harness) escalation destinations.
            escalation_destinations:write: >-
              Create, update, delete, and rotate the signing secret of
              escalation destinations.
            escalation_policies:read: Read an agent's escalation routing policy. [ — reserved]
            escalation_policies:write: Update an agent's escalation routing policy. [ — reserved]
            escalations:read: >-
              Read escalation sessions and their message history (connector). [
              — reserved]
            escalations:claim: >-
              Claim an escalation session and extend its lease (connector). [ —
              reserved]
            escalations:respond: >-
              Post messages into a claimed escalation session (connector). [ —
              reserved]
            escalations:resolve: >-
              Resolve, return, or request human takeover of an escalation
              (connector). [ — reserved]
            escalations:initiate: >-
              Start a NEW outbound SMS conversation with a customer (connector).
              Deliberately separate from escalations:respond — the other scopes
              work a conversation the platform handed over; this one texts a
              person who never contacted you on that thread. Also requires the
              org to be enabled for harness-initiated conversations.

````