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

# Upload Media

> Upload an image, video, or document to Meta and get the media handle used in template media headers.

## What it does

Uploads a file to Meta's resumable upload API and returns a **media handle**. Two endpoints consume that handle: [Create Template](/public-api-reference/template/create-template) uses it as the header example for an `IMAGE`, `VIDEO`, or `DOCUMENT` header, and [Update Business Profile](/public-api-reference/application/update-business-profile) uses it as `profilePictureReference`. Neither one accepts raw file bytes or a public URL.

The endpoint runs the two Meta steps for you in a single call:

1. **Create an upload session** for the file length and content type.
2. **Upload the bytes** into that session and read back the handle.

The content type is derived from the uploaded part itself, validated against the allowed list, and checked against the size limit for its media type — all before anything reaches Meta. The `app_id` is verified to belong to the organization.

<Tip>
  **Template header:** put the handle in `header.examples` alongside `"format": "IMAGE"` (or `VIDEO` / `DOCUMENT`). See [Create Template](/public-api-reference/template/create-template).

  **Profile picture:** send the handle as `profilePictureReference`. See [Update Business Profile](/public-api-reference/application/update-business-profile).
</Tip>

<Warning>
  **A handle is single-use.** It is consumed by the first request that references it and cannot be replayed — a second call with the same value fails. Upload the file again for each use, and never cache or share handles.
</Warning>

***

## Endpoint

```text theme={null}
POST /api/v1/wa/{app_id}/media/upload
```

There are no query parameters. Unlike the other Public API endpoints, the body is `multipart/form-data`, not JSON.

### Headers

