Web Scraping API für Markdown, Screenshots und strukturierte Daten#
POST /v2/perceive ist die Web-Scraping-API von EnConvert: Sie rendert
eine URL einmal in einem echten Headless-Browser (JavaScript wird
ausgeführt, Lazy-Content wird geladen) und liefert aus diesem einen
Render jede Ausgabe zurück, die du anforderst: sauberes Markdown
(standardmäßig nur der Hauptinhalt, Site-Chrome entfernt), bereinigtes
oder rohes HTML, einen Screenshot, ein PDF, das Link- und Bild-Inventar
sowie strukturierte Daten (Seitenmetadaten, JSON-LD, Überschriften,
Tabellen). Datei-Ausgaben kommen als kurzlebige, vorsignierte Download-URLs
zurück, der strukturierte Block inline, und Batches mit mehr als 10 URLs
laufen asynchron hinter einer abgefragten job_id. Eine einzige Anfrage
ersetzt einen ganzen Stapel einzelner Aufrufe: url-to-markdown,
url-to-screenshot, url-to-pdf, plus dein eigenes Scraping.
Hier ist der kleinste sinnvolle Aufruf. Sende eine URL und erhalte sauberes Markdown sowie die strukturierten Metadaten der Seite zurück:
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", "structured"]
}'
Die Antwort enthält eine vorsignierte Download-URL für die Markdown-Datei sowie den strukturierten Block inline:
{
"operation_id": "per_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7",
"status": "completed",
"url": "https://example.com/pricing",
"url_final": "https://example.com/pricing",
"content_hash": "9f2b8c1a...d4e5",
"render_quality": 0.93,
"cache_hit": false,
"outputs": {
"markdown": {
"url": "https://spaces.example.com/...signed...",
"object_key": "env/files/4127/v2-perceive/per_3f9a..._markdown.md",
"size_bytes": 8421,
"content_type": "text/markdown; charset=utf-8",
"expires_in": 900
}
},
"structured": {
"metadata": {
"title": "Pricing",
"description": "Simple, usage-based pricing."
},
"structured_data": [
{"@type": "Product", "name": "Studio", "offers": {"price": "99"}}
]
},
"extraction_tier": "heuristic",
"tokens": {"input": 0, "output": 0},
"cost_cents": 0.0,
"duration_ms": 6230,
"warnings": []
}
Endpunkte#
| Methode | Pfad | Zweck |
|---|---|---|
POST |
/v2/perceive |
Erfasst eine einzelne URL und liefert die angeforderten Ausgaben zurück. |
GET |
/v2/perceive/{operation_id} |
Ruft eine vergangene Operation mit neu signierten Download-URLs erneut ab. |
POST |
/v2/perceive/batch |
Erfasst bis zu 1,000 URLs, die sich einen Satz Optionen teilen. |
GET |
/v2/perceive/batch/{job_id} |
Fragt den Status und die Ergebnisse pro URL eines Batches ab. |
DELETE |
/v2/perceive/batch/{job_id} |
Bricht einen laufenden Batch ab. |
Content-Type: application/json bei jedem POST.
Authentifizierung#
Authentifiziere dich mit einem privaten Schlüssel im X-API-Key-Header
für Server-zu-Server-Aufrufe. Diesen Weg verwenden die folgenden
Beispiele.
X-API-Key: sk_your_private_key
Öffentliche Schlüssel mit einem JWT-Bearer-Token funktionieren ebenfalls,
nach demselben Ablauf wie bei jedem anderen Endpunkt: Erzeuge ein Token
mit deinem pk_-Schlüssel und sende es dann als
Authorization: Bearer <token>. Den vollständigen Ablauf, einschließlich
Domain-Lock und Token-Refresh, findest du im
Authentifizierungsleitfaden.
Jeder API-Schlüssel hat eine Allowlist erlaubter Endpunkte. Steht
/v2/perceive nicht auf der Liste des Schlüssels, wird die Anfrage mit
403 abgelehnt.
Wie perceive funktioniert#
Eine Anfrage löst einen Browser-Render über ein gemeinsam genutztes Headless-Chrome-Singleton aus und materialisiert anschließend jede Ausgabe aus diesem Render. Du bezahlst innerhalb eines einzelnen Aufrufs nie zweimal für dieselbe Seite.
- Render. Die Seite wird über einen automatischen Multi-Engine-Fallback abgerufen: zuerst ein schneller Echt-Browser-TLS-Fingerprint, mit Eskalation zu Headless-Chrome, wenn die Seite blockiert ist oder JavaScript benötigt, und noch einmal zu einem Stealth-gehärteten Render, wenn eine Seite weiterhin durch Anti-Bot-Schutz blockiert wirkt, sodass mehr reale Seiten mit nutzbarem Inhalt zurückkommen. Im Browser werden Cookie-Banner geschlossen, die Seite wird gescrollt, um Lazy-Loaded-Content auszulösen, Sticky-Header werden behandelt, und Bilder erhalten Zeit zum Laden. Es ist dieselbe Capture-Pipeline, die auch den url-to-pdf-Endpunkt antreibt.
- Materialisieren. Aus dem gerenderten DOM erstellt perceive alles,
was du in
outputsangegeben hast: Markdown, bereinigtes/rohes HTML, Links, Bilder, einen Screenshot, ein PDF. Das DOM wird zuvor normalisiert, damit das Markdown widerspiegelt, was ein Leser sieht: Code-Fences behalten ihre Sprache, Card-Links ihre Struktur, und Interface-Elemente werden unteronly_main_contententfernt. Siehe Markdown-Qualität. - Extrahieren. Wenn du die Ausgabe
structuredangefordert hast, führt perceive einen heuristischen Durchlauf für Seitenmetadaten, JSON-LD, Überschriften und Tabellen aus. Wenn du zusätzlich einschemasendest und dein Plan die LLM-Stufe enthält, füllt ein LLM-gestütztes Modell das Schema, wenn der heuristische Durchlauf nicht ausreicht. - Bewerten. Ein Render-Qualitäts-Score (0.0–1.0) unterscheidet
einen echten Render von einem fehlgeschlagenen. Werte unter 0.40
bedeuten einen fehlgeschlagenen Render: eine Anti-Bot-Seite, eine
Login-Wall, eine HTTP-Fehlerseite, ein Soft-404 oder eine leere
Hülle. Sieh in
deductionsnach dem Grund und instatus_codenach dem Upstream-Status.
Binäre und Text-Ausgaben (Markdown, HTML, Screenshots, PDFs, das Link-
und Bild-JSON) werden in den Storage hochgeladen und als
vorsignierte URLs zurückgegeben, die nach 15 Minuten ablaufen. Der
structured-Block wird inline im JSON zurückgegeben. Rufe eine
Operation mit GET /v2/perceive/{operation_id} erneut ab, um einen
neuen Satz signierter URLs zu erhalten.
Request-Parameter#
Die Validierung ist strikt: Ein Request-Key, den das Schema nicht kennt,
wird mit 422 abgelehnt, wobei das betroffene Feld benannt wird.
Unbekannte Keys werden nie still ignoriert. Jeder 422-Body enthält
außerdem ein Top-Level-Array errors mit menschenlesbaren Meldungen
neben der maschinenlesbaren detail-Liste.
Kern#
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
url |
string |
-- | Die zu erfassende Seite. Muss mit http:// oder https:// beginnen. Maximal 2,048 Zeichen. Erforderlich. |
outputs |
string[] |
["markdown", "structured"] |
Welche Ausgaben erzeugt werden sollen. Siehe Ausgaben. |
extract |
string[] |
[] |
Welche strukturierten Felder abgerufen werden, wenn structured in outputs enthalten ist. Siehe Strukturierte Extraktion. |
schema |
object |
null |
Ein JSON-Schema, das die zu extrahierenden Felder beschreibt. Löst die LLM-Extraktionsstufe auf Plänen aus, die sie enthalten. |
only_main_content |
boolean |
true |
Entfernt Site-Chrome (Navigation, Header, Footer, Sidebars, Cookie-Banner, versteckte Knoten) sowie Interface-Elemente (Buttons, Tab-Leisten, „War diese Seite hilfreich?“-Widgets, nur für Screenreader bestimmte Labels, Breadcrumbs) aus der markdown-Ausgabe und dem main_content-Extract, abgesichert durch einen Fidelity-Guard: Würde das Entfernen zu viel echten Inhalt streichen, wird stattdessen die vollständige Seite zurückgegeben und eine Warnung hinzugefügt. Bild-URLs werden als ihr Alt-Text gerendert (die vollständige Bildliste bleibt über outputs: ["images"] verfügbar). Setze false für die vollständige Seite, ohne dass etwas entfernt wird. Siehe Markdown-Qualität. |
truncate_data_arrays |
boolean |
nicht gesetzt | Kürzt lange Folgen numerischer Literale (rohe Embedding-Vektoren, Tensor-Dumps aus Notebook-Ausgabezellen) auf eine führende Stichprobe plus Anzahl, z. B. ... [truncated 1520 of 1536 values]. Nicht gesetzt folgt only_main_content: aktiv, wenn die Seite aufbereitet wird, inaktiv, wenn du die Seite unverändert angefordert hast. Setze true oder false, um es explizit zu steuern. |
allow_degraded |
boolean |
false |
Gibt den Render auch dann zurück, wenn es sich um eine Anti-Bot-Challenge oder Blockierseite ohne Seiteninhalt handelt. Standardmäßig schlägt ein solcher Render mit 502 fehl, statt den Text der Zwischenseite so auszuliefern, als wäre er die Seite. |
direct_download |
boolean |
false |
Liefert die Artefakt-Bytes direkt als HTTP-Response-Body statt eines JSON-Envelopes. Erfordert genau eine Artefakt-erzeugende Ausgabe. Nur für Einzel-URL-Anfragen, denn der Batch-Endpunkt lehnt es mit 422 ab. Siehe Direct Download. |
cache_mode |
string |
"enabled" |
enabled, bypass oder refresh. Siehe Caching. |
Ausgaben#
outputs akzeptiert jede Kombination dieser Namen:
| Output | Rückgabe als | Was du erhältst |
|---|---|---|
markdown |
signierte URL | Sauberes Markdown der Seite. Mit only_main_content (Standard true) wird Site-Chrome wie Navigation, Header, Footer, Sidebars, Cookie-Banner und versteckte Knoten hinter einem Fidelity-Guard entfernt, und Bild-URLs werden als ihr Alt-Text gerendert. Code-Blöcke behalten in beiden Modi ihre Sprache am Fence (```python). Setze only_main_content: false für die vollständige Seite. Siehe Markdown-Qualität. |
html_cleaned |
signierte URL | Das gerenderte HTML, bereinigt um Skripte, Styles und Boilerplate. |
html_raw |
signierte URL | Das vollständige gerenderte HTML, exakt so, wie der Browser es erzeugt hat. |
screenshot |
signierte URL | Ein Viewport-PNG in der angeforderten (oder Standard-) Viewport-Größe. |
screenshot_full_page |
signierte URL | Ein Full-Page-PNG, das die gesamte Scroll-Höhe erfasst. |
pdf |
signierte URL | Ein PDF der Seite. Akzeptiert die vollständige pdf_options-Oberfläche (siehe unten). |
links |
signierte URL | Ein JSON-Array aller gefundenen Links, mit absoluten URLs und Ankertext. |
images |
signierte URL | Ein JSON-Array aller Bilder, mit absoluter src-URL und alt-Text. |
structured |
Inline-JSON | Strukturierte Daten, extrahiert aus der Seite (das structured-Antwortfeld). |
Markdown-Qualität#
Bevor die Seite konvertiert wird, wird das gerenderte DOM normalisiert, damit das Markdown widerspiegelt, was ein Leser sieht, und nicht, wie die Seite gebaut wurde. Das läuft bei jedem Render, sodass das Ergebnis nicht davon abhängt, welche Extraktionsstrategie für eine Seite gewinnt.
Immer angewendet, in beiden only_main_content-Modi:
- Code-Fences behalten ihre Sprache. Die Sprache wird aus der
jeweils vom Site verwendeten Konvention gelesen
(
class="language-python",data-lang, ein blankeslanguage-Attribut oder ein Highlighter-Wrapper) und normalisiert, sodass```pythonankommt statt eines nackten Fence. - Card-Links bleiben lesbar. Ein Link, der eine Überschrift und
eine Beschreibung umschließt, wird zu einem verlinkten Titel gefolgt
von seiner Beschreibung, statt zu einem zusammengelaufenen Link wie
[DatabaseSupabase provides a full Postgres database...]. Die Ziel-URL bleibt erhalten. - Überschriften bleiben auf einer Zeile. Eine Überschrift, deren
Text in einem verschachtelten Element sitzt, erzeugt kein nacktes
##mehr, unter dem der Text gestrandet ist. - Benachbarte Elemente laufen nicht mehr zusammen. Layouts, die
ihre Elemente per CSS statt per Leerraum trennen, erzeugten
YesNoundEvaluationDeploymentProduction; diese lesen sich jetzt als getrennte Wörter. - Unsichtbare Zeichen werden entfernt: Zero-Width-Spaces als Anker-Labels, weiche Trennstriche und Private-Use-Area-Glyphen aus Icon-Fonts, die als nicht darstellbare Token ankommen.
- Leere Elemente werden verworfen: reine Icon-
<i>-Elemente, die als verirrte__gerendert wurden, und Links ohne Label.
Zusätzlich mit only_main_content: true:
- Interface-Steuerelemente werden entfernt: Buttons, Tab-Leisten, Tastenkürzel-Hinweise, „Copy page“-/„On this page“-Aktionen und „War diese Seite hilfreich? Ja/Nein“-Bewertungs-Widgets. Ein Steuerelement mit echtem Inhalt (eine FAQ-Frage, ein klickbarer Card-Body) bleibt erhalten.
- Nur für Screenreader bestimmter Text wird entfernt: Skip-Links und die „Section titled ...“-Labels, die viele Doku-Themes an jede Überschrift hängen.
- Von der Site deklarierter Nicht-Inhalt wird respektiert: Blöcke
mit
data-nosnippet,data-pagefind-ignoreoderdata-noindex, sofern sie keine Überschriften oder Code enthalten. - Doppelte Blöcke werden zusammengefasst: responsive Designs, die eine Desktop- und eine Mobile-Kopie derselben Leiste ausliefern, und Karussells, die jedes Frame vorrendern, erscheinen einmal.
- Breadcrumbs und Eyebrow-Labels über dem Seitentitel entfallen.
Aufgeschobener Inhalt bleibt bewusst erhalten: ein inaktives Tab-Panel innerhalb der Inhaltsregion enthält ein echtes Code-Beispiel (das Python-Beispiel in einem Tab, das JavaScript-Beispiel in einem anderen), sodass beide ins Markdown gelangen und nicht nur der Tab, der zum Render-Zeitpunkt zufällig ausgewählt war.
Rendering und Warten#
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
viewport |
object |
1920 x 1080 |
{"width": <int>, "height": <int>}. Breite 320–3840, Höhe 240–2160. |
mobile |
boolean |
false |
Rendert in einem mobilen Viewport (390 x 844), sofern viewport nicht explizit gesetzt ist. |
wait_for |
string |
null |
Wartet nach der Navigation auf einen CSS-Selektor (".price" oder "css:.price") oder einen JS-Ausdruck ("js:window.dataReady === true"). |
wait_timeout_ms |
integer |
30000 |
Wie lange wait_for warten darf, in Millisekunden. 0–60,000. Ein Timeout wird zu einer Warnung herabgestuft; die Seite wird so erfasst, wie sie ist. |
js_code |
string |
null |
JavaScript, das nach der Navigation auf der Seite ausgeführt wird. Maximal 20,000 Zeichen. Ein Fehler wird zu einer Warnung, nicht zu einem Fehlschlag. |
block_resources |
string[] |
[] |
Ressourcentypen, die vor dem Laden abgebrochen werden. Beliebige aus image, media, font, stylesheet, script, xhr, fetch, websocket, manifest, other. Nützlich für schnellere, reine Text-Renders. |
respect_robots |
boolean |
false |
Wenn true, wird eine von der robots.txt der Site untersagte URL mit 403 abgelehnt. |
pdf_options |
object |
null |
Seitenformat, Ränder, Kopf- und Fußzeilen, Skalierung und Ausrichtung für die pdf-Ausgabe. Dasselbe Objekt wie bei url-to-pdf. Ohne pdf_options erzeugt perceive eine einzige durchgehende Seite, byteidentisch zum V1-url-to-pdf. |
Authentifizierte und benutzerdefinierte Anfragen#
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
auth |
object |
null |
HTTP Basic Auth für die Zielseite: {"username": "...", "password": "..."}. |
cookies |
array |
null |
Cookies, die vor der Navigation injiziert werden. Maximal 50. Jedes benötigt name, value und entweder domain oder url. |
headers |
object |
null |
Benutzerdefinierte Request-Header. Maximal 20. Blockierte Namen: host, content-length, transfer-encoding, connection, upgrade, te, trailer. |
proxy_url (Production+),
geolocation und action_chain werden vom
Request-Schema akzeptiert, liefern aber aktuell 422 zurück. Sie
kommen in einem späteren Release; wenn du sie jetzt sendest, erfährst du
genau, welcher Schalter noch nicht bereit ist, statt dass er still
ignoriert wird.
Strukturierte Extraktion#
Wenn structured in outputs enthalten ist, bestimmt die
extract-Liste, welche Felder perceive abruft. Forderst du nichts an,
greift der Standard metadata und structured_data.
extract-Wert |
Feld in structured |
Status |
|---|---|---|
metadata |
metadata |
Live |
structured_data |
structured_data (JSON-LD) |
Live |
headings |
headings |
Live |
tables |
tables |
Live |
main_content |
main_content (Text, begrenzt auf 50,000 Zeichen) |
Live |
all |
expandiert zu allen oben genannten Live-Feldern | Live |
prices |
-- | Noch nicht live: liefert eine Warnung, wird ausgelassen |
contacts |
-- | Noch nicht live: liefert eine Warnung, wird ausgelassen |
technologies |
-- | Noch nicht live: liefert eine Warnung, wird ausgelassen |
Um ehrlich zu sein: prices, contacts und technologies sind
reservierte Namen. Forderst du heute eines davon an, gibt es keinen
Fehler: Der Name landet im warnings-Array und wird aus structured
entfernt.
Schema-gesteuerte Extraktion#
Sende ein schema, um bestimmte Felder in structured.extracted
abzurufen:
{
"url": "https://example.com/product/widget",
"outputs": ["markdown", "structured"],
"schema": {
"type": "object",
"properties": {
"name": {"type": "string"},
"price": {"type": "number"},
"in_stock": {"type": "boolean"}
}
}
}
Die LLM-gestützte Extraktionsstufe füllt das Schema, und
sie greift nur, wenn alle diese Bedingungen zutreffen: du hast ein
schema gesendet, dein Plan enthält die LLM-Stufe (Indie und höher),
die Seite wurde nicht als blockiert bewertet, und der heuristische
Durchlauf hat die Schemafelder leer gelassen. Läuft sie, ist
extraction_tier gleich "llm", und tokens sowie cost_cents geben
an, was diese Extraktion gekostet hat; andernfalls ist
extraction_tier gleich "heuristic" und beide sind null.
Hinweis. Die Schema-Extraktion ist hart gedeckelt, um deine Rechnung zu schützen: Eine einzelne Extraktion ist pro Anfrage begrenzt, und die Projektausgaben schöpfen aus deinem monatlichen AI-Credit-Guthaben ($5 / $15 / $40 pro Monat auf Indie / Studio / Production; ungenutzte Credits werden übertragen). LLM-Extraktion verbraucht Credits, keine Ops. Wird eine Obergrenze erreicht, liefert perceive das heuristische Ergebnis mit einem Hinweis in
warnings, statt zu überziehen. Auf einem Plan ohne die LLM-Stufe erhältst du nur heuristischestructured-Daten.
Antwort#
Sowohl POST /v2/perceive als auch GET /v2/perceive/{operation_id}
liefern dasselbe Objekt zurück.
| Feld | Typ | Beschreibung |
|---|---|---|
operation_id |
string |
Opake ID (per_...). Verwende sie mit dem GET-Endpunkt und gib sie beim Support an. |
status |
string |
queued, processing, completed oder failed. |
url |
string |
Die von dir gesendete URL. |
url_final |
string |
Die URL nach Weiterleitungen. |
content_hash |
string |
SHA-256 der gerenderten Seite. Steuert den 1-Stunden-Cache. |
render_quality |
number |
0.0–1.0. Werte unter 0.40 bedeuten einen fehlgeschlagenen Render: eine Anti-Bot-Seite, eine Login-Wall, eine HTTP-Fehlerseite, ein Soft-404 oder eine leere Hülle. Sieh in deductions nach dem Grund und in status_code nach dem Upstream-Status. |
status_code |
integer |
HTTP-Status der finalen Hauptdokument-Antwort (z. B. 200, 404). null, wenn unbekannt. |
deductions |
object |
Benannte Render-Qualitätsabzüge, die gegriffen haben, z. B. {"http_error": 0.7}, {"soft_404": 0.65}, {"login_wall": 0.65}. Leer bei einem sauberen Render. |
options_echo |
object |
Echo der Request-Optionen, die der Server berücksichtigt hat. Geheimnisse werden zu Booleans reduziert (auth_provided, cookies_provided, headers_provided, js_code_provided, schema_provided, pdf_options_provided). Die einfachen Optionen (outputs, only_main_content, truncate_data_arrays, allow_degraded, extract, cache_mode, mobile, respect_robots, direct_download, wait_for, wait_timeout_ms, viewport, block_resources) werden so zurückgegeben, wie sie berücksichtigt wurden. truncate_data_arrays wird als aufgelöster Boolean zurückgegeben, sodass du auch bei nicht gesetztem Wert siehst, wie entschieden wurde. |
cache_hit |
boolean |
true, wenn das Ergebnis aus dem Cache stammt statt aus einem frischen Render. |
outputs |
object |
Map von Ausgabename zu {url, object_key, size_bytes, content_type, expires_in}. Signierte URLs laufen nach 900 Sekunden ab. |
structured |
object |
Inline-strukturierte Daten, vorhanden, wenn structured angefordert wurde. |
extraction_tier |
string |
heuristic, css oder llm. |
tokens |
object |
{input, output} verwendete LLM-Tokens. Null, sofern die LLM-Stufe nicht lief. |
cost_cents |
number |
LLM-Kosten in Cent für diese Operation. Null, sofern die LLM-Stufe nicht lief. |
duration_ms |
integer |
Ende-zu-Ende-Renderzeit. |
error |
string |
Nur gesetzt, wenn status gleich failed ist. |
warnings |
string[] |
Nicht-fatale Hinweise: ein wait_for-Timeout, ein übersprungenes Extraktionsfeld, eine Markierung für eine blockierte Seite, ein only_main_content-Fallback auf die vollständige Seite, ein Hinweis, dass lange numerische Daten-Arrays gekürzt wurden. |
Eine Operation abrufen#
Signierte URLs laufen nach 15 Minuten ab. Um eine Ausgabe später herunterzuladen, rufe die Operation erneut ab. Perceive signiert jede URL anhand der gespeicherten Object-Keys neu. Es findet kein erneuter Render statt, daher werden dabei keine Ops verbraucht.
curl https://api.enconvert.com/v2/perceive/per_3f9a2c1b8e7d4a6f90b1c2d3e4f5a6b7 \
-H "X-API-Key: sk_your_private_key"
Eine unbekannte Operation-ID oder eine, die zu einem anderen Projekt
gehört, liefert 404. Die Existenz wird projektübergreifend nie
preisgegeben.
Direct Download#
Standardmäßig kommt jede Datei-Ausgabe als vorsignierte URL zurück, die
du in einer zweiten Anfrage abrufst. Setze direct_download: true im
POST, um den Envelope zu überspringen: Der HTTP-Response-Body ist
dann die Artefakt-Bytes, ohne JSON, ohne signierte URL und ohne zweiten
Abruf. Die Anfrage muss genau eine Artefakt-erzeugende Ausgabe
produzieren (outputs: ["markdown"], outputs: ["pdf"], …), sonst wird
sie mit 400 abgelehnt. Die Metadaten, die sonst im JSON stünden,
reisen stattdessen in Response-Headern mit: Content-Disposition,
X-Operation-Id, X-Object-Key, X-Cache-Hit, X-Render-Quality,
X-Source-Status-Code, X-Content-Hash und X-Warnings-Count.
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/blog/post",
"outputs": ["markdown"],
"direct_download": true
}' \
-o post.md
Die GET-Endpunkte streamen gespeicherte Artefakte auf dieselbe Weise:
GET /v2/perceive/{operation_id}?direct_download=true&output=markdownstreamt ein Artefakt einer vergangenen Operation.outputist erforderlich, wenn die Operation mehr als ein Artefakt erzeugt hat. Ein Artefakt außerhalb des Aufbewahrungsfensters deines Plans antwortet mit410.GET /v2/perceive/batch/{job_id}?direct_download=truestreamt die Batch-ZIP-Datei. Das gilt für Batches mitoutput_mode: "zip", deren Archiv bereit ist, andernfalls kommt400zurück.
direct_download gilt nur für einzelne URLs: POST /v2/perceive/batch
lehnt es mit 422 ab. Setze output_mode auf "zip" und lade das
Archiv herunter. Siehe Batch-Perceive.
Batch-Perceive#
POST /v2/perceive/batch erfasst eine Liste von URLs, die sich einen
options-Block teilen. Jede URL wird über dieselbe Pipeline wie bei
einem Einzelaufruf gerendert und erzeugt ihre eigene Operationszeile.
curl -X POST https://api.enconvert.com/v2/perceive/batch \
-H "X-API-Key: sk_your_private_key" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://example.com/a",
"https://example.com/b",
"https://example.com/c"
],
"options": {"outputs": ["markdown"]},
"output_mode": "manifest"
}'
Batches mit 10 oder weniger URLs laufen inline und antworten mit 200,
wobei jedes Ergebnis befüllt ist. Größere Batches antworten mit 202
und einer job_id; die URLs werden nacheinander abgearbeitet, und du
fragst die Ergebnisse ab:
curl https://api.enconvert.com/v2/perceive/batch/{job_id} \
-H "X-API-Key: sk_your_private_key"
Die Batch-Antwort meldet den aggregierten Fortschritt und enthält pro URL ein vollständiges perceive-Ergebnis, sobald gerendert wurde:
{
"job_id": "bat_8c1a...",
"status": "partial",
"output_mode": "manifest",
"total": 3,
"completed": 2,
"failed": 1,
"pending": 0,
"items": [
{"operation_id": "per_...", "status": "completed", "url": "https://example.com/a"},
{"operation_id": "per_...", "status": "completed", "url": "https://example.com/b"},
{"operation_id": "per_...", "status": "failed", "url": "https://example.com/c"}
]
}
status ist queued, processing, completed, failed, partial
(manche URLs erfolgreich, manche fehlgeschlagen) oder canceled. Setze
output_mode auf zip, um jedes Artefakt in einer einzigen ZIP-Datei
zu bündeln, die nach Abschluss des Batches im Feld zip zurückgegeben
wird.
Dauerhaft und wiederaufnehmbar#
Batches sind restart-sicher. Wenn der Dienst neu startet, während ein Batch läuft, wird der Batch automatisch fortgesetzt und rendert nur die URLs erneut, die noch nicht fertig waren, sodass bereits abgeschlossene URLs ihre Artefakte behalten. Du musst einen Batch wegen eines Neustarts nie erneut einreichen.
Einen Batch abbrechen#
DELETE /v2/perceive/batch/{job_id} bricht einen laufenden Batch ab.
Der Worker stoppt zwischen den URLs, sodass bereits gerenderte URLs ihre
Ergebnisse behalten und der Rest nicht gestartet wird. Der Aufruf ist
idempotent: Das Abbrechen eines bereits abgeschlossenen Batches liefert
einfach seinen aktuellen Zustand zurück, und der status des Batches
wird zu canceled.
curl -X DELETE https://api.enconvert.com/v2/perceive/batch/{job_id} \
-H "X-API-Key: sk_your_private_key"
Caching#
cache_mode steuert, wie perceive seinen 1-Stunden-Ergebnis-Cache
behandelt, der nach deinem Projekt, der URL und den render-relevanten
Request-Optionen geschlüsselt ist.
cache_mode |
Verhalten |
|---|---|
enabled (Standard) |
Liefert ein zwischengespeichertes Ergebnis, wenn eine identische Anfrage innerhalb der letzten Stunde gerendert wurde. cache_hit ist true, cost_cents ist 0. |
bypass |
Überspringt den Cache und rendert neu. |
refresh |
Rendert neu und ersetzt den zwischengespeicherten Eintrag. |
Wichtig: Ein Cache-Hit berechnet trotzdem eine Op gegen dein monatliches Ops-Kontingent. Das Kontingent misst Operationen, nicht Browser-Renders, daher spart dir der Cache Renderzeit, keine Ops.
Codebeispiele#
curl: Nur Markdown#
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/blog/post",
"outputs": ["markdown"]
}'
curl: Markdown plus strukturierte Daten#
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", "structured"],
"extract": ["metadata", "structured_data", "tables"]
}'
curl: Alle Ausgaben plus PDF#
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/report",
"outputs": ["markdown", "html_cleaned", "screenshot_full_page", "pdf"],
"pdf_options": {"format": "A4", "print_background": true}
}'
Python#
import requests
response = requests.post(
"https://api.enconvert.com/v2/perceive",
headers={"X-API-Key": "sk_your_private_key"},
json={
"url": "https://example.com/pricing",
"outputs": ["markdown", "structured"],
"extract": ["metadata", "tables"],
},
)
response.raise_for_status()
data = response.json()
# Download the Markdown artifact from its signed URL
markdown_url = data["outputs"]["markdown"]["url"]
markdown_text = requests.get(markdown_url).text
print(data["structured"])
print(markdown_text)
Node.js#
const res = await fetch("https://api.enconvert.com/v2/perceive", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "sk_your_private_key"
},
body: JSON.stringify({
url: "https://example.com/pricing",
outputs: ["markdown", "structured"],
extract: ["metadata", "tables"]
})
});
const data = await res.json();
// Download the Markdown artifact from its signed URL
const markdownText = await fetch(data.outputs.markdown.url).then(r => r.text());
console.log(data.structured);
console.log(markdownText);
Wenn du EnConvert aus Claude, Cursor oder einem anderen MCP-Client
aufrufst, ist dieselbe Funktion als perceive_url-Tool verfügbar.
Siehe die MCP-Serverseite.
Fehlerantworten#
| Status | Bedingung |
|---|---|
400 Bad Request |
URL ist nicht http(s), enthält eingebettete Zugangsdaten oder löst zu einer privaten, Loopback- oder Link-Local-Adresse auf (SSRF-Schutz). |
400 Bad Request |
Ungültiges auth (fehlendes username/password), cookies (kein Array, mehr als 50 Einträge, fehlende Felder) oder headers (kein Objekt, mehr als 20 Einträge, blockierter Name). |
401 Unauthorized |
Fehlender oder ungültiger API-Schlüssel / JWT-Token. |
402 Payment Required |
Perceive ist nicht in deinem aktuellen Plan enthalten, oder dein monatliches Ops-Kontingent ist aufgebraucht. |
403 Forbidden |
/v2/perceive ist nicht in den erlaubten Endpunkten des API-Schlüssels enthalten. |
403 Forbidden |
Batch ist in deinem Plan nicht verfügbar, oder die Batch-Größe überschreitet das Limit deines Plans. |
403 Forbidden |
respect_robots=true, und die robots.txt der Site untersagt die URL. |
404 Not Found |
Unbekannte operation_id oder job_id, oder eine, die einem anderen Projekt gehört. |
422 Unprocessable Entity |
Request-Validierung fehlgeschlagen (ungültiges Enum in outputs/extract, wait_timeout_ms außerhalb des Bereichs, Viewport außerhalb der Grenzen, ein unbekannter Request-Key). |
422 Unprocessable Entity |
proxy_url, geolocation oder action_chain wurde gesendet. Alle drei sind für ein späteres Release reserviert. |
500 Internal Server Error |
Der Render ist fehlgeschlagen. Die Meldung enthält die operation_id, die du beim Support angeben kannst. |
502 Bad Gateway |
Alle Engines wurden blockiert und der Origin lieferte eine Anti-Bot-Challenge ohne dahinterliegenden Seiteninhalt. Versuche es später erneut oder sende allow_degraded: true, um die Challenge-Seite unverändert zu erhalten. |
Unbekannte Request-Keys werden mit einem 422 abgelehnt, das das Feld
benennt, und zwar auf /v2/perceive, /v2/perceive/batch,
/v2/discover und /v2/lookup gleichermaßen. Still ignoriert werden
sie nie. Jeder 422-Body enthält ein Top-Level-Array errors mit
menschenlesbaren Meldungen neben der rohen detail-Liste.
Die vollständige Statuscode-Referenz findest du im Fehlercode-Leitfaden.
Limits#
| Limit | Wert |
|---|---|
| URL-Länge | 2,048 Zeichen |
wait_timeout_ms |
0–60,000 ms |
js_code-Länge |
20,000 Zeichen |
| Viewport-Breite | 320–3,840 px |
| Viewport-Höhe | 240–2,160 px |
| Cookies pro Anfrage | 50 |
| Benutzerdefinierte Header pro Anfrage | 20 |
main_content-Extract |
50,000 Zeichen |
| Batch-URLs pro Anfrage | 1,000 (Schema-Obergrenze) |
| Inline-Batch-Schwelle | 10 URLs (größere Batches laufen asynchron) |
| Ergebnis-Cache-TTL | 1 Stunde |
| Ablauf signierter URLs | 15 Minuten |
| Monatliche Ops (über alle Endpunkte geteilt) | 500 / 3.000 / 15.000 / 50.000 je Tarif; siehe Preise |
Häufig gestellte Fragen#
Wie konvertiere ich eine Webseite mit einer REST-API in Markdown?#
Sende POST /v2/perceive mit {"url": "...", "outputs": ["markdown"]}. Die Seite wird in Headless-Chrome gerendert, und die Antwort enthält eine vorsignierte Download-URL für die Markdown-Datei. Standardmäßig entfernt only_main_content das Site-Chrome, sodass du den Artikel bekommst, nicht die Navigation; setze "only_main_content": false für die vollständige Seite, oder füge "direct_download": true hinzu, um die Markdown-Bytes direkt im Response-Body zu erhalten.
Kann ich einen Screenshot und Markdown aus demselben Render erhalten?#
Ja. outputs akzeptiert jede Kombination, also erzeugt ["markdown", "screenshot"] (oder screenshot_full_page für die gesamte Scroll-Höhe) beides aus einem einzigen Browser-Render. Du bezahlst innerhalb eines Aufrufs nie zweimal für dieselbe Seite.
Rendert /v2/perceive JavaScript-Seiten?#
Ja. Jede Anfrage führt einen echten Headless-Chrome-Render aus: Cookie-Banner werden geschlossen, die Seite wird gescrollt, um Lazy-Loaded-Content auszulösen, und du kannst die Seite vor der Erfassung mit wait_for (einem CSS-Selektor oder JS-Ausdruck), js_code und block_resources steuern.
Warum funktioniert meine signierte Download-URL nicht mehr?#
Signierte URLs laufen nach 15 Minuten ab (expires_in: 900). Rufe die Operation mit GET /v2/perceive/{operation_id} erneut ab, um frisch signierte URLs zu erhalten. Es findet kein erneuter Render statt, und es werden keine Ops verbraucht.
Zählt ein zwischengespeichertes Ergebnis trotzdem gegen mein Kontingent?#
Ja. Ein Cache-Hit berechnet eine Op, denn das monatliche Kontingent misst Operationen, nicht Browser-Renders. Setze cache_mode auf bypass, um den 1-Stunden-Cache zu überspringen, oder auf refresh, um neu zu rendern und den zwischengespeicherten Eintrag zu ersetzen.