> ## 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.

# Envío de archivo

> Envíe imagen, video, audio y documento a una conversación, en dos pasos

Enviar un archivo por la API son **dos llamadas**: primero sube el archivo y recibe un
identificador, luego envía el mensaje referenciando ese identificador.

```
1. POST /v1/resource/upload-attachment   (multipart)  →  { "_id": "..." }
2. POST /v1/messages/send-document       (JSON, con el _id del paso 1)
```

No existe una llamada única que reciba el archivo y lo envíe. Intentar mandar el archivo
directo al endpoint de envío resulta en error.

Todo esto usa el scope `messages:create`, el mismo del envío de texto. Si su API key ya envía
mensajes, ya envía archivos.

## Paso 1: subir el archivo

<Steps>
  <Step title="Envíe el archivo en multipart/form-data">
    Dos campos obligatorios: `file` con el binario y `fileType` con la categoría.

    ```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="Guarde el `_id` de la respuesta">
    La respuesta trae el registro del adjunto. El campo que importa para el paso 2 es el `_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>
      El adjunto queda guardado y puede reutilizarse. Para enviar el mismo archivo a varios
      contactos, súbalo una vez y use el mismo `_id` en cada envío.
    </Tip>
  </Step>
</Steps>

### Valores de `fileType`

| `fileType` | Formatos aceptados                  | Endpoint de envío                 |
| ---------- | ----------------------------------- | --------------------------------- |
| `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`    |

El `fileType` de la subida debe coincidir con el endpoint de envío. Subir como `DOCUMENT` e
intentar enviar por `send-image` no funciona.

## Paso 2: enviar a la conversación

El cuerpo es JSON, con la conversación de destino y el objeto `message`:

<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": "Aquí tiene el presupuesto que acordamos.",
        "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: "Aquí tiene el presupuesto que acordamos.",
        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": "Aquí tiene el presupuesto que acordamos.",
              "contentType": "DOCUMENT",
              "attachment": upload["_id"],
          },
      },
  )
  ```
</CodeGroup>

### Campos de `message`

| Campo           | Tipo   | Obligatorio | Descripción                                                |
| --------------- | ------ | ----------- | ---------------------------------------------------------- |
| `attachment`    | string | Sí          | El `_id` devuelto por la subida.                           |
| `contentType`   | string | Sí          | `DOCUMENT`, `IMAGE`, `VIDEO` o `AUDIO`, según el endpoint. |
| `content`       | string | No          | Texto que acompaña al archivo, como leyenda.               |
| `quoteSourceId` | string | No          | Id del mensaje que quiere citar (vea abajo).               |

<Warning>
  El campo `contentAttributes.inReplyToExternalId` está **descontinuado y sin efecto**. Para
  responder citando un mensaje, use `quoteSourceId`.
</Warning>

### Citar un mensaje

`quoteSourceId` espera el id del mensaje **en el canal**, no el `_id` de Nuvia. Enviar el `_id` no
cita nada y no devuelve error.

| Canal              | Qué es el id |
| ------------------ | ------------ |
| WhatsApp (Meta)    | `wamid`      |
| Evolution          | `key.id`     |
| LinkedIn (Unipile) | `message_id` |

Ese valor llega en el campo `source_id` de cada mensaje devuelto por
`GET /v1/messages/conversation/{id}`. Léalo de ahí y envíelo de vuelta en `quoteSourceId`. El
mensaje citado tiene que pertenecer a la misma conversación.

## Límites

* **Subida**: hasta 100 MB por archivo.
* **WhatsApp**: imagen hasta 5 MB, audio y video hasta 16 MB, documento hasta 100 MB. El límite
  del canal aplica aunque la subida haya aceptado el archivo, así que un video de 50 MB se sube
  pero no llega al contacto.
* **Formatos**: solo los aceptados por WhatsApp, listados en la tabla anterior.

## Errores comunes

| Situación                                     | Respuesta                 |
| --------------------------------------------- | ------------------------- |
| API key sin el scope `messages:create`        | `403`                     |
| Archivo por encima de 100 MB                  | `413`, y nada se almacena |
| `fileType` ausente o fuera de la lista        | `400`                     |
| `attachment` que no existe, o de otra empresa | `400`                     |
| Token ausente, inválido o revocado            | `401`                     |

## Próximos pasos

<CardGroup cols={2}>
  <Card title="Referencia de la API" icon="code" href="/es/api-reference/messages">
    Todos los endpoints de envío, con parámetros y respuestas.
  </Card>

  <Card title="Ejemplos de integración" icon="puzzle-piece" href="/es/guias/exemplos-integracao">
    Casos completos de principio a fin.
  </Card>
</CardGroup>
