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

# Send a Message

> Send one message to a client: rich RCS content (a card, carousel, image, or text with suggested replies and actions) with SMS fallback text, which is required. The message appears in the practice's Oliver inbox like any other text, and clients who opted out are never texted.

Messages are sent immediately, never queued. Send only during the practice's sending hours, 8am to 9pm clinic time; outside them the request returns `400` and nothing is sent.

An opted-out recipient is not an error: it returns `200` with `status: "suppressed"`. Invalid input returns `400` with every problem in `data.errors`. If the messaging provider is briefly unavailable you get `502`; retry with the same `reference_id`.



## OpenAPI

````yaml POST /services/core/open_api/v1/messages
openapi: 3.1.0
info:
  title: Oliver Partner API
  description: API for partners to interact with Oliver
  license:
    name: MIT
  version: 1.0.0
servers:
  - url: https://partner-api.getoliver.com
security:
  - bearerAuth: []
paths:
  /services/core/open_api/v1/messages:
    post:
      summary: Send a message
      description: >-
        Send one message to a client: rich RCS content (a card, carousel, image,
        or text with suggested replies and actions) with SMS fallback text,
        which is required. The message appears in the practice's Oliver inbox
        like any other text, and clients who opted out are never texted.


        Messages are sent immediately, never queued. Send only during the
        practice's sending hours, 8am to 9pm clinic time; outside them the
        request returns `400` and nothing is sent.


        An opted-out recipient is not an error: it returns `200` with `status:
        "suppressed"`. Invalid input returns `400` with every problem in
        `data.errors`. If the messaging provider is briefly unavailable you get
        `502`; retry with the same `reference_id`.
      parameters:
        - name: X-Client-Id
          in: header
          description: The location id of the practice
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/CreateMessage'
      responses:
        '200':
          description: >-
            Suppressed (the recipient opted out), or an existing message
            returned for a repeated `reference_id`
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - data
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Message'
                  message:
                    type: string
                    nullable: true
        '201':
          description: Sent
          content:
            application/json:
              schema:
                type: object
                required:
                  - success
                  - data
                properties:
                  success:
                    type: boolean
                  data:
                    $ref: '#/components/schemas/Message'
                  message:
                    type: string
                    nullable: true
        '400':
          description: >-
            Bad Request: invalid input (every invalid field is listed), outside
            the practice's sending hours, `channel: "rcs"` at a practice without
            RCS, or content the messaging provider rejected. Nothing was sent.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ValidationError'
        '401':
          description: Unauthorized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GeneralError'
        '429':
          description: >-
            Too Many Requests: the per-practice rate limit (120/minute) was
            exceeded
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GeneralError'
        '500':
          description: Internal Server Error
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GeneralError'
        '502':
          description: >-
            Bad Gateway: the messaging provider was briefly unavailable and
            nothing was confirmed sent. Retry with the same `reference_id`;
            Oliver won't text the client twice.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/GeneralError'
