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

# Edit a message

> POST /api/v1/messages/:id/edit — edit a previously-sent outbound message within Apple's 15-minute window.

<Note>
  **Auth.** HMAC v1 or bearer token — see [Authentication](/api/authentication).
  **Status.** Live · **Idempotent:** No (each call posts a fresh edit; pass `new_text` you want the bubble to read).
</Note>

Replaces the text of a previously-sent outbound message. The recipient sees the bubble update in place with an "Edited" marker, matching native iMessage behavior. Apple enforces a **15-minute window from send time** — edits attempted after the window return an error.

### Which id goes in the URL

This is a message-scoped route: `{id}` is a `message_id`, not a `chat_id`. Prefer the Chert-issued **`message_id`** returned by [`POST /api/v1/send`](/api/sending), or the `message.id` returned by [`POST /api/v1/chats`](/api/chats/create) and [`POST /api/v1/chats/{id}/messages`](/api/chats/send-message). Chert resolves that id to the underlying iMessage identifier internally before applying the edit.

<Note>
  For outbound messages surfaced by [`GET /api/v1/chats/{id}/messages`](/api/chats/list-messages), the listed message `id` is also accepted.
</Note>

## Request

`POST /api/v1/messages/{id}/edit`

<RequestExample>
  ```bash cURL theme={null}
  curl -X POST https://console.trychert.com/api/v1/messages/msg_01H.../edit \
    -H "x-chert-tenant: $TENANT" \
    -H "x-chert-signature: v1,$TS,$SIG" \
    -H "content-type: application/json" \
    -d '{"new_text": "Updated copy — typo fix"}'
  ```

  ```js Node.js theme={null}
  const ts = Math.floor(Date.now() / 1000)
  const body = JSON.stringify({ new_text: "Updated copy — typo fix" })
  const sig = crypto.createHmac("sha256", SIGNING_SECRET).update(`${ts}.${body}`).digest("hex")
  const res = await fetch(
    `https://console.trychert.com/api/v1/messages/${messageId}/edit`,
    {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "x-chert-tenant": TENANT_SLUG,
        "x-chert-signature": `v1,${ts},${sig}`,
      },
      body,
    },
  )
  ```

  ```python Python theme={null}
  import hmac, hashlib, json, time, requests
  ts = int(time.time())
  body = json.dumps({"new_text": "Updated copy — typo fix"})
  sig = hmac.new(SIGNING_SECRET.encode(), f"{ts}.{body}".encode(), hashlib.sha256).hexdigest()
  r = requests.post(
      f"https://console.trychert.com/api/v1/messages/{message_id}/edit",
      headers={
          "Content-Type": "application/json",
          "x-chert-tenant": TENANT_SLUG,
          "x-chert-signature": f"v1,{ts},{sig}",
      },
      data=body,
  )
  ```
</RequestExample>

## Response

<ResponseExample>
  ```json 200 OK theme={null}
  {
    "status": "edited",
    "message_id": "msg_01H...",
    "new_text": "Updated copy — typo fix",
    "date_edited": "2026-05-27T16:42:11.318Z",
    "edit_history": null
  }
  ```
</ResponseExample>

### Path parameters

<ParamField path="id" type="string" required>
  The Chert `message_id` returned by a send endpoint, or the outbound message `id` surfaced on chat-message listings.
</ParamField>

### Body

<ParamField body="new_text" type="string" required>
  The replacement bubble text. Empty strings are rejected.
</ParamField>

<ParamField body="edited_part_index" type="integer" default="0">
  For multi-part messages, the zero-based index of the text part to edit. Default `0` (the first text part). Most sends have only one text part.
</ParamField>

### Response fields

<ResponseField name="status" type="string">
  Always `"edited"` on success.
</ResponseField>

<ResponseField name="message_id" type="string">
  Echoed-back `message_id` of the edited bubble.
</ResponseField>

<ResponseField name="new_text" type="string">
  The text that now appears in the bubble.
</ResponseField>

<ResponseField name="date_edited" type="string">
  ISO-8601 timestamp the edit was applied.
</ResponseField>

<ResponseField name="edit_history" type="array | null">
  Reserved for future use. May surface previous versions of the bubble when the underlying transport reports them.
</ResponseField>

## Errors

| Code   | HTTP | When                                                                                 |
| ------ | ---- | ------------------------------------------------------------------------------------ |
| `1002` | 400  | `new_text` missing or empty.                                                         |
| `1004` | 400  | Invalid JSON body.                                                                   |
| `2003` | 404  | Message not found, or not owned by this tenant.                                      |
| `4001` | 502  | Delivery failed — edit window may have expired, or the bubble is no longer editable. |
| `4099` | 501  | Edit is not available on this phone line.                                            |

See the full [error code reference](/api/errors).

## Notes

| Behavior             | What it means                                                                            |
| -------------------- | ---------------------------------------------------------------------------------------- |
| 15-minute window     | Apple-enforced. Edits attempted past 15 minutes from the original send fail with `4001`. |
| Outbound only        | You can only edit messages **you** sent. Inbound messages cannot be edited.              |
| Recipient experience | The bubble updates in place with an "Edited" marker. No new bubble is created.           |

## See also

* [React to a message](/api/messages/react)
* [Send a message](/api/sending)
