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

# Media

> Upload once, send many times — multipart, URL import, and Meta's 30-day expiry.

Media is uploaded before it is sent. The resulting id can be reused across messages until Meta's copy expires.

Unlike social APIs that give you a presigned URL and leave you talking to object storage, LazyApp uploads into **WhatsApp Cloud API** for you. You never hold a Meta access token or proxy bytes to Graph.

## Upload flow

**1. Upload the file**

Multipart to `POST /v1/media`, or JSON to `POST /v1/media/url` when the file already has a public URL.

**2. Keep the media id**

The response includes LazyApp's `uuid` and Meta's `provider_media_id`. Either works as `media_id` when sending.

**3. Send with it**

Pass `media_id` on `POST /v1/messages` for `image`, `video`, `audio`, `document`, or `sticker`.

```bash Multipart theme={null}
curl https://lazyapp.in/v1/media \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -F "file=@receipt.pdf" \
  -F "retention=managed"
```

```bash From a URL theme={null}
curl https://lazyapp.in/v1/media/url \
  -H "Authorization: Bearer $LAZYAPP_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://acme.example/receipts/1042.pdf", "filename": "receipt.pdf"}'
```

The URL form is usually what you want: LazyApp fetches the file and uploads it to Meta, so you do not proxy bytes through your own servers.

Every upload is virus-scanned and MIME-checked. Images are optimised and a thumbnail is generated when enabled.

Identical bytes in the same workspace and mode reuse the existing asset when Meta's copy is still valid — a retry of the same file cannot create a duplicate row.

## Supported formats

Default limit is **16 MB** per file (`MEDIA_MAX_SIZE_KB`). Meta may still refuse a type or size that WhatsApp does not accept for that message kind even when LazyApp accepted the upload.

| Kind | Typical MIME types |
| - | - |
| Images | `image/jpeg`, `image/png`, `image/webp` |
| Video | `video/mp4`, `video/3gpp` |
| Audio | `audio/aac`, `audio/amr`, `audio/mpeg`, `audio/mp4`, `audio/ogg` |
| Documents | `application/pdf`, Office formats, `text/plain` |

## Meta expires media after about 30 days

This catches people out. Meta's copy of an uploaded file is temporary — roughly 30 days — so a `media_id` you stored last quarter will not send today.

`provider_expires_at` tells you when Meta's copy goes. `retention` decides whether LazyApp keeps its own:

| Retention | Behaviour |
| - | - |
| `provider_only` | Nothing kept by LazyApp. Cheapest, and gone when Meta's copy expires |
| `managed` | LazyApp keeps a copy for a configured window and can re-upload |
| `customer_storage` | Stored in your own object storage |
| `no_retention` | Bytes discarded immediately after sending |

For anything you will resend — a catalogue, a logo, a standard form — use `managed`.

See [Media & limits](https://docs.lazyapp.in/reference/media-and-limits.md) for MIME allow-list, Meta error codes, and messaging caps.

## Sending it

```json theme={null}
{
  "type": "document",
  "to": "+919812345670",
  "media_id": "1234567890",
  "filename": "receipt-1042.pdf",
  "caption": "Your receipt"
}
```

You can also pass `media_url` on some sends so LazyApp imports and uploads in one step — prefer an explicit upload when you will reuse the same file.

## Access history

Every read, upload and delete is recorded with who did it, when, and from which IP:

```json theme={null}
{
  "uuid": "9f8b1c2d-...",
  "access_count": 3,
  "last_accessed_at": "2026-08-19T11:42:07+00:00",
  "access_history": [
    { "action": "downloaded", "actor": "Asha Rao", "ip": "203.0.113.7", "at": "2026-08-19T11:42:07+00:00" },
    { "action": "uploaded", "actor": "API key: Fulfilment", "ip": "203.0.113.7", "at": "2026-08-18T09:10:00+00:00" }
  ]
}
```

The history outlives the asset, so deleting media does not erase the record of who deleted it — which is the entry you most want when someone asks.

## Inbound media

When a contact sends you a file, the `message.received` webhook carries a media id. LazyApp downloads and retains it according to your retention policy, so you are not racing Meta's expiry to fetch it.

Download the bytes with [`GET /v1/messages/{message}/media`](https://docs.lazyapp.in/api-reference/messages/download-inbound-message-media), where `{message}` is the message uuid from the webhook. The response is the raw file. If the download is still queued, you get `409` — retry after a short pause rather than treating it as missing.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.