Datei-Ingestion#

Es gibt zwei Wege, Bytes an EnConvert zu übergeben: Lade die Datei selbst als multipart/form-data hoch, oder übergib eine url und lass die API die Ressource abrufen. Welchen du nutzen kannst, hängt vom Endpunkt ab, nicht von deinem Plan.


Welche Endpunkte was entgegennehmen#

Endpunkt-Familie Wie die Bytes ankommen Feldname
Datenformate, Dokumente, Bilder multipart/form-data-Upload, eine Datei pro Anfrage file
Webseiten (url-to-pdf, url-to-screenshot, url-to-markdown, website-to-pdf, website-to-screenshot) JSON-Body, EnConvert ruft die Seite ab url
POST /v2/ingest/files multipart/form-data, viele Dateien pro Job files
POST /v2/perceive, POST /v2/ingest JSON-Body, EnConvert ruft die Seite ab url

Einen dritten Weg gibt es nicht. Die Datei-Upload-Endpunkte rufen keine URL für dich ab, auch nicht die Sammelrouten anything-to-pdf und anything-to-markdown. Um eine Live-Seite in ein PDF zu verwandeln, rufe stattdessen url-to-pdf auf. Welche Formate jeder Endpunkt akzeptiert, steht unter Unterstützte Formate.


Eine lokale Datei hochladen#

Das Formularfeld heißt file, und jeder V1-Datei-Upload-Endpunkt nimmt genau eines entgegen. Alles Weitere im Formular ist optional.

Formularfeld Typ Beschreibung
file file Die zu konvertierende Datei. Ihre Dateiendung muss vom Endpunkt akzeptiert werden.
output_filename string Eigener Basisname für die Ausgabe. Die Zielendung wird für dich angehängt.
job_id string Vom Client vergebene Job-ID für die Timeout-Wiederherstellung. Frage GET /v1/convert/status/{job_id} ab, falls die Verbindung abbricht.
pdf_options string JSON-String mit PDF-Optionen, bei Endpunkten, die ein PDF erzeugen.
direct_download boolean Wird nur akzeptiert, damit die Request-Form zu den URL-Endpunkten passt. Hier hat es keine Wirkung: Ein Upload antwortet immer mit der JSON-Antwort unten, egal was du sendest.

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"

Die Antwort ist JSON mit einem vorsignierten Download-Link:

{
    "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
}

Dieselben Werte werden in den Response-Headern X-Object-Key, X-File-Size, X-Conversion-Time und X-Filename gespiegelt, du kannst sie also lesen, ohne den Body zu parsen. Hole die Datei zügig ab: Der Link ist kurzlebig, und Signierte URLs erklärt genau, wie kurz.

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));

Viele Dateien in einem Aufruf#

POST /v2/ingest/files ist der einzige Endpunkt, der mehr als eine Datei entgegennimmt. Wiederhole das Feld files, bis zu 200 Dateien pro Job:

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"

Jede Datei wird zu Markdown konvertiert, gechunkt und zu einem einzigen JSONL-Artefakt zusammengesetzt. Der Job ist immer asynchron und antwortet mit 202 Accepted und einer job_id. Alle Details stehen auf der Seite zum Ingest-Endpunkt.


EnConvert die Datei abrufen lassen#

Bei den URL-Endpunkten sendest du einen JSON-Body statt eines Formulars, und die API übernimmt das Abrufen:

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 akzeptiert einen String oder ein Array von Strings. Ein Array schaltet die Anfrage auf asynchron um und wird unter Batch-Verarbeitung behandelt.

Quellen hinter einem Login#

Drei optionale Felder erlauben es dem Abruf, Anmeldedaten mitzuführen. Alle drei erfordern einen Plan mit Basic-Auth-Zugriff (Indie und höher).

