> ## 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.

# Attachments

> Upload media, then send it as a message part.

Attachments use a two-step upload flow:

1. Create an upload slot.
2. PUT the bytes to `upload_url`.
3. Send a chat message with a `media` part through `POST /api/v1/chats/{id}/messages`.

## 1. Create an upload slot

```bash theme={null}
curl -X POST https://console.trychert.com/api/v1/attachments \
  -H "Authorization: Bearer $CHERT_SIGNING_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "filename": "invoice.pdf",
    "content_type": "application/pdf",
    "size_bytes": 204800
  }'
```

```json theme={null}
{
  "attachment_id": "9f3b27a8c14d4a6f8e2b91d4c5e6f7a8",
  "upload_url": "https://console.trychert.com/api/v1/attachments/9f3b27a8c14d4a6f8e2b91d4c5e6f7a8/upload",
  "required_headers": {
    "authorization": "Bearer <your-signing-secret>",
    "content-type": "application/pdf"
  },
  "expires_at": "2026-05-02T18:15:00Z"
}
```

## 2. Upload bytes

```bash theme={null}
curl -X PUT "$UPLOAD_URL" \
  -H "Authorization: Bearer $CHERT_SIGNING_SECRET" \
  -H "Content-Type: application/pdf" \
  --data-binary @invoice.pdf
```

HMAC is not supported on the binary upload route. Use bearer auth.

<Note>
  Single PUTs accept up to **100 MB** per file. Chert streams the body straight through to storage — there is no in-memory buffering bottleneck, so large uploads scale with your network throughput. Set `Content-Length` on the PUT so we can fast-reject a body that exceeds the size you registered.
</Note>

## 3. Send the attachment

```bash theme={null}
curl -X POST "https://console.trychert.com/api/v1/chats/CHAT_ID/messages" \
  -H "Authorization: Bearer YOUR_SIGNING_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "message": {
      "parts": [
        { "type": "text",  "value": "See attached." },
        { "type": "media", "attachment_id": "att_abc..." }
      ]
    }
  }'
```

## Send multiple images as one bubble

For multiple uploaded media parts that should arrive as **one grouped delivery** rather than N back-to-back bubbles, send a normal chat message and set `parallel_attachments: true`. Chert requests grouped delivery; Apple devices decide the final collage, stack, or attachment presentation.

The shape is the same `parts[]` you use for every other chat send. The only difference is the top-level `parallel_attachments: true` flag.

```bash theme={null}
curl -X POST "https://console.trychert.com/api/v1/chats/$CHAT_ID/messages" \
  -H "Authorization: Bearer $CHERT_SIGNING_SECRET" \
  -H "Content-Type: application/json" \
  -d '{
    "parallel_attachments": true,
    "message": {
      "parts": [
        { "type": "media", "attachment_id": "att_aaa..." },
        { "type": "media", "attachment_id": "att_bbb..." },
        { "type": "media", "attachment_id": "att_ccc..." }
      ]
    }
  }'
```

```json theme={null}
{
  "message": {
    "id":           "46eb1003c8b54b7ea8f1c2b03e9a7d12",
    "chat_id":      "0f2d3c1a-8b4e-4f6a-90d2-1a3b4c5d6e7f",
    "status":       "sent",
    "delivered_at": "2026-05-27T20:18:28.857Z"
  }
}
```

You can include `parallel_attachments` on the first touch too — `POST /api/v1/chats` accepts it inside `message`.

### Rules

| Rule                           | Value                                                                                          |
| ------------------------------ | ---------------------------------------------------------------------------------------------- |
| Count                          | 2 or more media parts                                                                          |
| Supported image types          | `image/jpeg`, `image/png`, `image/gif`, `image/webp`, `image/heic`, `image/heif`               |
| Per-file size                  | 100 MB max for registered uploads                                                              |
| Rendering                      | Chert requests grouped delivery. Apple devices decide the final collage or stack presentation. |
| 1 image                        | Drop `parallel_attachments` — a single media part renders as a normal image bubble.            |
| Large batches                  | Split into multiple sends when you want predictable recipient UX.                              |
| Mixed media (image + video)    | Delivery is provider/device-dependent. Send separately when presentation matters.              |
| Optional caption               | A `text` part is sent as its own text bubble; same-bubble captions are not guaranteed.         |
| Without `parallel_attachments` | Multiple `media` parts render as N separate bubbles.                                           |

### Errors

| Code   | HTTP | When                                               |
| ------ | ---- | -------------------------------------------------- |
| `1003` | 400  | Missing required field.                            |
| `1002` | 400  | Invalid part shape or MIME mismatch.               |
| `3002` | 503  | No eligible sender line available in your project. |
| `4001` | 502  | Delivery failed downstream.                        |

## Supported uploads

| Type      | MIME examples                                                                    |
| --------- | -------------------------------------------------------------------------------- |
| Images    | `image/jpeg`, `image/png`, `image/gif`, `image/heic`, `image/heif`, `image/webp` |
| Video     | `video/mp4`, `video/quicktime`                                                   |
| Audio     | `audio/mpeg`, `audio/m4a`, `audio/wav`, `audio/aac`                              |
| Documents | `application/pdf`                                                                |

The public API validates the declared file size and rejects unsupported MIME types. Upload slots expire after 15 minutes.

## Metadata and download

Use `GET /api/v1/attachments/{id}` to check the registered attachment.

```json theme={null}
{
  "attachment": {
    "attachment_id": "9f3b27a8c14d4a6f8e2b91d4c5e6f7a8",
    "content_type": "application/pdf",
    "size_bytes": 204800,
    "filename": "invoice.pdf",
    "uploaded_at": "2026-05-02T18:02:00Z",
    "expires_at": "2026-05-02T18:15:00Z"
  }
}
```

Use `GET /api/v1/attachments/{id}/content` to stream the binary content. This works for attachments you uploaded and for inbound media ids received in `message.received` webhooks.

```bash theme={null}
curl "https://console.trychert.com/api/v1/attachments/$ATTACHMENT_ID/content" \
  -H "Authorization: Bearer $CHERT_SIGNING_SECRET" \
  -H "x-chert-tenant: $SLUG" \
  --output attachment.bin
```

Inbound images and files arrive in `data.message.parts[]` as `media` parts:

```json theme={null}
{
  "type": "media",
  "attachment_id": "in_536f2fe3cafef93946d0bf9b4ac2e3ef",
  "filename": "photo.jpg",
  "mime_type": "image/jpeg",
  "size_bytes": 790394
}
```

## Caveats

| Caveat                                | What to do                                                                                               |
| ------------------------------------- | -------------------------------------------------------------------------------------------------------- |
| Group create with media or rich links | Create the group with text first, then send media or rich links with `POST /api/v1/chats/{id}/messages`. |

## See also

* [Usage examples](/api/usage-examples)
* [Create a chat](/api/chats/create)
