# Texto SMS API — OpenAPI 3.1 specification
openapi: 3.1.0
info:
  title: Texto SMS API
  version: "1.0"
  summary: Australia's simplest SMS API — send, receive, and track SMS for 3¢ per message.
  description: |
    REST API for the Texto SMS platform. Send single or bulk SMS to Australian
    mobiles, receive replies, manage opt-outs, and get real-time delivery
    receipts via signed webhooks. Data is hosted in Australia.

    Full human-readable docs: https://texto.com.au/api
    Webhook reference: https://texto.com.au/webhooks
  contact:
    name: Texto Support
    url: https://texto.com.au
    email: support@texto.com.au
  license:
    name: Proprietary
    url: https://texto.com.au/terms
externalDocs:
  description: API documentation
  url: https://texto.com.au/api
servers:
  - url: https://api.texto.com.au
    description: Production
security:
  - bearerAuth: []
tags:
  - name: Sending
  - name: Tracking
  - name: Inbound
  - name: Opt-outs
  - name: Account
  - name: Account Management
    description: |
      Multi-account endpoints for SaaS platforms, agencies and resellers: create and
      manage sub-accounts for your customers, allocate credits, provision API keys,
      invite users, assign dedicated numbers and configure webhooks. Requires account
      hierarchy to be enabled on the parent account.
  - name: Reporting