Parameter Typ Standard Beschreibung
auth object null HTTP-Basic-Auth-Anmeldedaten: {"username": "...", "password": "..."}.
cookies array null Array von Cookie-Objekten, die vor der Navigation injiziert werden. Max. 50 pro Anfrage. Jedes benötigt name, value und entweder domain oder url.
headers object null Eigene HTTP-Header, die mit den Anfragen gesendet werden. Max. 20 pro Anfrage. Blockierte Header sind nicht erlaubt: host, content-length, transfer-encoding, connection, upgrade, te, trailer.
Scope der Anmeldedaten: Anmeldedaten aus dem auth-Objekt und ein über headers übergebener Authorization-Header (etwa ein Bearer-Token) gehen nur an den Ziel-Origin, niemals an Drittanbieter-Subressourcen, die die Seite anfordert. Das verhindert, dass Anmeldedaten an Werbe-, Analytics- oder CDN-Hosts abfließen.

Private und interne Adressen#

Die url muss eine öffentliche http://- oder https://-Adresse sein. Bevor irgendetwas abgerufen wird, wird sie geprüft und mit 400 Bad Request abgelehnt, wenn sie:

  • ein anderes Schema als http/https verwendet;
  • Anmeldedaten einbettet, etwa https://user:pass@host/ (nutze stattdessen das Feld auth);
  • auf localhost, einen Cloud-Metadaten-Hostnamen oder eine IP zielt, die in einen privaten, Loopback-, Link-Local-, reservierten oder anderweitig nicht öffentlichen Bereich auflöst;
  • eine nicht standardmäßige IP-Notation (oktal, hexadezimal oder als gepackte Ganzzahl) verwendet, die mehrdeutig auflösen könnte.

Das gilt für die Seed-URL, für jede URL in einem Batch und für die Seiten, die die website-to-*-Crawl-Endpunkte entdecken.

Klartext: EnConvert läuft außerhalb deines Netzwerks. Es erreicht weder http://10.0.0.5/report.docx noch einen .internal-Hostnamen noch irgendetwas, das nur innerhalb deiner VPC auflöst. Mach die Datei entweder aus dem öffentlichen Internet erreichbar, oder lies die Bytes selbst und lade sie hoch.


Was mit deinem Dateinamen passiert#

Der Name, den du sendest, erledigt zwei Aufgaben.

Er wählt den Konverter. Die Dateiendung entscheidet, welcher Eingabepfad läuft, benenne die Datei also korrekt. Eine Datei namens report ohne Endung wird von jedem Endpunkt abgelehnt, der eine Endungs-Allowlist hat.

Er bildet die Grundlage für den Ausgabenamen. Der Ausgabedateiname wird so gebaut:

{base}_{YYYYMMDD_HHMMSSmmm}.{ext}

Der UTC-Zeitstempel wird immer angehängt, zwei Konvertierungen derselben Datei kollidieren also nie. base wird in dieser Reihenfolge aufgelöst:

  1. output_filename, falls du eines gesendet hast. Wenn du die Zielendung darin mitgeschickt hast, wird die Endung zuerst entfernt, damit du nicht report.pdf_20260405_123456789.pdf bekommst.
  2. Der Name der hochgeladenen Datei ohne ihre Endung. report.docx erzeugt report_20260405_123456789.pdf.
  3. Bei URL-Konvertierungen die Domain. https://example.com/page erzeugt example_20260405_123456789.pdf.
  4. Wenn nichts davon greift, das wörtliche output.

Der Storage-Key wird bereinigt, bevor das Ergebnis geschrieben wird: Nur der Basisname bleibt übrig, .. wird entfernt, die Zeichen <>:"|?* werden gestrippt, und Leerzeichen werden zu Unterstrichen. Lade My Report (final).docx hoch, und das PDF landet unter My_Report_(final)_20260405_123456789.pdf. Dieser Pfad wird dir als object_key zurückgegeben und hat die Form {env}/files/{project_id}/{endpoint}/{filename}.

Dateinamen, die an POST /v2/ingest/files gesendet werden, sind zusätzlich auf 255 Zeichen begrenzt.


Größenobergrenze und der 413#

Die Upload-Obergrenze gilt je Plan und für jede einzelne Datei.

Plan Max. Upload-Größe Bytes
Founding 5 MB 5242880
Indie 15 MB 15728640
Studio 50 MB 52428800
Production 150 MB 157286400
Enterprise Verhandelbar Laut Vertrag

