Signierte Download-URLs#
EnConvert legt deine konvertierte Datei standardmäßig nicht in den Response-Body. Die Datei wird in den Objektspeicher hochgeladen, und du bekommst eine signierte URL zurück: einen gewöhnlichen HTTPS-Link, der seine Autorisierung im Query-String mitführt und 15 Minuten nach der Ausstellung aufhört zu funktionieren.
Warum die Ausgabe ein Link ist#
Zwei Gründe, beide praktisch.
Die Antwort bleibt klein. Eine Konvertierungsantwort sind ein paar hundert Bytes JSON, egal wie schwer die Ausgabe ist, dein Client parst also eine einzige vorhersagbare Form, ob das Ergebnis nun eine 4 KB große Markdown-Datei oder ein 140 MB großes ZIP einer ganzen Website ist. Es bedeutet außerdem, dass eine Batch-Status-Antwort 400 Ergebnisse tragen kann, ohne 400 Dateien zu tragen.
Die Bytes kommen aus dem Speicher, nicht aus der API. Downloads liefert die
Speicherschicht direkt aus, ein langsamer Client, der ein großes PDF zieht,
hält also keinen API-Worker offen und sitzt nicht hinter denselben
Proxy-Timeouts, die eine Konvertierungsanfrage begrenzen. Der Link braucht
keinen X-API-Key-Header, und genau deshalb kannst du ihn bedenkenlos an
einen Browser, einen Queue-Consumer oder ein curl in einem Shell-Skript
weitergeben.
Wie die URL aussieht#
Es ist eine standardmäßige AWS-SigV4-GET-URL im Path-Style gegen den Storage-Host:
https://<region>.digitaloceanspaces.com/<bucket>/<object_key>
?X-Amz-Algorithm=AWS4-HMAC-SHA256
&X-Amz-Credential=<key>%2F<date>%2F<region>%2Fs3%2Faws4_request
&X-Amz-Date=<timestamp>
&X-Amz-Expires=900
&X-Amz-SignedHeaders=host
&X-Amz-Signature=<hex>
Host und Bucket hängen vom Deployment ab, lies sie also aus der URL, die du
bekommen hast, statt sie fest zu verdrahten. Die Form ändert sich nicht: ein
einfaches GET, keine Header nötig, X-Amz-Expires=900.
Der Object Key darin ist deterministisch und liegt in einem eigenen Namespace pro Projekt:
{env}/files/{project_id}/{endpoint}/{filename}
live/files/4127/v2-perceive/per_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7_markdown.md
Das Signieren ist auf dieses Präfix beschränkt. Ein Projekt kann immer nur
eine Signatur über seine eigenen Keys erhalten, ein object_key aus dem
Projekt einer anderen Person lässt sich also nicht in eine funktionierende
URL verwandeln.
Ablauf: 15 Minuten, V1 und V2#
Jede signierte URL, die EnConvert ausstellt, lebt 900 Sekunden. Es gibt keinen Request-Parameter, um sie länger oder kürzer zu machen.
| Wo sie auftaucht | Feld |
|---|---|
| V1 synchrone Konvertierungsantwort | presigned_url |
| V1 Batch-Status, pro Element | download_url |
| V1 Batch-Status, ZIP-Modus | zip_download_url |
| V1 Job-Status-Abfrage | presigned_url |
| V2 perceive, pro Ausgabe | outputs.<name>.url, zusammen mit expires_in: 900 |
| V2 perceive-Batch, ZIP-Modus | zip.url |
| V2 ingest, bei Abschluss | output_url |
Signierte URLs sind wiederverwendbar, nicht einmalig. Dieselbe URL funktioniert für wiederholte GETs weiter, bis die 15 Minuten abgelaufen sind. Nichts macht sie vorzeitig ungültig, und einmaliges Herunterladen verbraucht sie nicht.
Brauchst du die Datei länger als 15 Minuten, lade die Bytes herunter und speichere sie selbst. Neu signieren gibt dir einen frischen Link, keinen dauerhaften Zugriff.
Neu signieren#
Ein abgelaufener Link ist keine verlorene Datei. Frag die API erneut, und sie erzeugt eine neue Signatur über dasselbe gespeicherte Objekt:
| Job | Neu signieren mit |
|---|---|
| V1 asynchrone oder Batch-Konvertierung | GET /v1/convert/batch/{batch_id} |
| V1 synchrone Konvertierung, per eigener ID abgefragt | GET /v1/convert/status/{job_id} |
| V2 perceive-Operation | GET /v2/perceive/{operation_id} |
| V2 perceive-Batch | GET /v2/perceive/batch/{job_id} |
| V2 ingest-Job | GET /v2/ingest/{job_id} |
Jeder dieser Endpunkte baut die URLs bei jedem Aufruf neu aus den persistierten Object Keys auf. Neu signieren rendert nichts, konvertiert nichts und kostet keine Ops. Es funktioniert, solange das Objekt noch im Speicher liegt, und das ist die andere Uhr auf dieser Seite.
Ein Verhalten, für das du Code schreiben solltest: Lässt sich die Signatur
nicht erzeugen, kommt das Feld als null zurück, statt dass die Anfrage
fehlschlägt. Eine Status-Abfrage liefert wegen eines veralteten Keys nie eine
500. Prüfe also url, download_url und output_url auf null, bevor du
sie dereferenzierst.
Aufbewahrung ist eine andere Uhr#
Das ist die Unterscheidung, die am häufigsten falsch verstanden wird, hier also je eine Zeile:
- Der Signatur-Ablauf (15 Minuten) entscheidet, wie lange eine bestimmte URL funktioniert.
- Die Aufbewahrung (Stunden bis Tage, je nach Plan) entscheidet, wie lange die Datei überhaupt existiert.
Sie sind voneinander unabhängig, und deshalb sind beide verwirrenden Fälle real:
Eine abgelaufene URL heißt nicht, dass die Datei weg ist. Fünfzehn Minuten nach einer Konvertierung ist der Link tot, das Objekt aber mit ziemlicher Sicherheit noch da. Frag den Job erneut ab, und du bekommst einen funktionierenden Link zurück.
Eine gültige URL garantiert nicht, dass die Datei noch da ist. Signierst du eine Ausgabe im Founding-Plan nach 59 Minuten neu und nutzt den Link nach 62 Minuten, kann der Aufbewahrungs-Sweep das Objekt dazwischen gelöscht haben. Die Signatur ist gültig; das Objekt nicht. Der Download scheitert in der Speicherschicht, nicht in der API.
Die Aufbewahrungsdauer wird pro Plan über dein Abo gesetzt, und das Fenster des Founding-Plans beträgt eine Stunde, kurz genug, um es während der Entwicklung versehentlich zu treffen. Die Tabelle pro Plan steht in Rate-Limits und Kontingente.
Zwei weitere Dinge zur Aufbewahrung, die du kennen solltest:
- Das Löschen wird eingeplant und dann in Intervallen abgearbeitet, eine Datei kann ihr Fenster also um ein paar Minuten überleben. Baue nichts darauf. Das ist Spiel im Sweeper, keine Kulanzfrist.
- Projekte mit einem Storage-Add-on bekommen überhaupt keine geplanten Löschungen. Ihre Ausgaben bleiben, bis sie bewusst entfernt werden, und zählen stattdessen gegen das Speicherkontingent des Add-ons.
Unabhängig vom Fenster deines Plans werden gerenderte HTML-Captures, die für
das Render-Quality-Scoring genutzt werden, 90 Tage aufbewahrt und lassen sich
pro Anfrage mit dem Header X-Enconvert-No-Capture: true abschalten.
Quelldateien, die an POST /v2/ingest/files hochgeladen werden, werden
gelöscht, sobald das JSONL zusammengebaut ist, mit einer 24-Stunden-Absicherung
für den Fall, dass vorher etwas schiefgeht.
direct_download: die Bytes stattdessen streamen#
Wenn dir das Folgen einer URL ein Hop zu viel ist, fordere die Bytes im Response-Body an.
Bei V2 perceive#
direct_download: true bei POST /v2/perceive ersetzt den JSON-Envelope
vollständig. Der Response-Body ist das Artefakt, ausgeliefert mit dem eigenen
Content-Type des Artefakts.
curl -X POST https://api.enconvert.com/v2/perceive \
-H "X-API-Key: sk_your_private_key" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/pricing",
"outputs": ["markdown"],
"direct_download": true
}' \
--output pricing.md
Es braucht genau eine artefakterzeugende Ausgabe: markdown,
html_cleaned, html_raw, screenshot, screenshot_full_page, pdf,
links oder images, die alle auf
der perceive-Seite beschrieben sind. Forderst
du zwei an, antwortet der Aufruf mit 400 und listet auf, was du gesendet
hast. structured zählt nicht, weil es inline-JSON und keine gespeicherte
Datei ist, es darf also mitfahren, ohne die Regel zu brechen.
Die Metadaten, die im JSON-Body gestanden hätten, wandern in Header:
X-Operation-Id, X-Object-Key und X-Cache-Hit immer, dazu
X-Render-Quality, X-Source-Status-Code, X-Content-Hash und
X-Warnings-Count, sofern diese Werte existieren. Der Body trägt außerdem
Content-Disposition: attachment, Content-Length und
Cache-Control: no-transform.
Zwei GET-Endpunkte nehmen direct_download als Query-Parameter:
GET /v2/perceive/{operation_id}?direct_download=true&output=markdownstreamt ein Artefakt aus einer vergangenen Operation.outputist erforderlich, wenn die Operation mehr als ein Artefakt erzeugt hat, und ein unbekannter Name liefert404.GET /v2/perceive/batch/{job_id}?direct_download=truestreamt das Batch-ZIP für Batches mitoutput_mode: "zip", deren Archiv fertig ist, und antwortet sonst mit400.
POST /v2/perceive/batch lehnt direct_download mit 422 ab. Setze
output_mode auf "zip" und lade stattdessen das Archiv herunter.
410 Gone und einer Meldung, die dich auffordert, die Anfrage erneut auszuführen, statt still leere Bytes zurückzugeben. Gespart wird ein Roundtrip, kein Schreibvorgang im Speicher.
Bei V1-Konvertierungen#
V1 hat ebenfalls ein direct_download-Feld, mit anderem Verhalten als dem
von V2. Auf den URL-Endpunkten entscheidet es, ob die API rohe Datei-Bytes
oder einen JSON-Body mit einer vorsignierten Download-URL zurückgibt. Auf den
Datei-Upload-Endpunkten wird es nur akzeptiert, damit die Request-Form passt,
und bleibt wirkungslos: Diese Endpunkte antworten immer mit dem JSON-Body.
| Endpunkt-Typ | Schlüsseltyp | Standard | Was zurückkommt |
|---|---|---|---|
| Datei-Upload-Endpunkte | Alle Schlüssel | true |
JSON mit presigned_url, egal was du setzt. Das Feld ist hier wirkungslos. |
| URL-Endpunkte | Privater Schlüssel | false |
JSON mit presigned_url. Setze true für rohe Bytes. |
| URL-Endpunkte | Öffentlicher Schlüssel / Dashboard-Schlüssel | true (erzwungen) |
JSON mit presigned_url |
direct_download auf true erzwungen, die Antwort ist aber ein JSON-Objekt mit einer presigned_url (keine rohen Bytes). Das vermeidet Reverse-Proxy-Timeout-Probleme bei großen Dateien, deren Konvertierung 60 bis 120 Sekunden dauern kann.
Auf einem URL-Endpunkt liefert ein privater Schlüssel mit direct_download=true
rohe Datei-Bytes:
curl -X POST https://api.enconvert.com/v1/convert/url-to-pdf \
-H "X-API-Key: sk_your_private_key" \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "direct_download": true}' \
--output output.pdf
Einschränkungen:
direct_downloadlässt sich nicht mitasync_mode: truekombinieren (liefert400)direct_downloadlässt sich nicht mit mehreren URLs kombinieren (liefert400)
Beide Einschränkungen folgen aus derselben Tatsache: Sobald der Aufruf mit
202 geantwortet hat, ist keine Antwort mehr übrig, in die die Bytes passen.
Asynchrone Ergebnisse holst du per Polling oder Webhook ab. Siehe
Synchrone und asynchrone Jobs.
Response-Header#
Konvertierungen per Datei-Upload und URL-Konvertierungen mit einem öffentlichen oder Dashboard-Schlüssel wiederholen die Konvertierungs-Metadaten zusätzlich zum JSON-Body in Headern:
| Header | Beschreibung |
|---|---|
X-Object-Key |
Speicherpfad der konvertierten Datei |
X-File-Size |
Größe der konvertierten Datei in Bytes |
X-Conversion-Time |
Dauer der Konvertierung in Sekunden |
X-Filename |
Generierter Dateiname |
Eine Antwort mit rohen Bytes (URL-Endpunkt, privater Schlüssel,
direct_download: true) trägt diese vier plus
Content-Disposition: attachment; filename="{filename}", Content-Length und
Cache-Control: no-transform. Eine URL-Konvertierung, die einem privaten
Schlüssel JSON zurückgibt, trägt keinen davon; lies dort den Body.
Häufig gestellte Fragen#
Wie lange bleiben signierte Download-URLs von EnConvert gültig?#
Fünfzehn Minuten bzw. 900 Sekunden, bei V1 wie bei V2. Der
V2-Artefakt-Envelope gibt es explizit als expires_in: 900 an. Es gibt
keinen Parameter, um das zu verlängern. Ruf den Job oder die Operation erneut
ab, um eine frische URL über dieselbe Datei zu erzeugen.
Kann ich eine signierte URL mehr als einmal verwenden?#
Ja. Signierte URLs sind nicht einmalig. Derselbe Link bedient wiederholte GETs, bis die 15 Minuten abgelaufen sind, und ein Download macht ihn nicht ungültig.
Meine Download-URL ist abgelaufen. Wurde die Datei gelöscht?#
Mit ziemlicher Sicherheit nicht. Signatur-Ablauf und Datei-Aufbewahrung sind
getrennte Uhren. Frag den Job oder die Operation erneut ab
(GET /v1/convert/batch/{batch_id}, GET /v2/perceive/{operation_id},
GET /v2/ingest/{job_id}), und du bekommst einen frisch signierten Link ohne
Op-Kosten, solange die Datei noch im Aufbewahrungsfenster deines Plans liegt.
Wie lange bewahrt EnConvert meine konvertierten Dateien auf?#
Die Aufbewahrung wird pro Plan gesetzt, und der Founding-Plan behält Ausgaben eine Stunde lang. Bezahlte Pläne behalten sie länger, und Projekte mit einem Storage-Add-on werden überhaupt nie geleert. Die Zahlen pro Plan stehen in Rate-Limits und Kontingente.
Was bedeutet ein 410 Gone bei einem perceive-Artefakt?#
Das Objekt hat das Aufbewahrungsfenster deines Plans überschritten und wurde
aus dem Speicher gelöscht. direct_download liest das Artefakt vor dem
Streamen aus dem Speicher zurück, ein herausgefallenes Artefakt antwortet
deshalb mit 410 statt mit einem leeren Body. Führe die perceive-Anfrage
erneut aus, um es neu zu erzeugen.
Wie bekomme ich rohe Bytes statt einer Download-URL?#
Setze direct_download: true. Bei POST /v2/perceive verlangt das genau
eine artefakterzeugende Ausgabe, und der Response-Body wird zu diesem
Artefakt. Bei V1-URL-Endpunkten mit einem privaten Schlüssel liefert es die
Datei-Bytes, und es lässt sich nicht mit async_mode oder mit mehreren URLs
kombinieren.