<ParamField header="Authorization" type="string" required>
  Secret key in `Bearer sk_...` format. See [Secret Key authentication](/essentials/authentication#secret-key).
</ParamField>

<ParamField header="Content-Type" type="string" required>
  Always `multipart/form-data` (your HTTP client sets it, together with the boundary).
</ParamField>

### Path parameters

<ParamField path="app_id" type="string" required>
  Application ID. Must belong to the organization (otherwise `404`).
</ParamField>

***

## Request Body

<ParamField body="file" type="file" required>
  The file part. Its MIME type must be one of the allowed content types and its size must stay within that media type's limit — see [Media Type](/public-api-reference/media/reference/enums#media-type) and [Content Type](/public-api-reference/media/reference/enums#content-type). Both the media type and the size are resolved from the uploaded part, so you do not send them separately.
</ParamField>

<Note>
  **Limits at a glance:** image (`image/jpeg`, `image/png`) up to 5 MB, video (`video/mp4`) up to 16 MB, document (`application/pdf`) up to 100 MB. Anything else is rejected with `422` before the request reaches Meta.
</Note>

***

## Response

The envelope is `{ "data": {...}, "isSuccess": true }`.

<ResponseField name="isSuccess" type="boolean">
  `true` when the file was uploaded successfully.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="handle" type="string">
      The Meta media handle. Pass it as the header example when creating a template with a media header, or as `profilePictureReference` when updating the business profile. Valid for exactly one such use.
    </ResponseField>
  </Expandable>
</ResponseField>

<Note>
  The handle identifies the uploaded bytes on Meta's side; it is not a public URL and cannot be fetched directly. Because it is consumed on first use, upload the file once per template or profile update you want to attach it to — the same image used in two templates needs two uploads.
</Note>

***

## Examples

<AccordionGroup>
  <Accordion title="Demo 1 — upload an image">
    ```text theme={null}
    POST /api/v1/wa/app_7poyXj8GXuv76e/media/upload
    Authorization: Bearer sk_...
    Content-Type: multipart/form-data; boundary=----boundary
    ```

    ```text theme={null}
    ------boundary
    Content-Disposition: form-data; name="file"; filename="banner.png"
    Content-Type: image/png

    <binary data>
    ------boundary--
    ```

    ```json theme={null}
    { "data": { "handle": "4::aW1hZ2UvcG5n:ARZ9k1sample_handle_value" }, "isSuccess": true }
    ```
  </Accordion>

  <Accordion title="Demo 2 — use the handle in a template media header">
    Take the `handle` from the upload response and send it as the header example:

    ```text theme={null}
    POST /api/v1/wa/app_7poyXj8GXuv76e/templates
    Authorization: Bearer sk_...
    Content-Type: application/json
    ```

    ```json theme={null}
    {
      "name": "summer_sale",
      "language": "en",
      "category": "MARKETING",
      "header": { "format": "IMAGE", "examples": ["4::aW1hZ2UvcG5n:ARZ9k1sample_handle_value"] },
      "body": { "text": "Hi {{1}}, our summer sale is live. Enjoy up to 50% off.", "examples": ["John"] },
      "footer": { "text": "Sent via Connexease" }
    }
    ```

    ```json theme={null}
    { "data": { "sourceId": "1029384756127", "name": "summer_sale", "language": "en", "category": "MARKETING", "status": "PENDING" }, "isSuccess": true }
    ```
  </Accordion>

  <Accordion title="Demo 3 — use the handle as the business profile picture">
    ```text theme={null}
    PATCH /api/v1/wa/app_7poyXj8GXuv76e/profile
    Authorization: Bearer sk_...
    Content-Type: application/json
    ```

    ```json theme={null}
    { "profilePictureReference": "4::aW1hZ2UvcG5n:ARZ9k1sample_handle_value" }
    ```

    ```json theme={null}
    {
      "data": { "profilePictureUrl": "https://pps.whatsapp.net/v/t61.24694-24/sample_profile_picture", "about": "Open 09:00-18:00" },
      "isSuccess": true
    }
    ```

    The handle is spent by this call. See [Update Business Profile](/public-api-reference/application/update-business-profile).
  </Accordion>

  <Accordion title="Demo 4 — upload a document">
    ```text theme={null}
    POST /api/v1/wa/app_7poyXj8GXuv76e/media/upload
    Authorization: Bearer sk_...
    Content-Type: multipart/form-data; boundary=----boundary
    ```

    ```text theme={null}
    ------boundary
    Content-Disposition: form-data; name="file"; filename="invoice.pdf"
    Content-Type: application/pdf

    <binary data>
    ------boundary--
    ```

    ```json theme={null}
    { "data": { "handle": "4::YXBwbGljYXRpb24vcGRm:ARa7c2sample_handle_value" }, "isSuccess": true }
    ```
  </Accordion>

  <Accordion title="Demo 5 — error: unsupported content type → 422">
    A `image/gif` part is rejected before reaching Meta:

    ```text theme={null}
    POST /api/v1/wa/app_7poyXj8GXuv76e/media/upload
    Authorization: Bearer sk_...
    Content-Type: multipart/form-data; boundary=----boundary
    ```

    ```json theme={null}
    { "errors": { "code": "WA_VAL_003", "group": "VALIDATION", "description": "Content type 'image/gif' is not allowed. Allowed: image/jpeg, image/png, video/mp4, application/pdf." }, "isSuccess": false }
    ```
  </Accordion>

  <Accordion title="Demo 6 — error: file too large → 422">
    A 9 MB PNG exceeds the 5 MB image limit:

    ```json theme={null}
    { "errors": { "code": "WA_VAL_004", "group": "VALIDATION", "description": "File size 9437184 bytes exceeds maximum 5242880 bytes." }, "isSuccess": false }
    ```
  </Accordion>
</AccordionGroup>

***

## Errors

<ResponseField name="errors" type="object">
  Error details with `code`, `group`, and `description`; `isSuccess` is `false`.
</ResponseField>

### Auth / tenant

| HTTP | Code                      | When                                          |
| ---- | ------------------------- | --------------------------------------------- |
| 401  | `ORGANIZATION_SECRET_010` | Secret key is missing.                        |
| 401  | `ORGANIZATION_SECRET_011` | Secret key is invalid.                        |
| 404  | `APPLICATION_004`         | `app_id` does not belong to the organization. |

### Request validation (before reaching Meta)

| HTTP | Code                         | Group        | When                                                                  |
| ---- | ---------------------------- | ------------ | --------------------------------------------------------------------- |
| 422  | `WA_VAL_003`                 | `VALIDATION` | The file's content type is not in the allowed list.                   |
| 422  | `WA_VAL_004`                 | `VALIDATION` | The file exceeds the size limit for its media type.                   |
| 422  | `WA_VAL_002`                 | `VALIDATION` | The resolved media type is not one of `image` / `video` / `document`. |
| 422  | `VAL_*` (FastAPI validation) | `VALIDATION` | The `file` part is missing or the body is not `multipart/form-data`.  |

### Meta-side

| HTTP | Code       | When                                                      |
| ---- | ---------- | --------------------------------------------------------- |
| 401  | `META_068` | Token invalid/expired (190).                              |
| 429  | `META_069` | Rate limit (4 / 80007).                                   |
| 400  | `META_070` | Invalid parameter (100).                                  |
| 502  | `META_015` | General/fallback error while creating the upload session. |

### Meta-side

| HTTP | Code       | When                                              |
| ---- | ---------- | ------------------------------------------------- |
| 401  | `META_071` | Token invalid/expired (190).                      |
| 429  | `META_072` | Rate limit (4 / 80007).                           |
| 400  | `META_073` | Invalid parameter (100).                          |
| 502  | `META_016` | General/fallback error while uploading the bytes. |

Example error responses:

```json theme={null}
{ "errors": { "code": "WA_VAL_004", "group": "VALIDATION", "description": "File size 9437184 bytes exceeds maximum 5242880 bytes." }, "isSuccess": false }
```

```json theme={null}
{ "errors": { "code": "META_016", "group": "SERVICE_UNAVAILABLE", "description": "Failed to upload media file." }, "isSuccess": false }
```