Die Größe wird am hochgeladenen Part selbst gemessen, während der Body hereinströmt, nicht an einem Header, den du kontrollierst; ein Chunked-Upload ohne Content-Length wird also genauso geprüft. Eine Überschreitung liefert 413 Payload Too Large, bevor irgendeine Konvertierungsarbeit passiert und bevor Ops berechnet werden:

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

file_size und max_size sind beide in Bytes. tier ist der Plan-Slug (free, starter, pro, business, enterprise), nicht der Anzeigename, den du auf der Preisseite siehst; ein Studio-Projekt meldet also "tier": "pro". key_type ist private, public oder dashboard, mit Rückfall auf unknown.

5 MB sind schnell weg. Im Founding-Plan liegt ein gescanntes 40-seitiges PDF oder ein Deck mit ein paar randlosen Fotos meist schon darüber. Es gibt keinen Weg für Chunked- oder fortsetzbare Uploads: Die Lösung ist eine kleinere Datei oder ein größerer Plan.

Das Größen-Gate zu passieren ist nicht die letzte Hürde. Ein aufgebrauchtes Monatskontingent antwortet mit 402 Payment Required, und zu viele Anfragen in einem Zeitfenster antworten mit 429; beides ist unter Rate-Limits und Kontingente beschrieben.


Content-Type und Magic Bytes#

Uploads durchlaufen zwei Prüfungen, in dieser Reihenfolge.

1. Die Endungs-Allowlist. Jeder Endpunkt deklariert, welche Dateiendungen er akzeptiert. Eine Abweichung ist 400 Bad Request:

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

2. Der Magic-Byte-Test. Die ersten Bytes der Datei werden mit der Gruppe verglichen, die ihre Endung behauptet. Eine Abweichung mit hoher Konfidenz ist ebenfalls 400:

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

Genau das bekommst du, wenn du ein PDF in .png umbenennst und hochlädst: Die Bytes beginnen mit %PDF-, die Endung sagt PNG, und beide widersprechen sich. Die Prüfung existiert, weil die Endung deine Anfrage routet. Ohne sie erreichen PDF-Bytes einen Bild-Decoder, und du bekommst einen undurchsichtigen Fehler tief im Konverter statt eines klaren 400 an der Tür, und eine absichtlich falsch bezeichnete Datei landet bei einem Parser, der sie nie zu sehen bekommen sollte.

Zwei Dinge, die das nicht tut, beide wissenswert:

  • Der Content-Type des Parts wird nie inspiziert. Es gibt nirgends im Upload-Pfad eine MIME-Allowlist, application/octet-stream ist also in Ordnung. Die Dateiendung ist das Einzige, was die Anfrage routet, und genau deshalb schlägt eine Datei ohne Endung fehl.
  • Textformate haben keine verlässliche Signatur und überspringen den Test komplett: .json, .csv, .xml, .yaml, .toml, .md, .html, .svg, .txt. Eine .json-Datei, die in Wahrheit CSV enthält, wird an dieser Stelle akzeptiert und scheitert später, im Parser.

Die erkannten Binärsignaturen sind PNG, JPEG, GIF, WebP, HEIC/HEIF, PDF und die Office-Gruppe (ein ZIP-Container für .docx/.xlsx/.pptx/ODF/EPUB, oder das alte OLE2 für .doc/.xls/.ppt). Der Test ist bewusst fail-open: Bytes, die er nicht erkennt, werden durchgelassen statt blockiert.


