---
seo_title: API de Subida de Archivos: Multipart, URL y Límites | EnConvert
meta_desc: Todas las formas de enviar bytes a la API de EnConvert: subida multipart o una URL que la API descarga sola, con límites de tamaño, 413 y reglas de nombre.
keywords: api de subida de archivos, subir archivo multipart form data api, error 413 payload too large api, tamaño máximo de archivo por plan, validar magic bytes al subir, nombre de archivo de salida api, convertir archivo desde url api, límite de tamaño de subida api
---

# Ingesta de Archivos

Hay dos formas de enviar bytes a EnConvert: subir el archivo tú mismo como `multipart/form-data`, o pasar una `url` y dejar que la API descargue el recurso. Cuál puedes usar depende del endpoint, no de tu plan.

---

## Qué acepta cada endpoint

| Familia de endpoints | Cómo llegan los bytes | Nombre del campo |
|---|---|---|
| [Formatos de datos](/es/docs/endpoints/convert/data-formats.md), [documentos](/es/docs/endpoints/convert/documents.md), [imágenes](/es/docs/endpoints/convert/images.md) | Subida `multipart/form-data`, un archivo por solicitud | `file` |
| [Páginas web](/es/docs/endpoints/convert/web-pages.md) (`url-to-pdf`, `url-to-screenshot`, `url-to-markdown`, `website-to-pdf`, `website-to-screenshot`) | Cuerpo JSON, EnConvert descarga la página | `url` |
| [`POST /v2/ingest/files`](/es/docs/endpoints/ingest.md) | `multipart/form-data`, muchos archivos por trabajo | `files` |
| [`POST /v2/perceive`](/es/docs/endpoints/perceive.md), [`POST /v2/ingest`](/es/docs/endpoints/ingest.md) | Cuerpo JSON, EnConvert descarga la página | `url` |

No hay una tercera vía. Los endpoints de subida de archivos no descargan una URL por ti, incluidas las rutas comodín [anything-to-pdf](/es/docs/endpoints/convert/documents/anything-to-pdf.md) y [anything-to-markdown](/es/docs/endpoints/convert/documents/anything-to-markdown.md). Para convertir una página en vivo a PDF, llama a [url-to-pdf](/es/docs/endpoints/convert/web-pages/url-to-pdf.md) en su lugar. Para saber qué formatos acepta cada endpoint, consulta [formatos compatibles](/es/docs/reference/supported-formats.md).

---

## Subir un archivo local

El campo del formulario se llama `file`, y cada endpoint V1 de subida de archivos acepta exactamente uno. Todo lo demás en el formulario es opcional.

| Campo del formulario | Tipo | Descripción |
|-----------|------|-------------|
| `file` | file | El archivo a convertir. Su extensión debe estar aceptada por el endpoint. |
| `output_filename` | `string` | Nombre base personalizado para la salida. La extensión de destino se añade automáticamente. |
| `job_id` | `string` | ID de trabajo proporcionado por el cliente para recuperarse de un timeout. Consulta `GET /v1/convert/status/{job_id}` si se corta la conexión. |
| `pdf_options` | `string` | Cadena JSON con opciones de PDF, en los endpoints que producen un PDF. |
| `direct_download` | `boolean` | Se acepta por paridad con la forma de solicitud de los endpoints de URL. Aquí no tiene efecto: una subida siempre responde con el envoltorio JSON de abajo, envíes lo que envíes. |

### curl

```bash
curl -X POST https://api.enconvert.com/v1/convert/anything-to-pdf \
  -H "X-API-Key: sk_your_private_key" \
  -F "file=@quarterly-report.docx" \
  -F "direct_download=false"
```

La respuesta es JSON con un enlace de descarga prefirmado:

