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

# Remove a tapback

> Take back a tapback you sent. It's removed from the contact's phone, and `message.reaction.removed` fires when that happens. Only a tapback from you, on this message, can be removed.

Idempotent: removing one already removed (or replaced, or failed) answers `200` with it unchanged.

Counts toward the conversation-actions rate limit (300 a minute per account).

### Remove a tapback

**Method:** `DELETE` | **Endpoint:** `/v1/messages/{message_id}/reactions/{reaction_id}`

**Description:** Take back a tapback you [added](https://docs.ibubbl.com/api-reference/messages/react.md). It disappears from the message on the contact's phone, and [`message.reaction.removed`](https://docs.ibubbl.com/webhooks/events) fires when it does.

**Authentication:** API key in the `Authorization` header: `Authorization: Bearer ib_pk_<64 hex>`. Create keys in the dashboard under **Settings → API access**.

**Rate limit:** 300 conversation actions per 60 seconds per account, shared with tapbacks, mark read and typing. Over that, `429 rate_limited` with a `Retry-After` header; see [Rate limits](https://docs.ibubbl.com/concepts/limits.md#rate-limits).

#### Request Body

None. The path parameters are the message's `message_id` and the reaction's `id` (`reaction_id`), both UUIDs.

#### How it behaves

* **Only your own tapback, on that message.** A contact's reaction, or a reaction on another message, answers `404 not_found`.
* **The answer comes first.** It returns `200` with `status: "removed"` at once. `message.reaction.removed` fires when the phone confirms. If your line can't remove it, [`message.reaction.failed`](https://docs.ibubbl.com/webhooks/events) fires with `action: "remove"` and `failed_reason: "retract_rejected"`, and the tapback may still show on the phone.
* **A tapback still `pending`** won't be sent once you remove it.
* **It's safe to call twice.** Removing a tapback that's already removed, replaced or failed answers `200` with it unchanged.
* **Opt-outs don't stop it.** You can remove a tapback after the contact opted out.

```bash cURL theme={null}
curl -X DELETE https://api.ibubbl.com/v1/messages/8b3a4e72-1c2d-4e5f-9a0b-2c3d4e5f6a7b/reactions/7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d \
  -H "Authorization: Bearer $IBUBBL_API_KEY"
```

```typescript TypeScript theme={null}
const messageId = "8b3a4e72-1c2d-4e5f-9a0b-2c3d4e5f6a7b";
const reactionId = "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d";

const res = await fetch(
  `https://api.ibubbl.com/v1/messages/${messageId}/reactions/${reactionId}`,
  {
    method: "DELETE",
    headers: { Authorization: `Bearer ${process.env.IBUBBL_API_KEY}` },
    signal: AbortSignal.timeout(10_000),
  },
);

const data = await res.json();
if (!res.ok) throw new Error(`${res.status} ${data.detail?.error}`);
console.log(data.status, data.removed_at);
```

```python Python theme={null}
import os

import requests

API_KEY = os.environ["IBUBBL_API_KEY"]

message_id = "8b3a4e72-1c2d-4e5f-9a0b-2c3d4e5f6a7b"
reaction_id = "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d"

res = requests.delete(
    f"https://api.ibubbl.com/v1/messages/{message_id}/reactions/{reaction_id}",
    headers={"Authorization": f"Bearer {API_KEY}"},
    timeout=10,
)
data = res.json()
if not res.ok:
    raise RuntimeError(f"{res.status_code} {data['detail']['error']}")
print(data["status"], data["removed_at"])
```

```json 200 OK theme={null}
{
  "id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
  "message_id": "8b3a4e72-1c2d-4e5f-9a0b-2c3d4e5f6a7b",
  "conversation_id": "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d",
  "type": "love",
  "emoji": "❤️",
  "direction": "outbound",
  "status": "removed",
  "failed_reason": null,
  "created_at": "2026-11-11T16:03:25.403057Z",
  "removed_at": "2026-11-11T16:04:04.236980Z"
}
```

```json 404 Not Found theme={null}
{
  "detail": {
    "error": "not_found",
    "message": "Reaction not found"
  }
}
```

#### Responses

**200 OK**

The reaction, now `removed` (the same object as [Add a tapback](https://docs.ibubbl.com/api-reference/messages/react.md) returns). A tapback that had already failed keeps `status: "failed"`.

```json theme={null}
{
  "id": "7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d",
  "message_id": "8b3a4e72-1c2d-4e5f-9a0b-2c3d4e5f6a7b",
  "conversation_id": "3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d",
  "type": "love",
  "emoji": "❤️",
  "direction": "outbound",
  "status": "removed",
  "failed_reason": null,
  "created_at": "2026-11-11T16:03:25.403057Z",
  "removed_at": "2026-11-11T16:04:04.236980Z"
}
```

**401 Unauthorized**

```json theme={null}
{
  "detail": {
    "error": "unauthorized",
    "message": "Invalid or revoked API key"
  }
}
```

Other `message` values: `Authorization Bearer token required`, `API key required (use Authorization: Bearer ib_pk_…)`, `Invalid API key format`, `Customer account not found`.

**403 Forbidden**

```json theme={null}
{
  "detail": {
    "error": "forbidden",
    "message": "Account is suspended"
  }
}
```

Your iBubbl account is suspended or cancelled (`message` is then `Account is cancelled`). Every API-key endpoint returns this.

**404 Not Found**

```json theme={null}
{
  "detail": {
    "error": "not_found",
    "message": "Reaction not found"
  }
}
```

Also returned for a malformed ID, a contact's reaction, a reaction on a different message than the one in the path, and one that belongs to another account.

**422 Unprocessable Entity**

```json theme={null}
{
  "detail": {
    "error": "target_unsent",
    "message": "The contact unsent this message."
  }
}
```

`target_unsent` or `target_partly_unsent`: the contact unsent the message (or part of it) after you reacted, so the tapback can't be changed any more.

**429 Too Many Requests**

```json theme={null}
{
  "detail": {
    "error": "rate_limited",
    "message": "Too many conversation actions; wait 12s (limit 300 a minute per account)."
  }
}
```

Wait the `Retry-After` header's seconds, then send the same request again.


## OpenAPI

````yaml DELETE /v1/messages/{message_id}/reactions/{reaction_id}
openapi: 3.1.0
info:
  title: iBubbl API
  version: v1
  description: >-
    The iBubbl REST API. Send iMessages, manage contacts, and subscribe to
    webhook events from your backend.


    Authenticate every request with a Bearer API key (`ib_pk_<hex>`) — generate
    one from your dashboard's API Access settings. See the
    [Authentication](/authentication) guide.
servers:
  - url: https://api.ibubbl.com
security:
  - BearerAuth: []
tags:
  - name: Messages
  - name: Contacts
  - name: Conversations
  - name: Webhooks
  - name: Inbound Events
paths:
  /v1/messages/{message_id}/reactions/{reaction_id}:
    delete:
      tags:
        - Messages
      summary: Remove a tapback
      description: >-
        Take back a tapback you sent. It's removed from the contact's phone, and
        `message.reaction.removed` fires when that happens. Only a tapback from
        you, on this message, can be removed.


        Idempotent: removing one already removed (or replaced, or failed)
        answers `200` with it unchanged.


        Counts toward the conversation-actions rate limit (300 a minute per
        account).
      operationId: >-
        v1_remove_reaction_v1_messages__message_id__reactions__reaction_id__delete
      parameters:
        - name: message_id
          in: path
          required: true
          schema:
            type: string
            title: Message Id
        - name: reaction_id
          in: path
          required: true
          schema:
            type: string
            title: Reaction Id
      responses:
        '200':
          description: The reaction, now `removed`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Reaction'
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '403':
          description: '`forbidden`: your iBubbl account is suspended or cancelled.'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '404':
          description: Message (or reaction) not found.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '422':
          description: >-
            `target_unsent` or `target_partly_unsent`: the contact unsent the
            message, so the tapback can't be changed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
        '429':
          description: >-
            `rate_limited`: more than 300 conversation actions in 60 seconds
            from your account (all keys, and tapbacks, read and typing requests,
            count together; list requests have their own budget). Wait
            `Retry-After` seconds, then send the same request again.
          headers:
            Retry-After:
              description: Seconds to wait before retrying.
              schema:
                type: integer
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/ApiError'
components:
  schemas:
    Reaction:
      properties:
        id:
          type: string
          title: Id
          description: Reaction ID. The `reaction_id` in `message.reaction.*` events.
        message_id:
          type: string
          title: Message Id
          description: The message the tapback is on.
        conversation_id:
          anyOf:
            - type: string
            - type: 'null'
          title: Conversation Id
          description: The conversation that message is in.
        type:
          type: string
          title: Type
          description: '`love`, `like`, `dislike`, `laugh`, `emphasize` or `question`.'
        emoji:
          type: string
          title: Emoji
          description: The tapback as an emoji, e.g. `❤️`.
        direction:
          type: string
          enum:
            - outbound
          const: outbound
          title: Direction
          description: 'Always `outbound`: a tapback you sent.'
        status:
          type: string
          enum:
            - pending
            - sent
            - failed
            - removed
          title: Status
          description: >-
            `pending`: accepted, not yet on the contact's phone. `sent`:
            delivered to Messages (`message.reaction.added` fires then).
            `failed`: never sent, see `failed_reason` (`message.reaction.failed`
            fires). `removed`: you removed it, or a newer tapback of yours on
            the same message replaced it.
        failed_reason:
          anyOf:
            - type: string
            - type: 'null'
          title: Failed Reason
          description: >-
            Why a `failed` tapback wasn't sent: `sms_target`,
            `target_not_found`, `bb_rejected`, `messages_error`, `expired`,
            `retract_rejected` or `target_unsent`. Null otherwise.
        created_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Created At
          description: When you sent the tapback (UTC).
        removed_at:
          anyOf:
            - type: string
            - type: 'null'
          title: Removed At
          description: When it was removed or replaced (UTC); null while active.
      type: object
      required:
        - id
        - message_id
        - conversation_id
        - type
        - emoji
        - direction
        - status
        - failed_reason
        - created_at
        - removed_at
      title: Reaction
      example:
        conversation_id: 3a4b5c6d-7e8f-9a0b-1c2d-3e4f5a6b7c8d
        created_at: '2026-11-11T15:04:05.123Z'
        direction: outbound
        emoji: ❤️
        id: 7a8b9c0d-1e2f-3a4b-5c6d-7e8f9a0b1c2d
        message_id: 8b3a4e72-1c2d-4e5f-9a0b-2c3d4e5f6a7b
        status: pending
        type: love
    ApiError:
      properties:
        detail:
          allOf:
            - $ref: '#/components/schemas/ApiErrorDetail'
          description: >-
            Error envelope. Every `/v1` error response wraps a stable `error`
            code and a human-readable `message` in `detail`.
      type: object
      required:
        - detail
      title: ApiError
      example:
        detail:
          error: validation_error
          message: >-
            Exactly one of contact_id, to, chat_guid, or participant_phones is
            required
    ApiErrorDetail:
      properties:
        error:
          type: string
          title: Error
          description: >-
            Stable machine-readable error code. Use this for branching logic in
            your integration; the human-readable `message` is subject to change.
            See the Error Reference for the catalog.
          examples:
            - validation_error
        message:
          type: string
          title: Message
          description: Human-readable description of what went wrong.
          examples:
            - >-
              Exactly one of contact_id, to, chat_guid, or participant_phones is
              required
      type: object
      required:
        - error
        - message
      title: ApiErrorDetail
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: ib_pk_<64 hex>
      description: >-
        Bearer API key. Format: `Authorization: Bearer ib_pk_<64 hex>` (70
        characters). Generate keys from Dashboard → Settings → API Access.

````

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