> For the complete documentation index, see [llms.txt](https://docs.zenfinder.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.zenfinder.ai/reference/media.md).

# Media

Upload, retrieve and delete media attachments.

## Upload media

> Multipart upload. Stored for 30 days; returns an opaque \`MEDIA\_ID\` to\
> reference from a subsequent \`image\`/\`document\` message send.<br>

```json
{"openapi":"3.0.3","info":{"title":"ZenFinder Business API","version":"1.0"},"tags":[{"name":"Media","description":"Upload, retrieve and delete media attachments."}],"servers":[{"url":"https://api.zenfinder.ai/business/v1.0","description":"Production"}],"security":[{"bearerAuth":["zenfinder_business_messaging"]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"zf_live_...","description":"API key minted from Business Manager, sent as `Authorization: Bearer\n<key>`. Scopes: `zenfinder_business_messaging`,\n`zenfinder_business_management`.\n"}},"parameters":{"PhoneNumberId":{"name":"phoneNumberId","in":"path","required":true,"description":"The sender's opaque `PHONE_NUMBER_ID` (never the MSISDN itself).","schema":{"type":"string"}}},"responses":{"BadRequest":{"description":"Invalid, missing, or semantically rejected parameters (codes `100`, `131008`, `131009`, `131021`, `131026`, `131037`, `131047`–`131053`, `132000`–`132016`, `133010`, etc. — see `Error` schema).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing, malformed, revoked or expired Bearer token (codes `0`, `190`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Token lacks the required scope, or isn't authorised for this sender/ZBA/media (codes `3`, `10`, `200`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"DefaultError":{"description":"Unexpected error (code `131000`) or upstream unavailability (`131016`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}},"schemas":{"Error":{"type":"object","description":"Graph/WhatsApp-Cloud-API-compatible error envelope. Numeric `code`\nvalues are reused verbatim from the WhatsApp Cloud API:\n\n| Code | HTTP | Meaning |\n|---|---|---|\n| 0 | 401 | AuthException |\n| 3 | 403 | Capability or permissions issue |\n| 10 | 403 | Permission denied |\n| 100 | 400 | Invalid parameter |\n| 190 | 401 | Access token invalid or expired |\n| 200 | 403 | Permission denied (missing scope / not authorised for this resource) |\n| 130429 | 429 | Rate limit hit |\n| 131000 | 500 | Something went wrong |\n| 131008 | 400 | Required parameter is missing |\n| 131009 | 400 | Parameter value is not valid |\n| 131016 | 503 | Service unavailable |\n| 131021 | 400 | Recipient cannot be sender |\n| 131026 | 400 | Message undeliverable |\n| 131037 | 403 | Display name not approved |\n| 131042 | 402 | Business eligibility payment issue (insufficient wallet balance) |\n| 131047 | 400 | Re-engagement message (service window closed — send a template) |\n| 131048 | 400 | Spam rate limit hit |\n| 131049 | 400 | Per-user marketing message limit reached |\n| 131050 | 400 | User stopped marketing messages (opted out) |\n| 131051 | 400 | Unsupported message type |\n| 131052 | 400 | Media download error |\n| 131053 | 400 | Media upload error |\n| 132000 | 400 | Template parameter count mismatch |\n| 132001 | 400 | Template does not exist / not approved |\n| 132005 | 400 | Template hydrated text too long |\n| 132007 | 400 | Template format character policy violated |\n| 132012 | 400 | Template parameter format mismatch |\n| 132015 | 400 | Template is paused |\n| 132016 | 400 | Template is disabled |\n| 133010 | 400 | Sender not registered |\n","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string"},"code":{"type":"integer"},"error_subcode":{"type":"integer","nullable":true},"error_data":{"type":"object","properties":{"messaging_product":{"type":"string"},"details":{"type":"string"}}},"zen_trace_id":{"type":"string"}}}}}}},"paths":{"/{phoneNumberId}/media":{"post":{"operationId":"uploadMedia","tags":["Media"],"summary":"Upload media","description":"Multipart upload. Stored for 30 days; returns an opaque `MEDIA_ID` to\nreference from a subsequent `image`/`document` message send.\n","parameters":[{"$ref":"#/components/parameters/PhoneNumberId"}],"requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["file","type"],"properties":{"messaging_product":{"type":"string","enum":["zenfinder","whatsapp"]},"file":{"type":"string","format":"binary"},"type":{"type":"string","enum":["image","video","audio","document","sticker"],"description":"Declared media type — validated against its own allowed\nMIME types and size cap: image (jpeg/png, 5MB), video\n(mp4/3gpp, 16MB), audio (aac/mp4/mpeg/amr/ogg, 16MB),\ndocument (pdf/word/excel/ppt/txt, 100MB), sticker (webp, 500KB).\n"}}}}}},"responses":{"200":{"description":"Uploaded","content":{"application/json":{"schema":{"type":"object","properties":{"id":{"type":"string","description":"Opaque MEDIA_ID"}}}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"default":{"$ref":"#/components/responses/DefaultError"}}}}}}
```

## Get media metadata + download URL, OR get analytics (overloaded)

> This single \`GET /{id}\` slot is overloaded, mirroring how the real\
> Graph API overloads \`GET /{id}\` across node types:\
> \
> \- \*\*No \`fields\` query param, or one it doesn't recognise\*\* → treats\
> &#x20; \`{id}\` as a \`MEDIA\_ID\` and returns media metadata + a 5-minute\
> &#x20; signed download URL. Requires the \`zenfinder\_business\_messaging\`\
> &#x20; scope.\
> \- \*\*\`fields=analytics|pricing\_analytics|template\_analytics\`\*\* →\
> &#x20; treats \`{id}\` as a \`ZBA\_ID\` (which must match the token's own ZBA)\
> &#x20; and returns aggregated BigQuery analytics for that field. Requires\
> &#x20; the \`zenfinder\_business\_management\` scope, and is subject to the\
> &#x20; management-endpoint rate limit.<br>

```json
{"openapi":"3.0.3","info":{"title":"ZenFinder Business API","version":"1.0"},"tags":[{"name":"Media","description":"Upload, retrieve and delete media attachments."},{"name":"Analytics","description":"Aggregated message, pricing and template performance data."}],"servers":[{"url":"https://api.zenfinder.ai/business/v1.0","description":"Production"}],"security":[{"bearerAuth":["zenfinder_business_messaging","zenfinder_business_management"]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"zf_live_...","description":"API key minted from Business Manager, sent as `Authorization: Bearer\n<key>`. Scopes: `zenfinder_business_messaging`,\n`zenfinder_business_management`.\n"}},"schemas":{"MediaGetResponse":{"type":"object","properties":{"messaging_product":{"type":"string"},"id":{"type":"string"},"url":{"type":"string","format":"uri","description":"Signed download URL, valid for 5 minutes."},"mime_type":{"type":"string"},"sha256":{"type":"string"},"file_size":{"type":"integer"}}},"AnalyticsResponse":{"type":"object","properties":{"analytics":{"type":"object","properties":{"granularity":{"type":"string","enum":["DAY","MONTH"]},"data_points":{"type":"array","items":{"type":"object","properties":{"start":{"type":"string","format":"date-time"},"sender_id":{"type":"string"},"country":{"type":"string"},"type":{"type":"string"},"sent":{"type":"integer"},"delivered":{"type":"integer"}}}}}}}},"PricingAnalyticsResponse":{"type":"object","properties":{"pricing_analytics":{"type":"object","properties":{"data_points":{"type":"array","items":{"type":"object","properties":{"category":{"type":"string","enum":["MARKETING","UTILITY","AUTHENTICATION"]},"country":{"type":"string"},"currency":{"type":"string"},"cost_minor":{"type":"integer"},"message_count":{"type":"integer"}}}}}}}},"TemplateAnalyticsResponse":{"type":"object","properties":{"template_analytics":{"type":"object","properties":{"data_points":{"type":"array","items":{"type":"object","properties":{"template_id":{"type":"string"},"sent":{"type":"integer"},"delivered":{"type":"integer"},"read":{"type":"integer"},"clicked":{"type":"integer","nullable":true,"description":"Always null — button/CTA click tracking isn't implemented yet."}}}}}}}},"Error":{"type":"object","description":"Graph/WhatsApp-Cloud-API-compatible error envelope. Numeric `code`\nvalues are reused verbatim from the WhatsApp Cloud API:\n\n| Code | HTTP | Meaning |\n|---|---|---|\n| 0 | 401 | AuthException |\n| 3 | 403 | Capability or permissions issue |\n| 10 | 403 | Permission denied |\n| 100 | 400 | Invalid parameter |\n| 190 | 401 | Access token invalid or expired |\n| 200 | 403 | Permission denied (missing scope / not authorised for this resource) |\n| 130429 | 429 | Rate limit hit |\n| 131000 | 500 | Something went wrong |\n| 131008 | 400 | Required parameter is missing |\n| 131009 | 400 | Parameter value is not valid |\n| 131016 | 503 | Service unavailable |\n| 131021 | 400 | Recipient cannot be sender |\n| 131026 | 400 | Message undeliverable |\n| 131037 | 403 | Display name not approved |\n| 131042 | 402 | Business eligibility payment issue (insufficient wallet balance) |\n| 131047 | 400 | Re-engagement message (service window closed — send a template) |\n| 131048 | 400 | Spam rate limit hit |\n| 131049 | 400 | Per-user marketing message limit reached |\n| 131050 | 400 | User stopped marketing messages (opted out) |\n| 131051 | 400 | Unsupported message type |\n| 131052 | 400 | Media download error |\n| 131053 | 400 | Media upload error |\n| 132000 | 400 | Template parameter count mismatch |\n| 132001 | 400 | Template does not exist / not approved |\n| 132005 | 400 | Template hydrated text too long |\n| 132007 | 400 | Template format character policy violated |\n| 132012 | 400 | Template parameter format mismatch |\n| 132015 | 400 | Template is paused |\n| 132016 | 400 | Template is disabled |\n| 133010 | 400 | Sender not registered |\n","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string"},"code":{"type":"integer"},"error_subcode":{"type":"integer","nullable":true},"error_data":{"type":"object","properties":{"messaging_product":{"type":"string"},"details":{"type":"string"}}},"zen_trace_id":{"type":"string"}}}}}},"responses":{"BadRequest":{"description":"Invalid, missing, or semantically rejected parameters (codes `100`, `131008`, `131009`, `131021`, `131026`, `131037`, `131047`–`131053`, `132000`–`132016`, `133010`, etc. — see `Error` schema).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Unauthorized":{"description":"Missing, malformed, revoked or expired Bearer token (codes `0`, `190`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Token lacks the required scope, or isn't authorised for this sender/ZBA/media (codes `3`, `10`, `200`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"RateLimited":{"description":"Management-endpoint rate limit reached (code `130429`). Retry after the number of seconds in the `Retry-After` header.","headers":{"Retry-After":{"schema":{"type":"integer"},"description":"Seconds until the rate-limit window resets."}},"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"DefaultError":{"description":"Unexpected error (code `131000`) or upstream unavailability (`131016`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/{id}":{"get":{"operationId":"getMediaOrAnalytics","tags":["Media","Analytics"],"summary":"Get media metadata + download URL, OR get analytics (overloaded)","description":"This single `GET /{id}` slot is overloaded, mirroring how the real\nGraph API overloads `GET /{id}` across node types:\n\n- **No `fields` query param, or one it doesn't recognise** → treats\n  `{id}` as a `MEDIA_ID` and returns media metadata + a 5-minute\n  signed download URL. Requires the `zenfinder_business_messaging`\n  scope.\n- **`fields=analytics|pricing_analytics|template_analytics`** →\n  treats `{id}` as a `ZBA_ID` (which must match the token's own ZBA)\n  and returns aggregated BigQuery analytics for that field. Requires\n  the `zenfinder_business_management` scope, and is subject to the\n  management-endpoint rate limit.\n","parameters":[{"name":"id","in":"path","required":true,"description":"A `MEDIA_ID` (media lookup) or a `ZBA_ID` (analytics lookup — must equal the token's own ZBA).","schema":{"type":"string"}},{"name":"fields","in":"query","description":"Omit for a media lookup; set to one of the three analytics fields for an analytics lookup.","schema":{"type":"string","enum":["analytics","pricing_analytics","template_analytics"]}},{"name":"start","in":"query","description":"Analytics only. Range start: unix seconds, unix ms, or ISO 8601. Defaults to 7 days before `end`.","schema":{"type":"string"}},{"name":"end","in":"query","description":"Analytics only. Range end (same formats as `start`). Defaults to now.","schema":{"type":"string"}},{"name":"granularity","in":"query","description":"Analytics only, `fields=analytics` bucketing.","schema":{"type":"string","enum":["DAY","MONTH"],"default":"DAY"}}],"responses":{"200":{"description":"OK","content":{"application/json":{"schema":{"oneOf":[{"$ref":"#/components/schemas/MediaGetResponse"},{"$ref":"#/components/schemas/AnalyticsResponse"},{"$ref":"#/components/schemas/PricingAnalyticsResponse"},{"$ref":"#/components/schemas/TemplateAnalyticsResponse"}]}}}},"400":{"$ref":"#/components/responses/BadRequest"},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"429":{"$ref":"#/components/responses/RateLimited"},"default":{"$ref":"#/components/responses/DefaultError"}}}}}}
```

## DELETE /{id}

> Delete media

```json
{"openapi":"3.0.3","info":{"title":"ZenFinder Business API","version":"1.0"},"tags":[{"name":"Media","description":"Upload, retrieve and delete media attachments."}],"servers":[{"url":"https://api.zenfinder.ai/business/v1.0","description":"Production"}],"security":[{"bearerAuth":["zenfinder_business_messaging"]}],"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"zf_live_...","description":"API key minted from Business Manager, sent as `Authorization: Bearer\n<key>`. Scopes: `zenfinder_business_messaging`,\n`zenfinder_business_management`.\n"}},"schemas":{"SimpleSuccessResponse":{"type":"object","properties":{"success":{"type":"boolean"},"messaging_product":{"type":"string"}}},"Error":{"type":"object","description":"Graph/WhatsApp-Cloud-API-compatible error envelope. Numeric `code`\nvalues are reused verbatim from the WhatsApp Cloud API:\n\n| Code | HTTP | Meaning |\n|---|---|---|\n| 0 | 401 | AuthException |\n| 3 | 403 | Capability or permissions issue |\n| 10 | 403 | Permission denied |\n| 100 | 400 | Invalid parameter |\n| 190 | 401 | Access token invalid or expired |\n| 200 | 403 | Permission denied (missing scope / not authorised for this resource) |\n| 130429 | 429 | Rate limit hit |\n| 131000 | 500 | Something went wrong |\n| 131008 | 400 | Required parameter is missing |\n| 131009 | 400 | Parameter value is not valid |\n| 131016 | 503 | Service unavailable |\n| 131021 | 400 | Recipient cannot be sender |\n| 131026 | 400 | Message undeliverable |\n| 131037 | 403 | Display name not approved |\n| 131042 | 402 | Business eligibility payment issue (insufficient wallet balance) |\n| 131047 | 400 | Re-engagement message (service window closed — send a template) |\n| 131048 | 400 | Spam rate limit hit |\n| 131049 | 400 | Per-user marketing message limit reached |\n| 131050 | 400 | User stopped marketing messages (opted out) |\n| 131051 | 400 | Unsupported message type |\n| 131052 | 400 | Media download error |\n| 131053 | 400 | Media upload error |\n| 132000 | 400 | Template parameter count mismatch |\n| 132001 | 400 | Template does not exist / not approved |\n| 132005 | 400 | Template hydrated text too long |\n| 132007 | 400 | Template format character policy violated |\n| 132012 | 400 | Template parameter format mismatch |\n| 132015 | 400 | Template is paused |\n| 132016 | 400 | Template is disabled |\n| 133010 | 400 | Sender not registered |\n","properties":{"error":{"type":"object","properties":{"message":{"type":"string"},"type":{"type":"string"},"code":{"type":"integer"},"error_subcode":{"type":"integer","nullable":true},"error_data":{"type":"object","properties":{"messaging_product":{"type":"string"},"details":{"type":"string"}}},"zen_trace_id":{"type":"string"}}}}}},"responses":{"Unauthorized":{"description":"Missing, malformed, revoked or expired Bearer token (codes `0`, `190`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"Forbidden":{"description":"Token lacks the required scope, or isn't authorised for this sender/ZBA/media (codes `3`, `10`, `200`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}},"DefaultError":{"description":"Unexpected error (code `131000`) or upstream unavailability (`131016`).","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Error"}}}}}},"paths":{"/{id}":{"delete":{"operationId":"deleteMedia","tags":["Media"],"summary":"Delete media","parameters":[{"name":"id","in":"path","required":true,"description":"The `MEDIA_ID` to delete.","schema":{"type":"string"}}],"responses":{"200":{"description":"Deleted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SimpleSuccessResponse"}}}},"401":{"$ref":"#/components/responses/Unauthorized"},"403":{"$ref":"#/components/responses/Forbidden"},"default":{"$ref":"#/components/responses/DefaultError"}}}}}}
```


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.zenfinder.ai/reference/media.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