components:
  schemas:
    CreateMessage:
      type: object
      required:
        - recipient
        - fallback_text
      properties:
        recipient:
          type: object
          description: >-
            Who to text: an Oliver `client_id` (sent to the client's primary
            phone) or a `phone_number`.
          properties:
            client_id:
              type: string
              example: 0b0e5a54-7c1f-4a43-9d4e-2f3c7d0d1a11
            phone_number:
              type: string
              example: '+15125551234'
        channel:
          type: string
          enum:
            - auto
            - rcs
            - sms
          default: auto
          description: >-
            `auto`: RCS when the practice's RCS sender is live and the handset
            supports it, otherwise `fallback_text` as SMS. `rcs`: RCS only,
            never SMS. `sms`: `fallback_text` alone.
        fallback_text:
          type: string
          maxLength: 1600
          description: >-
            Required. The SMS sent when RCS isn't available, and the copy the
            clinic sees in its inbox. Make it stand on its own: include any link
            the buttons carry.
          example: >-
            Hi! Millie has preventive care due October 21-22. Book here:
            https://vet.example.com/book or call us at (512) 555-0100.
        content:
          $ref: '#/components/schemas/MessageContent'
        reference_id:
          type: string
          description: >-
            Your id for this message. Echoed in every response and webhook, and
            unique per practice: re-sending the same `reference_id` returns the
            existing message instead of texting again, so retries are safe. A
            refused request (`400`) stores nothing, so you can fix it and reuse
            the `reference_id`.
          example: inv-reminder-88412
        callback_url:
          type: string
          format: uri
          description: >-
            An https URL that receives a [status
            webhook](/api-reference/messages/status_webhook) on each status
            change.
    Message:
      type: object
      properties:
        id:
          type: string
          description: Oliver's id for the message.
        reference_id:
          type: string
          nullable: true
        status:
          type: string
          enum:
            - sent
            - delivered
            - read
            - failed
            - suppressed
        status_reason:
          type: string
          nullable: true
          description: >-
            Why a message is `suppressed` (`opted_out`, `blocked_number`) or
            `failed`.
        channel_requested:
          type: string
          enum:
            - auto
            - rcs
            - sms
        channel_used:
          type: string
          enum:
            - rcs
            - sms
          nullable: true
          description: >-
            How the client got it. For `auto`, null until the carrier reports
            it; read it from `GET` or the status webhook.
        client_id:
          type: string
          nullable: true
        error_code:
          type: string
          nullable: true
          description: Carrier error code when `failed`.
        error_message:
          type: string
          nullable: true
        sent_at:
          type: string
          format: date-time
          nullable: true
        delivered_at:
          type: string
          format: date-time
          nullable: true
        read_at:
          type: string
          format: date-time
          nullable: true
        failed_at:
          type: string
          format: date-time
          nullable: true
        created_at:
          type: string
          format: date-time
    ValidationError:
      type: object
      required:
        - success
        - message
        - data
      properties:
        success:
          type: boolean
          example: false
        message:
          type: string
          description: The first error.
          example: content.card.buttons must have at most 4 items
        data:
          type: object
          properties:
            message:
              type: string
            errors:
              type: array
              description: Every offending field.
              items:
                type: object
                properties:
                  field:
                    type: string
                    example: content.card.buttons
                    nullable: true
                    description: >-
                      The offending field, or null for a request-level error
                      such as sending outside the practice's sending hours.
                  message:
                    type: string
                    example: must have at most 4 items
    GeneralError:
      type: object
      required:
        - success
        - message
      properties:
        success:
          type: boolean
        message:
          type: string
        data:
          type: object
    MessageContent:
      type: object
      required:
        - type
      description: >-
        What the client sees over RCS. One message is either a text bubble or a
        card, never both, so put any intro sentence in the card's `description`.


        - `text`: the text is `fallback_text`; add `suggestions` for reply
        chips.

        - `media`: an image or video (`media_url`), with optional `suggestions`.

        - `card`: one rich card (`card`), with optional `suggestions`.

        - `carousel`: 2 to 10 cards (`cards`) the client swipes through. No
        `suggestions`; put buttons on the cards.
      properties:
        type:
          type: string
          enum:
            - text
            - media
            - card
            - carousel
        media_url:
          type: string
          format: uri
          description: '`media` only.'
        card:
          $ref: '#/components/schemas/MessageCard'
        cards:
          type: array
          minItems: 2
          maxItems: 10
          items:
            $ref: '#/components/schemas/MessageCard'
          description: '`carousel` only.'
        suggestions:
          type: array
          maxItems: 11
          items:
            $ref: '#/components/schemas/MessageButton'
          description: Suggested replies and actions after the message.
    MessageCard:
      type: object
      description: A rich card. `title` or `description` is required.
      properties:
        title:
          type: string
          maxLength: 200
          example: Millie's upcoming care
        description:
          type: string
          maxLength: 1600
          example: >-
            Bordetella vaccine, physical exam and ProHeart 6 injection. We'll
            coordinate the services in one visit when possible.
        media_url:
          type: string
          format: uri
          description: An https image or video URL shown at the top of the card.
          example: https://cdn.example.com/millie-reminder.png
        buttons:
          type: array
          maxItems: 4
          items:
            $ref: '#/components/schemas/MessageButton'
    MessageButton:
      type: object
      required:
        - type
        - text
      description: >-
        A button on a card, or a suggestion chip. `open_url` needs `url`, `dial`
        needs `phone_number`. Our RCS provider supports only these three types.
      properties:
        type:
          type: string
          enum:
            - open_url
            - dial
            - reply
        text:
          type: string
          description: Button label. At most 25 characters (20 for `reply`).
          example: Schedule now
        url:
          type: string
          format: uri
          description: '`open_url` only. An https URL.'
          example: https://vet.example.com/book
        phone_number:
          type: string
          description: '`dial` only. E.164, e.g. `+15125550100`.'
          example: '+15125550100'
        id:
          type: string
          description: '`reply` only, optional. Identifies the reply; defaults to the label.'
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.