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.

  1. 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.
  2. Materialisieren. Aus dem gerenderten DOM erstellt perceive alles, was du in outputs angegeben 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 unter only_main_content entfernt. Siehe Markdown-Qualität.
  3. Extrahieren. Wenn du die Ausgabe structured angefordert hast, führt perceive einen heuristischen Durchlauf für Seitenmetadaten, JSON-LD, Überschriften und Tabellen aus. Wenn du zusätzlich ein schema sendest und dein Plan die LLM-Stufe enthält, füllt ein LLM-gestütztes Modell das Schema, wenn der heuristische Durchlauf nicht ausreicht.
  4. 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 deductions nach dem Grund und in status_code nach 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 blankes language-Attribut oder ein Highlighter-Wrapper) und normalisiert, sodass ```python ankommt 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 YesNo und EvaluationDeploymentProduction; 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-ignore oder data-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.
Reserviert, noch nicht live. 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 heuristische structured-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=markdown streamt ein Artefakt einer vergangenen Operation. output ist erforderlich, wenn die Operation mehr als ein Artefakt erzeugt hat. Ein Artefakt außerhalb des Aufbewahrungsfensters deines Plans antwortet mit 410.
  • GET /v2/perceive/batch/{job_id}?direct_download=true streamt die Batch-ZIP-Datei. Das gilt für Batches mit output_mode: "zip", deren Archiv bereit ist, andernfalls kommt 400 zurü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.