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
}))
whatsapp.access.token
Types
UploadMediaRequest
UploadMediaRequest type {
phone_number_id: Str?,
content: Bytes | Str,
mime_type: Str,
filename: Str?
}