paths:
  /send:
    post:
      tags: [Sending]
      summary: Send a single SMS
      operationId: sendSms
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/SendRequest" }
            example:
              to: "+61412345678"
              message: "Hello from Texto!"
              sender: "+61400000000"
      responses:
        "200":
          description: Message accepted for delivery
          content:
            application/json:
              schema: { $ref: "#/components/schemas/SendResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PaymentRequired" }
        "500": { $ref: "#/components/responses/ServerError" }
      callbacks:
        deliveryReceipt:
          "{$request.body#/webhook_url}":
            post:
              summary: Delivery receipt webhook
              description: |
                Fired once per delivery receipt. Signed with HMAC-SHA256 using your
                signing secret. Retried up to 3 times. Reply with any 2xx within 15s.
              requestBody:
                required: true
                content:
                  application/json:
                    schema: { $ref: "#/components/schemas/WebhookEvent" }
              responses:
                "200": { description: Acknowledged }

  /send-batch:
    post:
      tags: [Sending]
      summary: Send SMS to multiple recipients (max 1,000 per call)
      operationId: sendBatch
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/BatchSendRequest" }
      responses:
        "200":
          description: Batch accepted
          content:
            application/json:
              schema: { $ref: "#/components/schemas/BatchSendResponse" }
        "400": { $ref: "#/components/responses/BadRequest" }
        "401": { $ref: "#/components/responses/Unauthorized" }
        "402": { $ref: "#/components/responses/PaymentRequired" }

  /message/{id}:
    get:
      tags: [Tracking]
      summary: Get a message by ID with delivery receipt
      operationId: getMessage
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Message detail
          content:
            application/json:
              schema: { $ref: "#/components/schemas/MessageDetail" }
        "404": { $ref: "#/components/responses/NotFound" }

  /campaign/{id}:
    get:
      tags: [Tracking]
      summary: Get campaign stats and messages
      operationId: getCampaign
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Campaign detail
          content:
            application/json:
              schema: { $ref: "#/components/schemas/CampaignDetail" }
        "404": { $ref: "#/components/responses/NotFound" }

  /inbox:
    get:
      tags: [Inbound]
      summary: List inbound messages
      operationId: listInbox
      parameters:
        - in: query
          name: limit
          schema: { type: integer, minimum: 1, maximum: 100, default: 25 }
        - in: query
          name: offset
          schema: { type: integer, minimum: 0, default: 0 }
        - in: query
          name: from
          description: Filter by sender (partial match)
          schema: { type: string }
        - in: query
          name: date_from
          schema: { type: string, format: date-time }
        - in: query
          name: date_to
          schema: { type: string, format: date-time }
      responses:
        "200":
          description: Inbox page
          content:
            application/json:
              schema: { $ref: "#/components/schemas/InboxPage" }

  /optouts:
    get:
      tags: [Opt-outs]
      summary: List opted-out numbers
      operationId: listOptouts
      responses:
        "200":
          description: Opt-out list
          content:
            application/json:
              schema: { $ref: "#/components/schemas/OptoutList" }

  /balance:
    get:
      tags: [Account]
      summary: Check your credit balance
      operationId: getBalance
      responses:
        "200":
          description: Current balance
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Balance" }

  /accounts:
    get:
      tags: [Account Management]
      summary: List your account and its sub-accounts
      operationId: listAccounts
      responses:
        "200":
          description: Account list
          content:
            application/json:
              schema:
                type: object
                properties:
                  accounts:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string, format: uuid }
                        business_name: { type: string }
                        parent_account_id: { type: [string, "null"], format: uuid }
                        created_at: { type: string, format: date-time }
    post:
      tags: [Account Management]
      summary: Create a sub-account for one of your customers
      operationId: createAccount
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [business_name]
              properties:
                business_name: { type: string }
                email: { type: string, description: Required unless managed_by_parent is true }
                daily_limit: { type: integer, default: 1000 }
                managed_by_parent: { type: boolean, default: false }
                team_access_from_parent: { type: boolean, default: false }
                optout_exempt: { type: boolean, default: false }
                seed_credits: { type: integer }
      responses:
        "201":
          description: Account created
          content:
            application/json:
              schema:
                type: object
                properties:
                  account_id: { type: string, format: uuid }
                  id: { type: string, format: uuid }
                  email: { type: string }
        "400": { $ref: "#/components/responses/BadRequest" }
        "403": { description: Account hierarchy is not enabled for this account }
        "409": { description: An account with that email already exists }

  /account/{id}:
    parameters:
      - in: path
        name: id
        required: true
        schema: { type: string, format: uuid }
    get:
      tags: [Account Management]
      summary: Get account detail
      operationId: getAccount
      responses:
        "200":
          description: Account detail
          content:
            application/json:
              schema:
                type: object
                properties:
                  account: { $ref: "#/components/schemas/AccountDetail" }
        "404": { $ref: "#/components/responses/NotFound" }
    patch:
      tags: [Account Management]
      summary: Update a sub-account
      operationId: updateAccount
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                business_name: { type: string }
                daily_limit: { type: integer }
                optout_exempt: { type: boolean }
                team_access_from_parent: { type: boolean }
      responses:
        "200": { description: Updated }
    delete:
      tags: [Account Management]
      summary: Schedule a sub-account for deletion
      operationId: deleteAccount
      responses:
        "200": { description: Scheduled for deletion }

  /account/{id}/balance:
    get:
      tags: [Account Management]
      summary: Get a sub-account credit balance
      operationId: getAccountBalance
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Balance
          content:
            application/json:
              schema: { $ref: "#/components/schemas/Balance" }

  /account/{id}/credits/allocate:
    post:
      tags: [Account Management]
      summary: Allocate credits from the parent balance to a sub-account
      operationId: allocateCredits
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreditTransfer" }
      responses:
        "200": { description: Credits allocated }

  /account/{id}/credits/recall:
    post:
      tags: [Account Management]
      summary: Recall credits from a sub-account
      operationId: recallCredits
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/CreditTransfer" }
      responses:
        "200": { description: Credits recalled }

  /account/{id}/keys:
    get:
      tags: [Account Management]
      summary: List API keys issued to an account
      operationId: listAccountKeys
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Key list
          content:
            application/json:
              schema:
                type: object
                properties:
                  keys:
                    type: array
                    items:
                      type: object
                      properties:
                        id: { type: string, format: uuid }
                        name: { type: string }
                        prefix: { type: string }
                        created_at: { type: string, format: date-time }
                        last_used_at: { type: [string, "null"], format: date-time }
                        revoked_at: { type: [string, "null"], format: date-time }

  /account/{id}/key:
    post:
      tags: [Account Management]
      summary: Provision an API key for an account (returned once)
      operationId: createAccountKey
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [name]
              properties:
                name: { type: string, maxLength: 100 }
      responses:
        "201":
          description: Key created
          content:
            application/json:
              schema:
                type: object
                properties:
                  id: { type: string, format: uuid }
                  key: { type: string }
                  prefix: { type: string }

  /account/{id}/key/{keyId}:
    delete:
      tags: [Account Management]
      summary: Revoke an API key
      operationId: revokeAccountKey
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
        - in: path
          name: keyId
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200": { description: Revoked }

  /account/{id}/users:
    parameters:
      - in: path
        name: id
        required: true
        schema: { type: string, format: uuid }
    get:
      tags: [Account Management]
      summary: List users on an account
      operationId: listAccountUsers
      responses:
        "200":
          description: User list
          content:
            application/json:
              schema:
                type: object
                properties:
                  users:
                    type: array
                    items: { $ref: "#/components/schemas/AccountUser" }
    post:
      tags: [Account Management]
      summary: Invite a user to an account
      operationId: inviteAccountUser
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string }
                name: { type: string }
                can_send: { type: boolean, default: true }
                can_billing: { type: boolean, default: false }
      responses:
        "201": { description: Invited }
        "409": { description: This person has already been invited }

  /account/{id}/users/{memberId}:
    delete:
      tags: [Account Management]
      summary: Remove a user from an account
      operationId: removeAccountUser
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
        - in: path
          name: memberId
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200": { description: Removed }

  /account/{id}/access:
    parameters:
      - in: path
        name: id
        required: true
        schema: { type: string, format: uuid }
    get:
      tags: [Account Management]
      summary: Read administration and team access settings
      operationId: getAccountAccess
      responses:
        "200":
          description: Access settings
          content:
            application/json:
              schema:
                type: object
                properties:
                  managed_by_parent: { type: boolean }
                  team_access_from_parent: { type: boolean }
                  direct_team: { type: array, items: { $ref: "#/components/schemas/AccountUser" } }
                  eligible_parent_team: { type: array, items: { $ref: "#/components/schemas/AccountUser" } }
    put:
      tags: [Account Management]
      summary: Update administration and team access settings
      operationId: setAccountAccess
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                managed_by_parent: { type: boolean }
                team_access_from_parent: { type: boolean }
      responses:
        "200": { description: Updated }

  /team:
    get:
      tags: [Account Management]
      summary: List users on your own parent account
      operationId: listParentTeam
      responses:
        "200":
          description: User list
          content:
            application/json:
              schema:
                type: object
                properties:
                  users: { type: array, items: { $ref: "#/components/schemas/AccountUser" } }

  /numbers:
    get:
      tags: [Account Management]
      summary: List dedicated numbers held by your account
      operationId: listNumbers
      responses:
        "200":
          description: Number list
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NumberList" }

  /numbers/group:
    get:
      tags: [Account Management]
      summary: List numbers across your account and all sub-accounts
      operationId: listGroupNumbers
      responses:
        "200":
          description: Number list
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NumberList" }

  /numbers/available:
    get:
      tags: [Account Management]
      summary: Browse numbers available to be taken up
      operationId: listAvailableNumbers
      parameters:
        - in: query
          name: country
          schema: { type: string, enum: [AU, NZ], default: AU }
        - in: query
          name: limit
          schema: { type: integer, maximum: 100, default: 20 }
        - in: query
          name: offset
          schema: { type: integer, default: 0 }
      responses:
        "200":
          description: Available numbers
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NumberList" }

  /account/{id}/numbers:
    get:
      tags: [Account Management]
      summary: List numbers assigned to a sub-account
      operationId: listAccountNumbers
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      responses:
        "200":
          description: Number list
          content:
            application/json:
              schema: { $ref: "#/components/schemas/NumberList" }

  /account/{id}/numbers/assign:
    post:
      tags: [Account Management]
      summary: Assign numbers to a sub-account
      operationId: assignNumbers
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/NumberTransfer" }
      responses:
        "200": { description: Assigned }

  /account/{id}/numbers/recall:
    post:
      tags: [Account Management]
      summary: Recall numbers from a sub-account
      operationId: recallNumbers
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/NumberTransfer" }
      responses:
        "200": { description: Recalled }

  /webhooks:
    get:
      tags: [Account Management]
      summary: Read configured webhook endpoints
      operationId: listWebhooks
      responses:
        "200":
          description: Webhook configuration
          content:
            application/json:
              schema:
                type: object
                properties:
                  delivery_receipt: { $ref: "#/components/schemas/WebhookEndpoint" }
                  inbound: { $ref: "#/components/schemas/WebhookEndpoint" }

  /webhooks/delivery:
    put:
      tags: [Account Management]
      summary: Create or update the delivery receipt webhook endpoint
      operationId: setDeliveryWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookUpsert" }
      responses:
        "200": { description: Saved }
    post:
      tags: [Account Management]
      summary: Rotate the delivery receipt webhook signing secret
      operationId: rotateDeliveryWebhookSecret
      responses:
        "200":
          description: New secret (shown once)
          content:
            application/json:
              schema:
                type: object
                properties:
                  secret: { type: string }

  /webhooks/inbound:
    put:
      tags: [Account Management]
      summary: Create or update the inbound message webhook endpoint
      operationId: setInboundWebhook
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookUpsert" }
      responses:
        "200": { description: Saved }
    post:
      tags: [Account Management]
      summary: Rotate the inbound webhook signing secret
      operationId: rotateInboundWebhookSecret
      responses:
        "200":
          description: New secret (shown once)
          content:
            application/json:
              schema:
                type: object
                properties:
                  secret: { type: string }

  /report:
    get:
      tags: [Reporting]
      summary: Usage summary for your own account
      operationId: getReport
      parameters:
        - in: query
          name: from
          schema: { type: string, format: date }
        - in: query
          name: to
          schema: { type: string, format: date }
        - in: query
          name: direction
          schema: { type: string, enum: [all, outbound, inbound], default: all }
        - in: query
          name: status
          schema: { type: string, enum: [queued, sent, delivered, failed] }
        - in: query
          name: country
          schema: { type: string, enum: [AU, NZ] }
        - in: query
          name: campaign_id
          schema: { type: string, format: uuid }
        - in: query
          name: keyword
          schema: { type: string }
        - in: query
          name: number
          schema: { type: string }
      responses:
        "200":
          description: Usage summary
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ReportSummary" }

  /account/{id}/report:
    get:
      tags: [Reporting]
      summary: Usage summary for one sub-account
      operationId: getAccountReport
      parameters:
        - in: path
          name: id
          required: true
          schema: { type: string, format: uuid }
        - in: query
          name: from
          schema: { type: string, format: date }
        - in: query
          name: to
          schema: { type: string, format: date }
      responses:
        "200":
          description: Usage summary
          content:
            application/json:
              schema: { $ref: "#/components/schemas/ReportSummary" }

  /report/group:
    get:
      tags: [Reporting]
      summary: Usage for every account in your hierarchy, plus a grand total
      operationId: getGroupReport
      parameters:
        - in: query
          name: from
          schema: { type: string, format: date }
        - in: query
          name: to
          schema: { type: string, format: date }
      responses:
        "200":
          description: Group usage report
          content:
            application/json:
              schema:
                type: object
                properties:
                  from: { type: string, format: date }
                  to: { type: string, format: date }
                  accounts:
                    type: array
                    items:
                      type: object
                      properties:
                        account_id: { type: string, format: uuid }
                        business_name: { type: string }
                        messages: { $ref: "#/components/schemas/ReportMessages" }
                        credits_consumed: { type: integer }
                        inbound: { type: integer }
                        optouts: { type: integer }
                        delivery_rate: { type: number }
                  grand_total:
                    type: object
                    properties:
                      messages: { $ref: "#/components/schemas/ReportMessages" }
                      credits_consumed: { type: integer }
                      inbound: { type: integer }
                      optouts: { type: integer }


