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, documentos, imágenes | Subida multipart/form-data, un archivo por solicitud |
file |
Páginas web (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 |
multipart/form-data, muchos archivos por trabajo |
files |
POST /v2/perceive, POST /v2/ingest |
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 y anything-to-markdown. Para convertir una página en vivo a PDF, llama a url-to-pdf en su lugar. Para saber qué formatos acepta cada endpoint, consulta formatos compatibles.
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#
curl -X POST https://api.enconvert.com/v1/convert/anything-to-pdf \
-H "X-API-Key: sk_your_private_key" \
-F "[email protected]" \
-F "direct_download=false"
La respuesta es JSON con un enlace de descarga prefirmado:
{
"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 explica exactamente cuánto.
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#
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:
curl -X POST https://api.enconvert.com/v2/ingest/files \
-H "X-API-Key: sk_your_private_key" \
-F "[email protected]" \
-F "[email protected]" \
-F "[email protected]" \
-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.
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:
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.
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. |
auth, y un encabezado Authorization (por ejemplo, un token Bearer) pasado vía headers, se envían solo al origen de destino, nunca a los subrecursos de terceros que solicita la página. Esto evita fugas de credenciales hacia hosts de publicidad, analítica o CDN.
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 campoauthen 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:
output_filename, si enviaste uno. Si incluiste en él la extensión de destino, esa extensión se elimina primero para que no acabes conreport.pdf_20260405_123456789.pdf.- El nombre del archivo subido sin su extensión.
report.docxproducereport_20260405_123456789.pdf. - Para conversiones de URL, el dominio.
https://example.com/pageproduceexample_20260405_123456789.pdf. - 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:
{
"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.
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.
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:
{
"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:
{
"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-Typede la parte nunca se inspecciona. No hay ninguna lista blanca de MIME en toda la ruta de subida, así queapplication/octet-streamvale. 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.jsonque 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#
- 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.
- Cuenta con que la subida es síncrona.
async_modesolo existe enurl-to-pdf,url-to-screenshotyurl-to-markdown. Los endpoints de subida de archivos lo ignoran y siempre convierten dentro de la solicitud. Consulta trabajos síncronos y asíncronos. - Envía un
job_idgenerado por ti. Si un proxy delante de ti corta la conexión antes de que termine la conversión, el trabajo se completa igualmente. ConsultaGET /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 devuelve409. - Ten en cuenta los timeouts. Una solicitud está limitada a 300 segundos de extremo a extremo, tras los cuales obtienes
504con{"error": "Request timeout"}. Las conversiones ofimáticas basadas en LibreOffice tienen su propio tope de 120 segundos, que también se expone como un504. - Gestiona el
503conRetry-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. - Para muchos documentos, cambia de endpoint.
POST /v2/ingest/filesacepta hasta 200 archivos, devuelve202de inmediato con unjob_id, y admite unwebhook_urlpara que nunca tengas que hacer sondeo. Consulta webhooks. - 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.