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

# Add a reaction

> Adds a tapback to a message in an iMessage chat, from the Blujay phone number the chat belongs to. You can react to messages your contacts sent and to your own. The six standard reactions are `love`, `like`, `dislike`, `laugh`, `emphasize` and `question`. To react with any emoji, send `type: custom` with `emoji` set to exactly one emoji, such as `😍` or `👍🏽`. A symbol sent without its emoji style, such as `❤`, is sent and reported as iPhones send it (`❤️`). A message can carry one reaction per person on each part, so adding a different one, standard or emoji, replaces yours. Recipients on iOS 17 or earlier see an emoji reaction as the text "Reacted 😍 to …".

Stickers contacts put on your messages arrive as `sticker` reactions, but you can't send stickers (`400 VALIDATION_ERROR`).

A `202` means Blujay accepted the reaction and is sending it. Confirm it through the `reaction.added` webhook, where `reactor` is your phone number. If you already have that reaction on the message, Blujay returns `202` without sending it again. Send an `Idempotency-Key` so a retry after a lost response is safe.

iMessage only: a message sent or received as SMS or RCS returns `400 UNSUPPORTED_CHAT_OPERATION`. A reaction can't itself be reacted to (`404 NOT_FOUND`), and an unsent or failed message can't be either (`400 VALIDATION_ERROR`). A reaction notifies the other person like a message, so a chat with an opted-out contact returns `403 RECIPIENT_SUPPRESSED`, the same rule as sending a message in it.




## OpenAPI

````yaml /api-reference/openapi.yaml post /v1/messages/{message_id}/reactions
openapi: 3.1.0
info:
  title: Blujay API
  version: 1.0.0
  description: >-
    The Blujay API lets you send and receive iMessage, RCS and SMS messages from
    your

    Blujay phone numbers, manage contacts, and receive real-time webhooks.


    Every response uses the same JSON envelope and carries a `trace_id` you can
    share

    with support. Authenticate with your workspace API key.
servers:
  - url: https://api.blujay.io
    description: Production
security:
  - PublicApiKey: []
tags:
  - name: Auth
    description: Check which workspace and credential a request is using.
  - name: Chats
    description: >-
      Direct and group conversations between your Blujay numbers and your
      contacts.
  - name: Messages
    description: Send, read, edit, unsend and react to messages.
  - name: Phone Numbers
    description: >-
      Your Blujay numbers, whether each can send right now, and their live
      sending limits.
  - name: Capability
    description: >-
      Check whether a phone number can be reached by iMessage, RCS or SMS before
      you send.
  - name: Contacts
    description: Create, find, update, tag, assign and opt out contacts.
  - name: vCards
    description: >-
      Saved contact cards that you can send in a message. Recipients get a
      native contact card over iMessage, SMS or RCS.
  - name: Team Members
    description: People in your workspace you can assign contacts to.
  - name: Workflows
    description: Enroll contacts in workflows you start manually.
  - name: Attachments
    description: >-
      Upload files once and reuse them in messages, or download files from
      messages.
  - name: Calls
    description: Download call recordings referenced by call webhooks.
  - name: Webhooks
    description: Subscribe to events, inspect delivery attempts and verify signatures.
  - name: Partners
    description: >-
      For Blujay reseller partners. A partner runs many customer workspaces
      through one integration. Partner keys carry a role (`admin` or
      `messaging`) and name a customer in the `Blujay-Workspace` header.