components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: API key
      description: |
        Pass your API key as `Authorization: Bearer YOUR_API_KEY`.
        Generate keys at https://app.texto.com.au → Developer → API Keys.

  responses:
    BadRequest:
      description: Bad request — invalid parameters or phone number
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    Unauthorized:
      description: Unauthorized — invalid or revoked API key
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    PaymentRequired:
      description: Payment required — insufficient credits
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    NotFound:
      description: Not found — resource doesn't exist
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }
    ServerError:
      description: Internal server error
      content: { application/json: { schema: { $ref: "#/components/schemas/Error" } } }

  schemas:
    SendRequest:
      type: object
      required: [to, message]
      properties:
        to:
          type: string
          description: Recipient Australian mobile number (E.164 preferred).
          example: "+61412345678"
        message:
          type: string
          description: SMS body text.
          example: "Hello from Texto!"
        sender:
          type: string
          description: Registered Sender ID or dedicated number. Defaults to account default.
          example: "+61400000000"

    SendResponse:
      type: object
      properties:
        message_id: { type: string, format: uuid }
        credits_used: { type: integer, example: 1 }
        credits_remaining: { type: integer, example: 99 }

    BatchRecipient:
      type: object
      required: [phone]
      properties:
        phone: { type: string, example: "+61412345678" }
        merge_data:
          type: object
          additionalProperties: { type: string }
          example: { name: "Alice" }

    BatchSendRequest:
      type: object
      required: [recipients, message]
      properties:
        recipients:
          type: array
          minItems: 1
          maxItems: 1000
          items: { $ref: "#/components/schemas/BatchRecipient" }
        message:
          type: string
          description: Body with {{merge}} placeholders.
          example: "Hi {{name}}, welcome!"
        sender:
          type: string
          example: "+61400000000"

    BatchSendResponse:
      type: object
      properties:
        campaign_id: { type: string, format: uuid }
        recipients_queued: { type: integer }
        invalid_numbers: { type: array, items: { type: string } }
        opted_out_count: { type: integer }
        credits_used: { type: integer }
        credits_remaining: { type: integer }

    Message:
      type: object
      properties:
        id: { type: string, format: uuid }
        recipient: { type: string }
        body: { type: string }
        status:
          type: string
          enum: [queued, sent, delivered, failed]
        sent_at: { type: string, format: date-time }

    DeliveryReceipt:
      type: object
      properties:
        status:
          type: string
          enum: [queued, sent, delivered, failed]
        received_at: { type: string, format: date-time }

    MessageDetail:
      type: object
      properties:
        message: { $ref: "#/components/schemas/Message" }
        delivery_receipt: { $ref: "#/components/schemas/DeliveryReceipt" }

    CampaignDetail:
      type: object
      properties:
        campaign:
          type: object
          properties:
            id: { type: string, format: uuid }
            status:
              type: string
              enum: [pending, processing, completed]
            recipient_count: { type: integer }
            created_at: { type: string, format: date-time }
        messages:
          type: array
          items: { $ref: "#/components/schemas/Message" }

    InboxPage:
      type: object
      properties:
        messages:
          type: array
          items: { $ref: "#/components/schemas/Message" }
        total: { type: integer }
        limit: { type: integer }
        offset: { type: integer }

    OptoutList:
      type: object
      properties:
        optouts:
          type: array
          items:
            type: object
            properties:
              number: { type: string }
              created_at: { type: string, format: date-time }

    Balance:
      type: object
      properties:
        credits: { type: integer, example: 100 }

    AccountDetail:
      type: object
      properties:
        id: { type: string, format: uuid }
        email: { type: string }
        business_name: { type: string }
        credits: { type: integer }
        account_status: { type: string }
        daily_send_limit: { type: integer }
        default_sender_id_value: { type: string }
        sending_number: { type: string }
        parent_account_id: { type: [string, "null"], format: uuid }
        managed_by_parent: { type: boolean }
        team_access_from_parent: { type: boolean }
        optout_exempt: { type: boolean }
        user_count: { type: integer }
        number_count: { type: integer }
        created_at: { type: string, format: date-time }

    AccountUser:
      type: object
      properties:
        id: { type: string, format: uuid }
        email: { type: string }
        name: { type: [string, "null"] }
        can_send: { type: boolean }
        can_billing: { type: boolean }
        status: { type: string, enum: [pending, active] }
        source: { type: string, enum: [direct, parent] }

    CreditTransfer:
      type: object
      required: [amount]
      properties:
        amount: { type: integer, minimum: 1, example: 500 }

    NumberList:
      type: object
      properties:
        numbers:
          type: array
          items:
            type: object
            properties:
              id: { type: string, format: uuid }
              number: { type: string, example: "+61480123456" }
              label: { type: [string, "null"] }
              assigned_to: { type: [string, "null"], format: uuid }
              price_monthly: { type: number, example: 15 }

    NumberTransfer:
      type: object
      required: [numbers]
      properties:
        numbers:
          type: array
          items: { type: string }
          example: ["+61480123456"]

    WebhookEndpoint:
      type: object
      properties:
        url: { type: string }
        enabled: { type: boolean }
        signing_enabled: { type: boolean }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    WebhookUpsert:
      type: object
      required: [url]
      properties:
        url: { type: string, description: Must start with https:// }
        enabled: { type: boolean, default: true }

    ReportMessages:
      type: object
      properties:
        total: { type: integer }
        parts: { type: integer }
        sent: { type: integer }
        delivered: { type: integer }
        failed: { type: integer }

    ReportSummary:
      type: object
      properties:
        account_id: { type: string, format: uuid }
        from: { type: string, format: date }
        to: { type: string, format: date }
        filters: { type: object }
        summary:
          type: object
          properties:
            messages: { $ref: "#/components/schemas/ReportMessages" }
            credits_consumed: { type: integer }
            inbound: { type: integer }
            optouts: { type: integer }

    WebhookEvent:
      type: object

      description: |
        Delivery receipt webhook payload. Byte-identical to GET /message/:id.
        Headers: X-Texto-Event (message.dlr), X-Texto-Delivery (UUID, dedupe on this),
        X-Texto-Signature (sha256=<hex> HMAC-SHA256 of raw body).
      properties:
        message: { $ref: "#/components/schemas/Message" }
        delivery_receipt: { $ref: "#/components/schemas/DeliveryReceipt" }

    Error:
      type: object
      properties:
        error: { type: string }
        message: { type: string }