```json
{
    "presigned_url": "https://spaces.example.com/...signed...",
    "object_key": "env/files/{project_id}/anything-to-pdf/quarterly-report_20260714_101530123.pdf",
    "filename": "quarterly-report_20260714_101530123.pdf",
    "file_size": 51240,
    "conversion_time_seconds": 2.1,
    "job_id": null
}
```

Los mismos valores se reflejan en los encabezados de respuesta `X-Object-Key`, `X-File-Size`, `X-Conversion-Time` y `X-Filename`, así que puedes leerlos sin parsear el cuerpo. Descarga el archivo enseguida: el enlace es de corta duración, y [URLs firmadas](/es/docs/concepts/signed-urls.md) explica exactamente cuánto.

### Python

```python
import requests

with open("quarterly-report.docx", "rb") as f:
    response = requests.post(
        "https://api.enconvert.com/v1/convert/anything-to-pdf",
        headers={"X-API-Key": "sk_your_private_key"},
        files={"file": ("quarterly-report.docx", f)},
        data={"direct_download": "false"},
    )

response.raise_for_status()
result = response.json()

# Download the PDF from the pre-signed URL.
pdf = requests.get(result["presigned_url"]).content
with open("quarterly-report.pdf", "wb") as out:
    out.write(pdf)
```

### Node.js

```javascript
import { readFile, writeFile } from "node:fs/promises";

const form = new FormData();
form.append(
    "file",
    new Blob([await readFile("quarterly-report.docx")]),
    "quarterly-report.docx"
);
form.append("direct_download", "false");

const response = await fetch(
    "https://api.enconvert.com/v1/convert/anything-to-pdf",
    { method: "POST", headers: { "X-API-Key": "sk_your_private_key" }, body: form }
);

const result = await response.json();
const pdf = await fetch(result.presigned_url).then((r) => r.arrayBuffer());
await writeFile("quarterly-report.pdf", Buffer.from(pdf));
```

### Varios archivos en una sola llamada

`POST /v2/ingest/files` es el único endpoint que acepta más de un archivo. Repite el campo `files`, hasta 200 archivos por trabajo:

```bash
curl -X POST https://api.enconvert.com/v2/ingest/files \
  -H "X-API-Key: sk_your_private_key" \
  -F "files=@handbook.pdf" \
  -F "files=@pricing.xlsx" \
  -F "files=@faq.docx" \
  -F "max_words=700" \
  -F "webhook_url=https://your-app.example.com/hooks/ingest"
```

Cada archivo se convierte a Markdown, se fragmenta y se ensambla en un único entregable JSONL. El trabajo siempre es asíncrono y responde `202 Accepted` con un `job_id`. Los detalles completos están en la [página del endpoint de ingesta](/es/docs/endpoints/ingest.md).

---

## Dejar que EnConvert descargue el archivo

En los endpoints de URL envías un cuerpo JSON en lugar de un formulario, y la API se encarga de la descarga:

```bash
curl -X POST https://api.enconvert.com/v1/convert/url-to-markdown \
  -H "X-API-Key: sk_your_private_key" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://example.com/report"}'
```

`url` acepta una cadena o un array de cadenas. Un array cambia la solicitud a asíncrona y se explica en [procesamiento por lotes](/es/docs/guides/batch-processing.md).

### Fuentes detrás de un inicio de sesión

Tres campos opcionales permiten que la descarga lleve credenciales. Los tres requieren un plan con acceso a autenticación básica (Indie o superior).

| Parámetro | Tipo | Predeterminado | Descripción |
|-----------|------|---------|-------------|
| `auth` | `object` | `null` | Credenciales de HTTP Basic Auth: `{"username": "...", "password": "..."}`. |
| `cookies` | `array` | `null` | Array de objetos cookie inyectados antes de la navegación. Máximo 50 por solicitud. Cada uno requiere `name`, `value` y, o bien `domain`, o bien `url`. |
| `headers` | `object` | `null` | Encabezados HTTP personalizados enviados con las solicitudes. Máximo 20 por solicitud. No puede incluir encabezados bloqueados: `host`, `content-length`, `transfer-encoding`, `connection`, `upgrade`, `te`, `trailer`. |