paths:
  /v1/messages/{message_id}/reactions:
    post:
      tags:
        - Messages
      summary: Add a reaction
      description: >
        Adds a tapback to a message in an iMessage chat, from the Blujay phone
        number the chat belongs to. You can react to messages your contacts sent
        and to your own. The six standard reactions are `love`, `like`,
        `dislike`, `laugh`, `emphasize` and `question`. To react with any emoji,
        send `type: custom` with `emoji` set to exactly one emoji, such as `😍`
        or `👍🏽`. A symbol sent without its emoji style, such as `❤`, is sent
        and reported as iPhones send it (`❤️`). A message can carry one reaction
        per person on each part, so adding a different one, standard or emoji,
        replaces yours. Recipients on iOS 17 or earlier see an emoji reaction as
        the text "Reacted 😍 to …".


        Stickers contacts put on your messages arrive as `sticker` reactions,
        but you can't send stickers (`400 VALIDATION_ERROR`).


        A `202` means Blujay accepted the reaction and is sending it. Confirm it
        through the `reaction.added` webhook, where `reactor` is your phone
        number. If you already have that reaction on the message, Blujay returns
        `202` without sending it again. Send an `Idempotency-Key` so a retry
        after a lost response is safe.


        iMessage only: a message sent or received as SMS or RCS returns `400
        UNSUPPORTED_CHAT_OPERATION`. A reaction can't itself be reacted to (`404
        NOT_FOUND`), and an unsent or failed message can't be either (`400
        VALIDATION_ERROR`). A reaction notifies the other person like a message,
        so a chat with an opted-out contact returns `403 RECIPIENT_SUPPRESSED`,
        the same rule as sending a message in it.
      operationId: createPublicMessageReaction
      parameters:
        - $ref: '#/components/parameters/MessageId'
        - $ref: '#/components/parameters/IdempotencyKey'
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/MessageReactionRequest'
            examples:
              love:
                summary: Love a message
                value:
                  type: love
              custom:
                summary: React with an emoji
                value:
                  type: custom
                  emoji: 😍
      responses:
        '202':
          $ref: '#/components/responses/MessageReactionAccepted'
        '400':
          $ref: '#/components/responses/MessageReactionRejected'
        '403':
          $ref: '#/components/responses/RecipientSuppressed'
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          $ref: '#/components/responses/MessageReactionRejected'
        '502':
          $ref: '#/components/responses/MessageReactionRejected'
        '503':
          $ref: '#/components/responses/PhoneNumberUnavailable'
      security:
        - PublicApiKey: []
components:
  parameters:
    MessageId:
      name: message_id
      in: path
      required: true
      schema:
        $ref: '#/components/schemas/MessageId'
    IdempotencyKey:
      name: Idempotency-Key
      in: header
      required: false
      schema:
        type: string
        minLength: 1
        maxLength: 255
      description: >
        A unique value you choose (1–255 characters) that makes this request
        safe to retry. Send the same key with the same request and Blujay
        returns the original response instead of doing the work again. Use a new
        key for each new action.


        Blujay keeps keys for at least 24 hours. Keys are separate for each API
        key and each route family (for example every `POST
        /v1/chats/{chat_id}/messages` shares one). Reusing a key with a
        different request returns `409 IDEMPOTENCY_REQUEST_MISMATCH`. Sending it
        while the first request is still running returns `409
        IDEMPOTENCY_IN_PROGRESS`; retry shortly.
  schemas:
    MessageReactionRequest:
      type: object
      additionalProperties: false
      required:
        - type
      properties:
        type:
          $ref: '#/components/schemas/ReactionType'
        emoji:
          allOf:
            - $ref: '#/components/schemas/ReactionEmoji'
          description: >-
            Required with type `custom`, and not allowed with the standard
            types.
        part_index:
          type: integer
          minimum: 0
          default: 0
          description: >-
            Which part of the message to react to, numbered the same way as
            `part_index` in `reaction.added`. For a text-only message, `0` is
            the only part.
    MessageId:
      type: string
      examples:
        - msg_abc123
      pattern: ^msg_[A-Za-z0-9_-]+$
    ReactionType:
      type: string
      enum:
        - love
        - like
        - dislike
        - laugh
        - emphasize
        - question
        - custom
      description: >-
        One of Apple's six standard tapbacks, or `custom` for an emoji reaction
        (set `emoji`).
    ReactionEmoji:
      type: string
      examples:
        - 😍
      minLength: 1
      description: >-
        Exactly one emoji, as the emoji keyboard sends it. Skin tones, families,
        flags and keycaps count as one emoji.
    SuccessEnvelope:
      type: object
      description: Wraps every successful response. The result is in `data`.
      required:
        - status
        - trace_id
        - timestamp
      properties:
        status:
          type: string
          enum:
            - success
        trace_id:
          $ref: '#/components/schemas/TraceId'
        timestamp:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
        client_request_id:
          type: string
          description: >-
            Your `X-Request-ID` header value, echoed back when you send one. Not
            included on the send responses (`POST /v1/chats` and `POST
            /v1/chats/{chat_id}/messages`); keep your own mapping from your
            request ID to the returned message ID there.
    AcceptedMessageReaction:
      type: object
      additionalProperties: false
      required:
        - message_id
        - chat_id
        - phone_number_id
        - operation
        - status
        - reaction
      properties:
        message_id:
          $ref: '#/components/schemas/MessageId'
        chat_id:
          $ref: '#/components/schemas/ChatId'
        phone_number_id:
          $ref: '#/components/schemas/PhoneNumberId'
          description: >-
            The Blujay phone number the reaction is sent from, which is the
            number in the message's chat.
        operation:
          type: string
          enum:
            - reaction_add
            - reaction_remove
        status:
          const: accepted
        reaction:
          type: object
          additionalProperties: false
          required:
            - type
            - part_index
          properties:
            type:
              $ref: '#/components/schemas/ReactionType'
            part_index:
              type: integer
              minimum: 0
            emoji:
              allOf:
                - $ref: '#/components/schemas/ReactionEmoji'
              description: The emoji, present when `type` is `custom`.
    ErrorEnvelope:
      type: object
      description: >-
        Wraps every error response. Branch on `error.code`, and include
        `trace_id` when you contact support.
      required:
        - status
        - error
        - trace_id
        - timestamp
      properties:
        status:
          const: error
        error:
          $ref: '#/components/schemas/PublicError'
        trace_id:
          $ref: '#/components/schemas/TraceId'
        timestamp:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
        client_request_id:
          type: string
          description: Your `X-Request-ID` header value, echoed back when you send one.
    TraceId:
      type: string
      examples:
        - trc_8f2a1c0e4b7d
      pattern: ^trc_[A-Za-z0-9_-]+$
    ChatId:
      type: string
      examples:
        - chat_abc123
      pattern: ^chat_[A-Za-z0-9_-]+$
    PhoneNumberId:
      type: string
      examples:
        - phone_abc123
      pattern: ^phone_[A-Za-z0-9_-]+$
    PublicError:
      type: object
      required:
        - code
        - message
        - type
        - http_status
        - retryable
        - doc_url
        - details
      properties:
        code:
          type: string
          description: Stable, machine-readable error code. Branch on this value.
          examples:
            - RATE_LIMIT_EXCEEDED
            - QUEUE_UNAVAILABLE
            - FEATURE_NOT_ENABLED
        message:
          type: string
          description: Human-readable explanation. It can change, so don't parse it.
        type:
          type: string
          description: Broad error category.
          examples:
            - validation
            - auth
            - idempotency
            - rate_limit
            - queue
            - feature_gate
        http_status:
          type: integer
          description: The HTTP status code of the response.
          enum:
            - 400
            - 401
            - 403
            - 404
            - 409
            - 413
            - 415
            - 422
            - 429
            - 500
            - 502
            - 503
        retryable:
          type: boolean
          description: Whether sending the same request again later can succeed.
        doc_url:
          type: string
          format: uri
          description: Link to documentation for this error code.
        details:
          type: array
          description: >-
            Extra structured context, such as the fields that failed validation
            or the limit you hit.
          items:
            type: object
            additionalProperties: true
        retry_after:
          type: integer
          minimum: 1
          description: >-
            Seconds to wait before retrying. Also sent as the `Retry-After`
            header.
        reset_at:
          type: string
          examples:
            - '2026-10-14T17:01:00.000Z'
          format: date-time
          description: When the limit you hit resets.
  responses:
    MessageReactionAccepted:
      description: >-
        Blujay accepted the reaction and is sending it from the message's phone
        number. Confirm it through the `reaction.added` or `reaction.removed`
        webhook. If the reaction was already in place (or already absent), the
        response is the same and nothing is sent.
      headers:
        X-Trace-ID:
          $ref: '#/components/headers/XTraceID'
      content:
        application/json:
          schema:
            allOf:
              - $ref: '#/components/schemas/SuccessEnvelope'
              - type: object
                required:
                  - data
                properties:
                  data:
                    $ref: '#/components/schemas/AcceptedMessageReaction'
          examples:
            accepted:
              value:
                status: success
                data:
                  message_id: msg_abc123
                  chat_id: chat_abc123
                  phone_number_id: phone_abc123
                  operation: reaction_add
                  status: accepted
                  reaction:
                    type: love
                    part_index: 0
                trace_id: trc_8f2a1c0e4b7d
                timestamp: '2026-10-14T17:02:00.000Z'
    MessageReactionRejected:
      description: >
        The reaction was refused, and nothing was sent.


        `400`:

        - `VALIDATION_ERROR`: the request is invalid (including a `custom`
        reaction without exactly one emoji, or a sticker), or the message was
        unsent or failed to send.

        - `UNSUPPORTED_CHAT_OPERATION`: the message was sent or received as SMS
        or RCS. Reactions are iMessage only.


        `403 RECIPIENT_SUPPRESSED`: the contact in this chat opted out.


        `409`:

        - `MESSAGE_NOT_SENT_YET`: your message hasn't finished sending yet.
        Retry shortly.

        - `SENDER_UNAVAILABLE`: the phone number the message belongs to is no
        longer available.

        - `IDEMPOTENCY_REQUEST_MISMATCH` or `IDEMPOTENCY_IN_PROGRESS`: see
        `Idempotency-Key`.


        `502 CHANGE_REJECTED`: the phone number refused the reaction. Don't
        retry the same request.
      headers:
        X-Trace-ID:
          $ref: '#/components/headers/XTraceID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    RecipientSuppressed:
      description: The recipient opted out, so the message or reaction can't be sent.
      headers:
        X-Trace-ID:
          $ref: '#/components/headers/XTraceID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          examples:
            recipientSuppressed:
              value:
                status: error
                error:
                  code: RECIPIENT_SUPPRESSED
                  message: Recipient is suppressed for outbound messaging
                  type: authorization
                  http_status: 403
                  retryable: false
                  doc_url: https://docs.blujay.io/errors/recipient-suppressed
                  details: []
                trace_id: trc_8f2a1c0e4b7d
                timestamp: '2026-10-14T00:00:00.000Z'
    NotFound:
      description: The resource doesn't exist or isn't in your workspace.
      headers:
        X-Trace-ID:
          $ref: '#/components/headers/XTraceID'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    PhoneNumberUnavailable:
      description: >-
        The messaging network couldn't accept the request right now
        (`PHONE_NUMBER_UNAVAILABLE`). Nothing was changed. Retry after
        `Retry-After` seconds.
      headers:
        X-Trace-ID:
          $ref: '#/components/headers/XTraceID'
        Retry-After:
          $ref: '#/components/headers/RetryAfter'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          examples:
            phoneNumberUnavailable:
              value:
                status: error
                error:
                  code: PHONE_NUMBER_UNAVAILABLE
                  message: This service is temporarily unavailable
                  type: phone_number
                  http_status: 503
                  retryable: true
                  doc_url: https://docs.blujay.io/errors/phone-number-unavailable
                  details: []
                  retry_after: 30
                trace_id: trc_8f2a1c0e4b7d
                timestamp: '2026-10-14T00:00:00.000Z'
  headers:
    XTraceID:
      description: >-
        Unique ID for this request. It matches `trace_id` in the body. Include
        it when you contact Blujay support.
      schema:
        $ref: '#/components/schemas/TraceId'
    RetryAfter:
      description: Seconds to wait before retrying.
      schema:
        type: integer
        minimum: 1
  securitySchemes:
    PublicApiKey:
      type: http
      scheme: bearer
      bearerFormat: blu_...
      description: >
        Your workspace API key, sent as `Authorization: Bearer blu_...`.
        Workspace admins can find it in the Blujay app under **API**.


        An API key has full access to your workspace and works on every
        endpoint. There is no test mode: every key sends real messages and
        changes real data, so keep it secret and use it only from your server.


        Each API key can make 600 read requests and 120 write requests per
        minute, plus 60 attachment requests per minute. Over a limit you get
        `429 RATE_LIMIT_EXCEEDED` with `Retry-After`.


        **Reseller partner keys.** A partner key acts for one of the partner's
        customers when the request names it in the `Blujay-Workspace` header
        (the customer's `workspace_` ID). Without the header it reaches only
        `/v1/partner/*` and `/v1/me`. Each customer gets the limits above for
        each partner key, and all of a partner's requests together get ten times
        those limits; a `429` says which in `error.details[0].scope`
        (`partner_customer` or `partner`). A `messaging` key can read anything
        for a customer and change chats, messages, contacts, attachments and
        vCards; everything else needs an `admin` key (`403
        PARTNER_ROLE_FORBIDDEN`). Paused or closing customers can be read but
        not changed (`409 CUSTOMER_PAUSED` or `CUSTOMER_CLOSING`). Any other
        workspace ID in the header returns `404 NOT_FOUND`; using the header
        with any other credential returns `400 WORKSPACE_HEADER_NOT_ALLOWED`.

````

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