Große Dateien: die Checkliste#

  1. Prüfe zuerst die Obergrenze. Ein 413 ist für die API billig und für dich teuer, denn du hast den kompletten Body hochgeladen, um ihn zu bekommen.
  2. Rechne mit einem synchronen Upload. async_mode gibt es nur bei url-to-pdf, url-to-screenshot und url-to-markdown. Datei-Upload-Endpunkte ignorieren es und konvertieren immer innerhalb der Anfrage. Siehe Synchrone und asynchrone Jobs.
  3. Sende eine selbst erzeugte job_id. Wenn ein vorgelagerter Proxy die Verbindung kappt, bevor die Konvertierung fertig ist, läuft die Arbeit trotzdem zu Ende. Frage GET /v1/convert/status/{job_id} ab, und du bekommst {"status": "processing"}, dann entweder {"status": "success", "presigned_url": ..., "object_key": ...} oder {"status": "failed", "error": ...}. Deine eigene ID erneut zu verwenden startet diesen Job neu; die ID eines anderen Projekts zu verwenden liefert 409.
  4. Kalkuliere die Timeouts ein. Eine Anfrage ist Ende zu Ende auf 300 Sekunden gedeckelt, danach bekommst du 504 mit {"error": "Request timeout"}. Office-Konvertierungen auf LibreOffice-Basis haben ihre eigene 120-Sekunden-Grenze, die ebenfalls als 504 erscheint.
  5. Behandle 503 mit Retry-After: 10. Dateikonvertierungen laufen hinter einem Admission-Gate mit einer begrenzten Warteschlange. Ist die Warteschlange voll, wird die Anfrage sofort abgelehnt, statt sich anzustellen; wiederhole sie also nach dem Intervall aus dem Header.
  6. Bei vielen Dokumenten: wechsle den Endpunkt. POST /v2/ingest/files nimmt bis zu 200 Dateien, antwortet sofort mit 202 und einer job_id und akzeptiert eine webhook_url, sodass du nie pollen musst. Siehe Webhooks.
  7. Lade zügig herunter. Ausgabelinks sind signiert und laufen ab. Ist deiner abgelaufen, lies den Status-Endpunkt erneut: Jede Abfrage erzeugt einen frischen Link auf dasselbe gespeicherte Objekt.

Häufig gestellte Fragen#

Welchen Feldnamen erwartet die EnConvert-API für einen Datei-Upload?#

file, als multipart/form-data gesendet, eine Datei pro Anfrage, bei jedem V1-Konvertierungsendpunkt, der einen Upload entgegennimmt. Die Ausnahme ist POST /v2/ingest/files, das files verwendet und bis zu 200 Dateien pro Job akzeptiert.

Kann EnConvert die Datei von einer URL herunterladen, statt dass ich sie hochlade?#

Nur bei den URL-Endpunkten (url-to-pdf, url-to-screenshot, url-to-markdown, website-to-pdf, website-to-screenshot) und den V2-Endpunkten /v2/perceive und /v2/ingest. Die Datei-Upload-Konvertierungsendpunkte haben keinen url-Parameter. Jede URL, die du übergibst, muss öffentlich erreichbar sein: Private, Loopback-, Link-Local- und Cloud-Metadaten-Adressen werden mit 400 abgelehnt.

Warum hat mein Upload 413 Payload Too Large zurückgegeben?#

Die Datei war größer als die Obergrenze pro Datei deines Plans, die bei Founding 5 MB, bei Indie 15 MB, bei Studio 50 MB und bei Production 150 MB beträgt. Der Response-Body trägt ein detail-Objekt mit error, file_size, max_size, tier und key_type, sodass du dem Aufrufer die exakten Zahlen zeigen kannst.

Warum scheitert mein PNG-Upload mit „File content does not match“?#

Die ersten Bytes der Datei gehören zu einem anderen Format als dem, das ihre Endung behauptet, zum Beispiel ein PDF, das in .png umbenannt wurde. Sende die Datei unter ihrer echten Endung. Textformate wie .json und .csv werden nie byteweise geprüft, dieser Fehler erscheint also nur bei Binärtypen.

Kann ich eine große Datei asynchron hochladen?#

Nicht bei den V1-Datei-Upload-Endpunkten: Sie konvertieren immer innerhalb der Anfrage. Sende eine clientseitig erzeugte job_id und frage GET /v1/convert/status/{job_id} ab, um einen Verbindungsabbruch zu überstehen, oder nutze POST /v2/ingest/files, das per Design asynchron ist und einen Webhook aufrufen kann, wenn der Job fertig ist.