<div class="alert alert-info">
<strong>Alcance de las credenciales:</strong> Las credenciales del objeto <code>auth</code>, y un encabezado <code>Authorization</code> (por ejemplo, un token Bearer) pasado vía <code>headers</code>, se envían <strong>solo al origen de destino</strong>, nunca a los subrecursos de terceros que solicita la página. Esto evita fugas de credenciales hacia hosts de publicidad, analítica o CDN.
</div>

### Direcciones privadas e internas

La `url` debe ser una dirección pública `http://` o `https://`. Antes de descargar nada, se analiza y se rechaza con `400 Bad Request` cuando:

- usa un esquema distinto de `http`/`https`;
- incrusta credenciales como `https://user:pass@host/` (usa el campo `auth` en su lugar);
- apunta a `localhost`, a un hostname de metadatos de nube o a una IP que resuelve en un rango privado, de loopback, link-local, reservado o no público de cualquier otro tipo;
- usa una notación de IP no estándar (octal, hexadecimal o entero empaquetado) que podría resolverse de forma ambigua.

Esto se aplica a la URL semilla, a cada URL de un lote y a las páginas descubiertas por los endpoints de rastreo `website-to-*`.

Dicho claramente: EnConvert se ejecuta fuera de tu red. No puede alcanzar `http://10.0.0.5/report.docx`, un hostname `.internal` ni nada que solo resuelva dentro de tu VPC. O haces que el archivo sea accesible desde la internet pública, o lees los bytes tú mismo y los subes.

---

## Qué le pasa a tu nombre de archivo

El nombre que envías cumple dos funciones.

**Elige el conversor.** La extensión decide qué ruta de entrada se ejecuta, así que nombra bien el archivo. Un archivo llamado `report` sin extensión es rechazado por cualquier endpoint que tenga una lista blanca de extensiones.

**Sirve de base para el nombre de salida.** El nombre del archivo de salida se construye así:

```
{base}_{YYYYMMDD_HHMMSSmmm}.{ext}
```

La marca de tiempo UTC siempre se añade, así que dos conversiones del mismo archivo nunca colisionan. `base` se resuelve en este orden:

1. `output_filename`, si enviaste uno. Si incluiste en él la extensión de destino, esa extensión se elimina primero para que no acabes con `report.pdf_20260405_123456789.pdf`.
2. El nombre del archivo subido sin su extensión. `report.docx` produce `report_20260405_123456789.pdf`.
3. Para conversiones de URL, el dominio. `https://example.com/page` produce `example_20260405_123456789.pdf`.
4. Si nada de eso aplica, el literal `output`.

La clave de almacenamiento se sanea antes de escribir el resultado: solo sobrevive el nombre base, se elimina `..`, se quitan los caracteres `<>:"|?*` y los espacios pasan a ser guiones bajos. Sube `My Report (final).docx` y el PDF acaba en `My_Report_(final)_20260405_123456789.pdf`. Esa ruta se te devuelve como `object_key` y tiene la forma `{env}/files/{project_id}/{endpoint}/{filename}`.

Los nombres de archivo enviados a `POST /v2/ingest/files` están además limitados a 255 caracteres.

---

## Límite de tamaño y el 413

El límite de subida es por plan y se aplica a cada archivo individual.

| Plan | Tamaño máximo de subida | Bytes |
|------|-----------------|-------|
| Founding | 5 MB | 5242880 |
| Indie | 15 MB | 15728640 |
| Studio | 50 MB | 52428800 |
| Production | 150 MB | 157286400 |
| Enterprise | Negociado | Según contrato |

El tamaño se mide desde la propia parte subida a medida que el cuerpo llega en streaming, no desde un encabezado que tú controlas, así que una subida chunked sin `Content-Length` se comprueba igual. Pasarse devuelve `413 Payload Too Large` antes de que se haga ningún trabajo de conversión y antes de que se facture ninguna op:

```json
{
    "detail": {
        "error": "File too large",
        "file_size": 10485760,
        "max_size": 5242880,
        "tier": "free",
        "key_type": "private"
    }
}
```

`file_size` y `max_size` están ambos en bytes. `tier` es el slug del plan (`free`, `starter`, `pro`, `business`, `enterprise`), no el nombre visible que ves en la página de precios, así que un proyecto Studio informa `"tier": "pro"`. `key_type` es `private`, `public` o `dashboard`, con `unknown` como valor de reserva.

<div class="alert alert-warning">
<strong>5 MB se agotan rápido.</strong> En el plan Founding, un PDF escaneado de 40 páginas o una presentación con unas cuantas fotos a sangre completa ya suele pasarse de la raya. No hay ruta de subida fragmentada ni reanudable: la solución es un archivo más pequeño o un plan más grande.
</div>

Superar la barrera del tamaño no es el último obstáculo. Una asignación mensual agotada responde `402 Payment Required` y demasiadas solicitudes en una ventana responden `429`, ambos descritos en [límites de frecuencia y cuotas](/es/docs/reference/rate-limits.md).

---

## Tipo de contenido y magic bytes

Las subidas pasan por dos comprobaciones, en este orden.

**1. La lista blanca de extensiones.** Cada endpoint declara qué extensiones acepta. Una discrepancia devuelve `400 Bad Request`:

```json
{
    "detail": "Invalid file format '.pdf' for png-to-jpeg. Allowed: .png"
}
```

**2. El sniffing de magic bytes.** Los primeros bytes del archivo se comparan con el grupo que declara su extensión. Una discrepancia de alta confianza también devuelve `400`:

```json
{
    "detail": "File content does not match the 'png-to-jpeg' input type."
}
```

Eso es lo que obtienes cuando renombras un PDF a `.png` y lo subes: los bytes empiezan con `%PDF-`, la extensión dice PNG, y ambos se contradicen. La comprobación existe porque la extensión es lo que enruta tu solicitud. Sin ella, los bytes de PDF llegan a un decodificador de imágenes y obtienes un fallo opaco en lo más profundo del conversor en lugar de un 400 claro en la puerta, y un archivo mal etiquetado a propósito acaba en manos de un parser que nunca debió verlo.

Dos cosas que esto **no** hace, y conviene saberlas:

- El `Content-Type` de la parte nunca se inspecciona. No hay ninguna lista blanca de MIME en toda la ruta de subida, así que `application/octet-stream` vale. La extensión del nombre de archivo es lo único que enruta la solicitud, y por eso enviar un archivo sin extensión falla.
- Los formatos de texto no tienen una firma fiable y se saltan el sniffing por completo: `.json`, `.csv`, `.xml`, `.yaml`, `.toml`, `.md`, `.html`, `.svg`, `.txt`. Un archivo `.json` que en realidad contiene CSV se acepta en esta etapa y falla después, en el parser.

Las firmas binarias reconocidas son PNG, JPEG, GIF, WebP, HEIC/HEIF, PDF y el grupo ofimático (un contenedor ZIP para `.docx`/`.xlsx`/`.pptx`/ODF/EPUB, o el OLE2 heredado para `.doc`/`.xls`/`.ppt`). El sniffing es deliberadamente fail-open: los bytes que no reconoce se dejan pasar en lugar de bloquearse.

---

## Archivos grandes: la lista de comprobación

