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

# Expiring messages

> Retire a message when it stops being true: after a time, at a time, or when an event says so. Replace it with a notice or delete it.

> [!NOTE]
>
> Check [environment and feature availability](https://docs.lithoblocks.com/guides/availability.md) before following this guide.

Some messages should not outlive their moment: a one-time code, an offer that closes, an incident
that resolves, an approval that has been decided. An expiring message is replaced with a notice
that it expired, or deleted. Its buttons stop working the moment the deadline passes.

Expiry is free. It runs about once a minute, so a lifetime is at least 60 seconds.

## When it expires, what happens

| | Replace (default) | Delete |
| - | - | - |
| In Slack | The message becomes a notice, "This message expired *date*.", or the template's own notice. Slack shows it as edited. | The message is deleted. Replies in its thread stay, under Slack's "This message was deleted". |
| Buttons | Gone. A click that arrives after the deadline is answered "This message has expired" and does nothing. | Gone. |
| LithoBlocks' copy | Cleared, unless the expiry keeps it (`retain_content: true`). The notice is kept. | Cleared, unless kept. |

## Expire a message when you send it

Add `expiration` to [`/v1/templates/send`](https://docs.lithoblocks.com/guides/send-with-api) or `/v1/templates/send-bulk`:

```json theme={null}
{
  "template_id": "YOUR_TEMPLATE_ID",
  "channel_id": "C0AS92CQ04D",
  "data": { "code": "418-552" },
  "reference": "login-418552",
  "expiration": { "after_seconds": 600 }
}
```

| Field | Meaning |
| - | - |
| `after_seconds` | Seconds after delivery: at least 60, at most 366 days. In a bulk send each destination's message counts from its own delivery. |
| `at` | An absolute time instead, at least 60 seconds away. |
| `mode` | `replace` (default) or `delete`. |
| `retain_content` | `true` keeps LithoBlocks' copy of what the message said. By default it's cleared. |

The response adds `expires_at`. Expiring at send time needs only `messages:create`.

Scheduled messages take the same `expiration`. `after_seconds` counts from each delivery, so it
suits recurring messages. `at` must come after `send_at` and can't be combined with `recurrence`;
an occurrence that would post after its `at` isn't sent at all. See
[Schedule a message](https://docs.lithoblocks.com/guides/schedule-a-message.md).

## Expire every message from a template

A template version can carry an expiry policy, which applies to every send of it: the API, the
web app, `/lb-send`, scheduled messages and batch digests. In the builder, open the **gear** next
to the template's name and choose **Expiry**. Over the API, pass `expiration` when you create the
template or a version:

```json theme={null}
{
  "name": "Spring offer",
  "blocks": [{ "type": "section", "text": { "type": "mrkdwn", "text": "Use *{{code}}* for 20% off until Friday." } }],
  "expiration": {
    "default": { "after_seconds": 86400 },
    "max_after_seconds": 604800,
    "notice": {
      "blocks": [{ "type": "section", "text": { "type": "mrkdwn", "text": ":hourglass: Offer *{{code}}* has closed. <https://example.com/offers|See current offers>" } }],
      "text": "Offer {{code}} has closed."
    }
  }
}
```

* `default` applies to a send that names no expiry.
* `max_after_seconds` is the longest a message may live. A send asking for longer is refused with
  `422 expiration_exceeds_maximum` before anything is posted or charged. With no `default`, the
  maximum becomes the default, so no message from this template outlives it.
* `notice` replaces the system notice. It's filled in with the send's data when the message is
  sent, and its placeholders count in the template's request format. It can't hold anything a
  reader could click or fill in.

The policy is part of the version, like the blocks: it goes live when the version does.

## Expire a message later

```bash theme={null}
# Set or change when it expires
curl -X PUT https://api.lithoblocks.com/v1/messages/MESSAGE_ID/expiration \
  -H "Authorization: Bearer $LITHOBLOCKS_API_KEY" -H "Content-Type: application/json" \
  -d '{ "after_seconds": 3600, "mode": "replace" }'

# Remove its expiry
curl -X DELETE https://api.lithoblocks.com/v1/messages/MESSAGE_ID/expiration \
  -H "Authorization: Bearer $LITHOBLOCKS_API_KEY"
```

In the web app, open the message in **Sent messages** and choose **Expire**.

## Expire it now, when something happens

This is the hook for "expire when the incident resolves": the automation that knows calls it.

```bash theme={null}
# One message: replaced (or deleted) before this returns
curl -X POST https://api.lithoblocks.com/v1/messages/MESSAGE_ID/expire \
  -H "Authorization: Bearer $LITHOBLOCKS_API_KEY" -H "Content-Type: application/json" -d '{}'

# Every live message with a reference: their buttons stop at once, and each is replaced within about a minute
curl -X POST https://api.lithoblocks.com/v1/messages/expire \
  -H "Authorization: Bearer $LITHOBLOCKS_API_KEY" -H "Content-Type: application/json" \
  -d '{ "reference": "incident-2041" }'
```

Expiring by reference acts on at most 100 messages per call. A member's call leaves alone the
messages others sent and says how many in `not_permitted`. If Slack refuses an expire-now for the
moment, the expiry stays due and is retried within minutes. Its buttons already don't work.

These need `messages:update`, with the same rules for who may change which message as
[correcting](https://docs.lithoblocks.com/guides/correct-and-delete-messages.md#who-can-change-what). Over MCP they're
`set_message_expiration`, `cancel_message_expiration`, `expire_message` and
`expire_messages_by_reference`.

## What expiry can't do

Expiry removes the message from Slack and, by default, LithoBlocks' copy. It can't take back
what has already been seen:

* people who read it, and push or email notifications already delivered;
* AI tools that already read the channel;
* your workspace's retention settings, exports and legal holds;
* thread replies that quoted it.

Don't put secrets in a message's notification text: it's what notifications show. Expiry is
the right tool when a message must stop being *current*. It doesn't make something that was once
shown confidential.


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