MsgBubblesDocs

Attachments

Sending media is two steps: upload the bytes to get an attachment id, then reference that id when sending a message. Oversized images and video are transcoded down automatically to fit the channel’s limits (SMS/MMS caps are far smaller than iMessage’s).

1. Upload

POST/v1/attachments

multipart/form-data with one or more file parts (up to 10 per request):

Upload a file
curl -X POST https://api.msgbubbles.com/v1/attachments \
  -H "Authorization: Bearer sk_live_…" \
  -F "file=@/path/to/photo.jpg"
201 Created
{
  "data": [
    {
      "id": "3f8a2c41-…",
      "filename": "photo.jpg",
      "mime_type": "image/jpeg",
      "size_bytes": 84213,
      "url": "https://…signed…",
      "message_id": null,
      "created_at": "2026-06-11T18:21:04.000Z"
    }
  ],
  "request_id": "req_…"
}

Large files: presigned direct upload

POST/v1/attachments/presign

For big media (long videos), skip proxying bytes through the API: request an upload target, then PUT the file straight to storage.

Request an upload URL
curl -X POST https://api.msgbubbles.com/v1/attachments/presign \
  -H "Authorization: Bearer sk_live_…" \
  -H "content-type: application/json" \
  -d '{ "filename": "demo.mp4", "content_type": "video/mp4" }'
201 Created — then PUT the bytes
{
  "data": {
    "id": "9b1d4e72-…",
    "upload_url": "https://…signed-put…",
    "method": "PUT",
    "headers": { "content-type": "video/mp4" }
  },
  "request_id": "req_…"
}

2. Send with the attachment

Pass the ids as attachment_ids on POST /v1/messages. An attachment id is single-use: once a message claims it, reusing it returns 400 invalid_attachments.

Send media
curl -X POST https://api.msgbubbles.com/v1/messages \
  -H "Authorization: Bearer sk_live_…" \
  -H "content-type: application/json" \
  -d '{
    "to": "+15555550123",
    "from_handle": "+18005551111",
    "text": "check this out",
    "attachment_ids": ["3f8a2c41-…"]
  }'

Voice notes

Set "voice_memo": true on the send and audio attachments render as native voice-note bubbles (iMessage voice memo / WhatsApp voice message) with a play button and duration, instead of a generic audio file.

Downloading media

GET/v1/attachments/:id

Returns the attachment’s metadata plus a short-lived signed url (about an hour). URLs expire — re-fetch the attachment when you need a fresh one rather than storing the link. Inbound message media (photos people send you) arrives the same way: the message.received webhook flags has_attachments, and the message object lists attachment ids to fetch.