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

# File sending

> Send an image, video, audio, or document to a conversation, in two steps

Sending a file through the API is **two calls**: first you upload the file and get an
identifier, then you send the message referencing that identifier.

```
1. POST /v1/resource/upload-attachment   (multipart)  →  { "_id": "..." }
2. POST /v1/messages/send-document       (JSON, with the _id from step 1)
```

There's no single call that receives the file and sends it. Trying to send the file directly
to the sending endpoint results in an error.

Everything here uses the `messages:create` scope, the same one used for sending text. If your
key already sends messages, it already sends files.

## Step 1: upload the file

<Steps>
  <Step title="Send the file as multipart/form-data">
    Two required fields: `file` with the binary and `fileType` with the category.

    ```bash cURL theme={null}
    curl -X POST https://api.nuvia.ai/v1/resource/upload-attachment \
      -H "Authorization: Bearer $NUVIA_API_KEY" \
      -F "file=@orcamento.pdf" \
      -F "fileType=DOCUMENT"
    ```
  </Step>

  <Step title="Save the `_id` from the response">
    The response carries the attachment record. The field that matters for step 2 is the `_id`:

    ```json theme={null}
    {
      "_id": "65f1a2b3c4d5e6f7a8b9c0d1",
      "extension": "pdf",
      "external_url": "https://s3.amazonaws.com/bucket/orcamento.pdf",
      "file_type": "DOCUMENT",
      "meta": {
        "mimeType": "application/pdf",
        "size": 102400,
        "fileName": "orcamento.pdf"
      }
    }
    ```

    <Tip>
      The attachment stays saved and can be reused. To send the same file to several contacts,
      upload it once and use the same `_id` in each send.
    </Tip>
  </Step>
</Steps>

### `fileType` values

| `fileType` | Accepted formats                    | Sending endpoint                  |
| ---------- | ----------------------------------- | --------------------------------- |
| `DOCUMENT` | PDF, DOC, DOCX, XLS, XLSX, PPT, TXT | `POST /v1/messages/send-document` |
| `IMAGE`    | JPG, PNG, GIF, WEBP                 | `POST /v1/messages/send-image`    |
| `VIDEO`    | MP4, 3GP                            | `POST /v1/messages/send-video`    |
| `AUDIO`    | MP3, OGG, AAC, AMR                  | `POST /v1/messages/send-audio`    |
| `STICKER`  | WEBP                                | `POST /v1/messages/send-image`    |

The `fileType` from the upload must match the sending endpoint. Uploading as `DOCUMENT` and
trying to send through `send-image` doesn't work.

## Step 2: send to the conversation

The body is JSON, with the target conversation and the `message` object:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.nuvia.ai/v1/messages/send-document \
    -H "Authorization: Bearer $NUVIA_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "conversationId": "65f1a2b3c4d5e6f7a8b9c0d3",
      "message": {
        "content": "Here's the quote we agreed on.",
        "contentType": "DOCUMENT",
        "attachment": "65f1a2b3c4d5e6f7a8b9c0d1"
      }
    }'
  ```

  ```javascript JavaScript theme={null}
  const upload = new FormData();
  upload.append("file", file);
  upload.append("fileType", "DOCUMENT");

  const { _id } = await fetch("https://api.nuvia.ai/v1/resource/upload-attachment", {
    method: "POST",
    headers: { Authorization: `Bearer ${process.env.NUVIA_API_KEY}` },
    body: upload,
  }).then((r) => r.json());

  await fetch("https://api.nuvia.ai/v1/messages/send-document", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.NUVIA_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      conversationId,
      message: {
        content: "Here's the quote we agreed on.",
        contentType: "DOCUMENT",
        attachment: _id,
      },
    }),
  });
  ```

  ```python Python theme={null}
  import os
  import requests

  headers = {"Authorization": f"Bearer {os.environ['NUVIA_API_KEY']}"}

  with open("orcamento.pdf", "rb") as f:
      upload = requests.post(
          "https://api.nuvia.ai/v1/resource/upload-attachment",
          headers=headers,
          files={"file": f},
          data={"fileType": "DOCUMENT"},
      ).json()

  requests.post(
      "https://api.nuvia.ai/v1/messages/send-document",
      headers=headers,
      json={
          "conversationId": conversation_id,
          "message": {
              "content": "Here's the quote we agreed on.",
              "contentType": "DOCUMENT",
              "attachment": upload["_id"],
          },
      },
  )
  ```
</CodeGroup>

### `message` fields

| Field           | Type   | Required | Description                                                      |
| --------------- | ------ | -------- | ---------------------------------------------------------------- |
| `attachment`    | string | Yes      | The `_id` returned by the upload.                                |
| `contentType`   | string | Yes      | `DOCUMENT`, `IMAGE`, `VIDEO`, or `AUDIO`, matching the endpoint. |
| `content`       | string | No       | Text that accompanies the file, such as a caption.               |
| `quoteSourceId` | string | No       | Id of the message you want to quote (see below).                 |

<Warning>
  The `contentAttributes.inReplyToExternalId` field is **deprecated and has no effect**. To
  reply quoting a message, use `quoteSourceId`.
</Warning>

### Quoting a message

`quoteSourceId` expects the id of the message **on the channel**, not Nuvia's `_id`. Sending the
`_id` quotes nothing and returns no error.

| Channel            | What the id is |
| ------------------ | -------------- |
| WhatsApp (Meta)    | `wamid`        |
| Evolution          | `key.id`       |
| LinkedIn (Unipile) | `message_id`   |

That value arrives in the `source_id` field of every message returned by
`GET /v1/messages/conversation/{id}`. Read it from there and send it back in `quoteSourceId`. The
quoted message has to belong to the same conversation.

## Limits

* **Upload**: up to 100 MB per file.
* **WhatsApp**: images up to 5 MB, audio and video up to 16 MB, documents up to 100 MB. The
  channel limit applies even if the upload accepted the file, so a 50 MB video uploads but
  doesn't reach the contact.
* **Formats**: only those accepted by WhatsApp, listed in the table above.

## Common errors

| Situation                                                      | Response                     |
| -------------------------------------------------------------- | ---------------------------- |
| Key without the `messages:create` scope                        | `403`                        |
| File over 100 MB                                               | `413`, and nothing is stored |
| `fileType` missing or not in the list                          | `400`                        |
| `attachment` that doesn't exist, or belongs to another company | `400`                        |
| Token missing, invalid, or revoked                             | `401`                        |

## Next steps

<CardGroup cols={2}>
  <Card title="API reference" icon="code" href="/en/api-reference/messages">
    All the sending endpoints, with parameters and responses.
  </Card>

  <Card title="Integration examples" icon="puzzle-piece" href="/en/guias/exemplos-integracao">
    Complete end-to-end use cases.
  </Card>
</CardGroup>
