Media management: upload (multipart, binary-safe), look up, download (as Bytes), and delete. Uploaded media persists for 30 days unless deleted; media URLs from get-media expire after 5 minutes.

Functions

build-upload-body

fn (boundary: Str, content: Bytes | Str, mime-type: Str, filename: Str): Bytes

Build the multipart/form-data body for a media upload as Bytes: messaging_product=whatsapp, type={mime-type}, and the file part. The text parts and the content are concatenated as Bytes, so binary content (images, audio) is never reinterpreted as text. content may be Bytes or Str (encoded as UTF-8). Exposed for testing.

delete-media

fn (media-id: Str): Map
fn (media-id: Str, opts: Map): Map
fn (media-id: Str, phone-number-id: Str): Map
fn (media-id: Str, phone-number-id: Str, opts: Map): Map

DELETE /{media-id} - Delete uploaded media; returns {success: true}. The 2-arity form passes phone_number_id so only media owned by that number is deleted.

Example

::media ::whatsapp::media

::media/delete-media("1234567890")

download-media

fn (media-url: Str): Bytes
fn (media-url: Str, opts: Map): Bytes

Download media content from a URL returned by get-media. Returns the raw file as Bytes (binary-safe), or an Err carrying HttpError. A 404 usually means the URL expired: call get-media again for a fresh one.

Example

::media ::whatsapp::media

info ::media/get-media("1234567890")
bytes ::media/download-media(info.url)
length(bytes) // the file size in bytes

download-media-by-id

fn (media-id: Str): Map
fn (media-id: Str, opts: Map): Map

Look up a media id and download it in one step. Returns {bytes, mime_type, sha256, file_size, id}, or the first Err. The usual way to fetch media from an inbound webhook message (message.image.id, ...).

get-media

fn (media-id: Str): Map
fn (media-id: Str, opts: Map): Map
fn (media-id: Str, phone-number-id: Str): Map
fn (media-id: Str, phone-number-id: Str, opts: Map): Map

GET /{media-id} - Media metadata: url (valid for 5 minutes; download it with download-media), mime_type, sha256, file_size, id. The 2-arity form passes phone_number_id, so the call only succeeds when the media belongs to that business number.

Example

::media ::whatsapp::media

info ::media/get-media("1234567890")
info.mime_type // => "image/jpeg"

media-url

fn (media-id: Str, phone-number-id: Str?): Str
fn (media-id: Str, phone-number-id: Str?, opts: Map): Str

The URL for a media id, with the optional phone_number_id ownership check. Exposed for testing.

upload-media

fn (request: UploadMediaRequest): Map
fn (request: UploadMediaRequest, opts: Map): Map

POST /{phone-number-id}/media - Upload media for later sending; returns {id}. Pass the id as image_id, document_id, audio_id, video_id, or sticker_id to a send function. content is the file as Bytes (e.g. from download-media, an HTTP response's body-bytes, or a file read) or a Str for text files. filename defaults to "upload".

Limits: image (JPEG, PNG) 5 MB; video (MP4, 3GPP) and audio (AAC, AMR, MP3, MP4, OGG/OPUS) 16 MB; document 100 MB; sticker (WebP) 100 KB static / 500 KB animated.

Example

::media ::whatsapp::media
::messages ::whatsapp::messages

photo ::hot::http/get("https://example.com/photo.png").body-bytes
uploaded ::media/upload-media(::media/UploadMediaRequest({
  phone_number_id: "109876543210",
  content: photo,
  mime_type: "image/png",
  filename: "photo.png"
}))
::messages/send-image(::messages/SendImageRequest({
  phone_number_id: "109876543210",
  to: "15551234567",
  image_id: uploaded.id
}))
Context Vars: whatsapp.access.token

Types

UploadMediaRequest

UploadMediaRequest type {
    phone_number_id: Str?,
    content: Bytes | Str,
    mime_type: Str,
    filename: Str?
}