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

# Updating messages

> Change a message after you send it — progress bars, and safe retries.

You can rewrite a message after you send it. This powers progress bars (queued, then 40%, then done), and it makes retries harmless.

An update always **replaces the whole block list**. There is no merging and no partial patch. Send the full message again each time.

## Give the message a mid

A `mid` is your own name for a message. It must be unique inside the conversation. Set one, and you can point at that message later.

<ParamField body="mid" type="string">
  Must match `^[a-zA-Z0-9_.:-]{1,64}$`, and be unique per conversation.
</ParamField>

## Send the same mid again to update

POST to the `response_url` again with the same `mid`, and the message is **replaced**, not copied. The response tells you which one happened:

```json theme={null}
{
  "ok": true,
  "messages": [{ "message_id": "0f8c", "mid": "job-42", "updated": true }]
}
```

<Tip>
  This is the fix for double messages. If Make or n8n retries a step, the same `mid` updates instead of posting twice. Put a `mid` on anything you would hate to see doubled.
</Tip>

## A progress loop

Three POSTs to the same `response_url`, all using `mid: "job-42"`. The visitor sees one bubble change.

<CodeGroup>
  ```json 1. Queued theme={null}
  {
    "messages": [
      {
        "mid": "job-42",
        "blocks": [{ "type": "status", "state": "pending", "label": "Queued" }]
      }
    ]
  }
  ```

  ```json 2. Working theme={null}
  {
    "messages": [
      {
        "mid": "job-42",
        "blocks": [
          {
            "type": "status",
            "state": "in_progress",
            "label": "Generating your report",
            "progress": 40,
            "detail": "Crunching 3,200 rows"
          }
        ]
      }
    ]
  }
  ```

  ```json 3. Done theme={null}
  {
    "messages": [
      {
        "mid": "job-42",
        "blocks": [
          { "type": "status", "state": "success", "label": "Report ready" },
          { "type": "file", "file_id": "6f1c2a", "name": "june-report.pdf" }
        ]
      }
    ]
  }
  ```
</CodeGroup>

<Note>
  Pure status and progress changes do not mark the message as edited. Changing other blocks does.
</Note>

## Updating after the 45-minute window

Once the token expires, use an API key. `PATCH` targets a message by id, with no time limit:

```text theme={null}
PATCH https://app.my-aichatbot.com/api/v1/messages/{message_id}
Authorization: Bearer mac_live_
```

<CodeGroup>
  ```bash curl theme={null}
  curl -X PATCH "https://app.my-aichatbot.com/api/v1/messages/$MESSAGE_ID" \
    -H "Authorization: Bearer $MAC_KEY" \
    -H "Content-Type: application/json" \
    -d '{ "blocks": [{ "type": "status", "state": "success", "label": "Done" }] }'
  ```

  ```javascript Node.js theme={null}
  await fetch(`https://app.my-aichatbot.com/api/v1/messages/${messageId}`, {
    method: 'PATCH',
    headers: {
      Authorization: `Bearer ${process.env.MAC_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      blocks: [{ type: 'status', state: 'success', label: 'Done' }]
    })
  })
  ```

  ```python Python theme={null}
  requests.patch(
      f"https://app.my-aichatbot.com/api/v1/messages/{message_id}",
      headers={"Authorization": f"Bearer {os.environ['MAC_KEY']}"},
      json={"blocks": [{"type": "status", "state": "success", "label": "Done"}]},
  )
  ```

  ```php PHP theme={null}
  $ch = curl_init("https://app.my-aichatbot.com/api/v1/messages/{$messageId}");
  curl_setopt_array($ch, [
      CURLOPT_CUSTOMREQUEST => 'PATCH',
      CURLOPT_HTTPHEADER => [
          'Authorization: Bearer ' . getenv('MAC_KEY'),
          'Content-Type: application/json',
      ],
      CURLOPT_POSTFIELDS => json_encode([
          'blocks' => [['type' => 'status', 'state' => 'success', 'label' => 'Done']],
      ]),
      CURLOPT_RETURNTRANSFER => true,
  ]);
  curl_exec($ch);
  ```

  ```go Go theme={null}
  body, _ := json.Marshal(map[string]any{
      "blocks": []map[string]string{
          {"type": "status", "state": "success", "label": "Done"},
      },
  })

  req, _ := http.NewRequest("PATCH",
      "https://app.my-aichatbot.com/api/v1/messages/"+messageID,
      bytes.NewReader(body))
  req.Header.Set("Authorization", "Bearer "+os.Getenv("MAC_KEY"))
  req.Header.Set("Content-Type", "application/json")

  resp, err := http.DefaultClient.Do(req)
  ```
</CodeGroup>

```json Response theme={null}
{ "ok": true, "message_id": "0f8c" }
```

`PATCH` takes **exactly one** message. Sending more than one returns `400 AMBIGUOUS_UPDATE_TARGET`.

## Rules

| Rule                   | Detail                                                                |
| ---------------------- | --------------------------------------------------------------------- |
| Bot messages only      | Editing a visitor or operator message returns `403 not_bot_authored`. |
| 60 updates per message | Update number 61 returns `429 UPDATE_LIMIT_REACHED`.                  |
| Full replacement       | Blocks you leave out are removed. Send everything again.              |
| Checked every time     | Each update runs the full Block Kit checks.                           |
| Frozen after takeover  | `409 conversation_taken_over`.                                        |

## Errors

| Status | Code                      | Meaning                                        |
| ------ | ------------------------- | ---------------------------------------------- |
| `400`  | `AMBIGUOUS_UPDATE_TARGET` | `PATCH` got more than one message.             |
| `403`  | `not_bot_authored`        | The target is not a bot message.               |
| `404`  | `message_not_found`       | Unknown message, or it belongs to another bot. |
| `409`  | `conversation_taken_over` | A human took over.                             |
| `429`  | `UPDATE_LIMIT_REACHED`    | Past 60 updates on this message.               |