1. **Comprueba el límite primero.** Un 413 le sale barato a la API y caro a ti, porque subiste el cuerpo entero para ganártelo.
2. **Cuenta con que la subida es síncrona.** `async_mode` solo existe en `url-to-pdf`, `url-to-screenshot` y `url-to-markdown`. Los endpoints de subida de archivos lo ignoran y siempre convierten dentro de la solicitud. Consulta [trabajos síncronos y asíncronos](/es/docs/concepts/sync-and-async.md).
3. **Envía un `job_id` generado por ti.** Si un proxy delante de ti corta la conexión antes de que termine la conversión, el trabajo se completa igualmente. Consulta `GET /v1/convert/status/{job_id}` y obtendrás `{"status": "processing"}`, y luego `{"status": "success", "presigned_url": ..., "object_key": ...}` o `{"status": "failed", "error": ...}`. Reutilizar tu propio ID reinicia ese trabajo; reutilizar el ID de otro proyecto devuelve `409`.
4. **Ten en cuenta los timeouts.** Una solicitud está limitada a 300 segundos de extremo a extremo, tras los cuales obtienes `504` con `{"error": "Request timeout"}`. Las conversiones ofimáticas basadas en LibreOffice tienen su propio tope de 120 segundos, que también se expone como un `504`.
5. **Gestiona el `503` con `Retry-After: 10`.** Las conversiones de archivos se ejecutan detrás de una barrera de admisión con una cola de pendientes acotada. Cuando la cola está llena, la solicitud se rechaza de inmediato en lugar de quedarse esperando, así que reintenta pasado el intervalo indicado en el encabezado.
6. **Para muchos documentos, cambia de endpoint.** `POST /v2/ingest/files` acepta hasta 200 archivos, devuelve `202` de inmediato con un `job_id`, y admite un `webhook_url` para que nunca tengas que hacer sondeo. Consulta [webhooks](/es/docs/guides/webhooks.md).
7. **Descarga enseguida.** Los enlaces de salida están firmados y caducan. Si el tuyo expiró, vuelve a leer el endpoint de estado: cada consulta genera un enlace nuevo sobre el mismo objeto almacenado.

---

## Preguntas frecuentes

### ¿Qué nombre de campo espera la API de EnConvert para subir un archivo?

`file`, enviado como `multipart/form-data`, un archivo por solicitud, en todos los endpoints de conversión V1 que aceptan una subida. La excepción es `POST /v2/ingest/files`, que usa `files` y acepta hasta 200 por trabajo.

### ¿Puede EnConvert descargar el archivo desde una URL en lugar de subirlo yo?

Solo en los endpoints de URL (`url-to-pdf`, `url-to-screenshot`, `url-to-markdown`, `website-to-pdf`, `website-to-screenshot`) y en los endpoints V2 `/v2/perceive` y `/v2/ingest`. Los endpoints de conversión por subida de archivos no tienen parámetro `url`. Cualquier URL que pases debe ser accesible públicamente: las direcciones privadas, de loopback, link-local y de metadatos de nube se rechazan con `400`.

### ¿Por qué mi subida devolvió 413 Payload Too Large?

El archivo superaba el límite por archivo de tu plan, que es de 5 MB en Founding, 15 MB en Indie, 50 MB en Studio y 150 MB en Production. El cuerpo de la respuesta lleva un objeto `detail` con `error`, `file_size`, `max_size`, `tier` y `key_type` para que puedas mostrar al llamante las cifras exactas.

### ¿Por qué mi subida de PNG falla con «File content does not match»?

Los primeros bytes del archivo pertenecen a un formato distinto del que declara su extensión, por ejemplo un PDF renombrado a `.png`. Envía el archivo con su extensión real. Los formatos de texto como `.json` y `.csv` nunca se comprueban a nivel de bytes, así que este error solo aparece con tipos binarios.

### ¿Puedo subir un archivo grande de forma asíncrona?

En los endpoints V1 de subida de archivos no: siempre convierten dentro de la solicitud. Envía un `job_id` generado por el cliente y consulta `GET /v1/convert/status/{job_id}` para sobrevivir a una conexión caída, o usa `POST /v2/ingest/files`, que es asíncrono por diseño y puede llamar a un webhook cuando el trabajo termina.
