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

# Consuming messages

> Lease, acknowledge, release, and inspect messages in a trigger inbox.

# Consuming messages

Once your [trigger inbox](/white-label/trigger-inbox/what-is-trigger-inbox-api) is active, you work with messages through four operations: lease, acknowledge, release, and list. This page explains each operation, their parameters, and how to handle edge cases like quarantined messages and possible duplicates.

## Lease messages

Leasing claims a batch of available messages and gives your app exclusive access for a set duration. No other consumer can claim the same messages while they are leased. If you do not acknowledge before the lease expires, the messages return to the queue automatically.

The Python examples on this page use a `headers` variable defined once at the top of your script:

```python theme={null}
import requests

TOKEN = "your-access-token"
INBOX_ID = "your-inbox-id"

headers = {"Authorization": f"Bearer {TOKEN}"}
```

<CodeGroup>
  ```bash cURL theme={null}
  curl -s -X POST "https://api.zapier.com/trigger-inbox/api/v1/inboxes/$INBOX_ID/messages/lease" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"lease_seconds": 300, "lease_limit": 10}' | python3 -m json.tool
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(
    `https://api.zapier.com/trigger-inbox/api/v1/inboxes/${INBOX_ID}/messages/lease`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ lease_seconds: 300, lease_limit: 10 }),
    }
  );
  const data = await res.json();
  console.log(data.lease_id, data.results.length);
  ```

  ```python Python theme={null}
  response = requests.post(
      f"https://api.zapier.com/trigger-inbox/api/v1/inboxes/{INBOX_ID}/messages/lease",
      json={"lease_seconds": 300, "lease_limit": 10},
      headers=headers,
  )
  response.raise_for_status()
  data = response.json()
  print(data["lease_id"], len(data["results"]))
  ```
</CodeGroup>

### Request parameters

| Parameter       | Type    | Default | Max  | Description                                                                                                   |
| --------------- | ------- | ------- | ---- | ------------------------------------------------------------------------------------------------------------- |
| `lease_seconds` | integer | 300     | 3600 | How long, in seconds, to hold the lease. Messages return to the queue if not acknowledged within this window. |
| `lease_limit`   | integer | 10      | 100  | Maximum number of messages to lease in one call.                                                              |

### Response fields

| Field                                                  | Description                                                                                                              |
| ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------ |
| `lease_id`                                             | Unique identifier for the lease batch. Required for acknowledge and release. `null` if no messages are available.        |
| `leased_until`                                         | ISO 8601 timestamp when the lease expires. `null` if no messages are available.                                          |
| `results[]`                                            | Array of leased messages. Empty if no messages are available.                                                            |
| `results[].id`                                         | Unique message identifier.                                                                                               |
| `results[].created_at`                                 | When the message was queued.                                                                                             |
| `results[].status`                                     | `"leased"` while held, transitions to `"acked"` after acknowledgment.                                                    |
| `results[].payload`                                    | The event data from the connected app.                                                                                   |
| `results[].message_attributes.lease_count`             | Number of times this message has been leased. Increases each time a lease expires or is released.                        |
| `results[].message_attributes.error_message`           | Error from the data hydration step, if one occurred. The message is still delivered. Check this field before processing. |
| `results[].message_attributes.possible_duplicate_data` | `true` if Zapier detected a potential duplicate due to a deduplication key change.                                       |
| `inbox_attributes.status`                              | Current inbox status at the time of the lease call.                                                                      |
| `inbox_attributes.paused_reason`                       | Set if the inbox is paused.                                                                                              |

### Empty queue

When no messages are available, the API returns HTTP 200 with `lease_id: null` and `results: []`.

```json theme={null}
{
  "lease_id": null,
  "leased_until": null,
  "results": [],
  "inbox_attributes": {
    "status": "active",
    "paused_reason": null
  }
}
```

Continue polling on a 200 response. This is the normal idle state between events.

### Paused and drained (409)

If the inbox is paused and all buffered messages have been consumed, the API returns HTTP 409:

```json theme={null}
{
  "detail": "Inbox is paused and fully drained.",
  "inbox_attributes": {
    "status": "paused",
    "paused_reason": "authentication",
    "paused_at": "2026-03-12T16:59:47.185Z"
  }
}
```

Check `inbox_attributes.paused_reason` to determine the appropriate action. For `authentication` or `authentication_access_revoked`, the connected app account needs to be reconnected. For other reasons, go to [Manage your inbox](/white-label/trigger-inbox/manage-your-inbox#handle-an-inbox-paused-by-zapier) for the full list of actions.

<Note>
  A 409 response means the inbox is paused *and* empty. If messages are still available in a paused inbox, leasing continues to return them with HTTP 201 until the queue drains.
</Note>

***

## Acknowledge messages

Acknowledging permanently removes messages from the inbox. Only acknowledge after your processing succeeds.

### Acknowledge a full batch

Pass the `lease_id` to acknowledge all messages in the lease:

<CodeGroup>
  ```bash cURL theme={null}
  curl -s -X POST "https://api.zapier.com/trigger-inbox/api/v1/inboxes/$INBOX_ID/messages/ack" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"lease_id": "'"$LEASE_ID"'"}' | python3 -m json.tool
  ```

  ```typescript TypeScript theme={null}
  await fetch(
    `https://api.zapier.com/trigger-inbox/api/v1/inboxes/${INBOX_ID}/messages/ack`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ lease_id: LEASE_ID }),
    }
  );
  ```

  ```python Python theme={null}
  requests.post(
      f"https://api.zapier.com/trigger-inbox/api/v1/inboxes/{INBOX_ID}/messages/ack",
      json={"lease_id": LEASE_ID},
      headers=headers,
  ).raise_for_status()
  ```
</CodeGroup>

A successful response returns HTTP 201 with each acknowledged message showing `"status": "acked"`.

### Acknowledge specific messages

To acknowledge a subset of the leased batch, include `message_ids`:

<CodeGroup>
  ```bash cURL theme={null}
  curl -s -X POST "https://api.zapier.com/trigger-inbox/api/v1/inboxes/$INBOX_ID/messages/ack" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{
      "lease_id": "'"$LEASE_ID"'",
      "message_ids": ["'"$MESSAGE_ID_1"'", "'"$MESSAGE_ID_2"'"]
    }' | python3 -m json.tool
  ```

  ```typescript TypeScript theme={null}
  await fetch(
    `https://api.zapier.com/trigger-inbox/api/v1/inboxes/${INBOX_ID}/messages/ack`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        lease_id: LEASE_ID,
        message_ids: [MESSAGE_ID_1, MESSAGE_ID_2],
      }),
    }
  );
  ```

  ```python Python theme={null}
  requests.post(
      f"https://api.zapier.com/trigger-inbox/api/v1/inboxes/{INBOX_ID}/messages/ack",
      json={"lease_id": LEASE_ID, "message_ids": [MESSAGE_ID_1, MESSAGE_ID_2]},
      headers=headers,
  ).raise_for_status()
  ```
</CodeGroup>

Non-acknowledged messages in the same lease remain leased until the lease expires, then return to the queue for retry.

### Expired leases

If the lease has expired when you call acknowledge, the API returns an error. Messages that have been leased and returned to the queue 5 times without acknowledgment are [quarantined](/white-label/trigger-inbox/what-is-trigger-inbox-api#message-lifecycle) automatically. Check [Handle quarantined messages](#handle-quarantined-messages) for next steps.

<Note>
  The Trigger Inbox API uses [at-least-once delivery](/white-label/trigger-inbox/what-is-trigger-inbox-api#how-it-works). Build idempotent processing and check the message `id` before acting on a payload.
</Note>

***

## Release messages

Releasing immediately returns leased messages to the queue without acknowledging them. Use release when your processing has failed and you want to make the messages available again without waiting for the lease to expire.

<CodeGroup>
  ```bash cURL theme={null}
  curl -s -X POST "https://api.zapier.com/trigger-inbox/api/v1/inboxes/$INBOX_ID/messages/release" \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d '{"lease_id": "'"$LEASE_ID"'"}' | python3 -m json.tool
  ```

  ```typescript TypeScript theme={null}
  await fetch(
    `https://api.zapier.com/trigger-inbox/api/v1/inboxes/${INBOX_ID}/messages/release`,
    {
      method: "POST",
      headers: {
        Authorization: `Bearer ${TOKEN}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ lease_id: LEASE_ID }),
    }
  );
  ```

  ```python Python theme={null}
  requests.post(
      f"https://api.zapier.com/trigger-inbox/api/v1/inboxes/{INBOX_ID}/messages/release",
      json={"lease_id": LEASE_ID},
      headers=headers,
  ).raise_for_status()
  ```
</CodeGroup>

Released messages return with `"status": "available"` in the response.

To release specific messages within a lease, include `message_ids`, using the same pattern as partial acknowledgment.

<Note>
  Release counts as an attempt. Each release increments `lease_count` toward the quarantine threshold of 5. If you expect repeated failures on a message, investigate before releasing again rather than looping.
</Note>

**Release vs. letting the lease expire:** Use release when you want messages available immediately for another consumer or for a retry. Let the lease expire when you prefer a delay before retry, for example to back off from a downstream service that is temporarily unavailable.

***

## List messages

To inspect the current state of your inbox, including quarantined messages and messages with errors, use the list endpoint:

<CodeGroup>
  ```bash cURL theme={null}
  curl -s "https://api.zapier.com/trigger-inbox/api/v1/inboxes/$INBOX_ID/messages" \
    -H "Authorization: Bearer $TOKEN" | python3 -m json.tool
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(
    `https://api.zapier.com/trigger-inbox/api/v1/inboxes/${INBOX_ID}/messages`,
    { headers: { Authorization: `Bearer ${TOKEN}` } }
  );
  const data = await res.json();
  console.log(data.results);
  ```

  ```python Python theme={null}
  resp = requests.get(
      f"https://api.zapier.com/trigger-inbox/api/v1/inboxes/{INBOX_ID}/messages",
      headers=headers,
  )
  resp.raise_for_status()
  print(resp.json()["results"])
  ```
</CodeGroup>

Results are paginated using cursor-based pagination. Use the `next` link from the response to retrieve the next page. List results include messages in all states: `available`, `leased`, `acked`, and `quarantined`.

<Note>
  List results do not include message payloads. Use leasing to retrieve payloads.
</Note>

***

## Handle quarantined messages

A [quarantined](/white-label/trigger-inbox/what-is-trigger-inbox-api#message-lifecycle) message is no longer returned by lease calls but remains visible in list calls for inspection.

To find quarantined messages, list messages and filter by status:

<CodeGroup>
  ```bash cURL theme={null}
  curl -s "https://api.zapier.com/trigger-inbox/api/v1/inboxes/$INBOX_ID/messages" \
    -H "Authorization: Bearer $TOKEN" \
    | python3 -c "
  import sys, json
  msgs = json.load(sys.stdin)['results']
  quarantined = [m for m in msgs if m['status'] == 'quarantined']
  print(json.dumps(quarantined, indent=2))
  "
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(
    `https://api.zapier.com/trigger-inbox/api/v1/inboxes/${INBOX_ID}/messages`,
    { headers: { Authorization: `Bearer ${TOKEN}` } }
  );
  const { results } = await res.json();
  const quarantined = results.filter((m: { status: string }) => m.status === "quarantined");
  console.log(quarantined);
  ```

  ```python Python theme={null}
  resp = requests.get(
      f"https://api.zapier.com/trigger-inbox/api/v1/inboxes/{INBOX_ID}/messages",
      headers=headers,
  )
  quarantined = [m for m in resp.json()["results"] if m["status"] == "quarantined"]
  print(quarantined)
  ```
</CodeGroup>

A message is quarantined after it is leased 5 times without being acknowledged, so the cause is almost always on the consuming side: your processing repeatedly fails before it can acknowledge the message.

To investigate, check `message_attributes.error_message` and your application logs from around the lease attempts. Fix the processing issue so future messages are acknowledged normally.

Quarantined messages are not automatically removed and are not redelivered. They remain visible in `list` calls for inspection (metadata only, not the payload).

***

## Handle error messages and possible duplicates

### Error messages

When Zapier encounters a problem fetching full event data from the connected app, it still delivers the message with a partial payload. Check `message_attributes.error_message` before processing:

```json theme={null}
{
  "id": "019ce2fe-5ed1-7318-8a60-33dffd911f6b",
  "status": "leased",
  "payload": { "key": "value" },
  "message_attributes": {
    "lease_count": 1,
    "error_message": "Your Slack account on Zapier is expired/invalid. Please reconnect it to fix this.",
    "possible_duplicate_data": false
  }
}
```

If `error_message` is set, the payload may be incomplete. Acknowledge the message to remove it from the queue, or release it if you want to retry after addressing the underlying issue (for example, reconnecting the app account). For API-level errors and inbox lifecycle failures, go to [Trigger Inbox errors](/white-label/trigger-inbox/errors).

### Possible duplicates

When `possible_duplicate_data` is `true`, the message may contain event data that was already delivered under a different deduplication key. This can occur after a trigger updates how it identifies unique events.

Apply your own deduplication logic when this flag is set. For example, check a unique field in the payload against your data store before writing.

***

## Next steps

* [Manage your inbox](/white-label/trigger-inbox/manage-your-inbox): pause, resume, update, and delete inboxes.
* [Trigger Inbox API errors](/white-label/trigger-inbox/errors).
* [Trigger Inbox API reference](/white-label/api-reference/inboxes/list-inboxes).
