---
seo_title: Ruby SDK für Dateikonvertierung: RubyGems-Client | EnConvert
meta_desc: Offizielles EnConvert Ruby SDK für Ruby 3.0+. Installiere das enconvert-Gem, konvertiere Dateien und lies Webseiten per perceive, discover, distill, ingest und watch.
keywords: ruby sdk dateikonvertierung, dateien konvertieren ruby, url zu pdf ruby, web scraping api ruby, docx in pdf konvertieren ruby, enconvert ruby sdk, rubygems client dateikonvertierung api, heic in webp konvertieren ruby, html in pdf umwandeln ruby gem, webseite in markdown umwandeln ruby, strukturierte datenextraktion ruby, website crawler gem ruby
---

# Ruby SDK für Dateikonvertierung

`enconvert` ist das offizielle Ruby-Gem für die EnConvert API. Es konvertiert URLs, Bilder und Dokumente über 43 implementierte Konvertierungs-Endpunkte und stellt einen zweiten Namensraum bereit, `client.v2`, der lebende Webseiten in agentenfertiges Markdown, JSON, Screenshots und PDFs liest. Jeder V2-Lesevorgang trägt einen `render_quality`-Wert, sodass eine blockierte Seite oder eine leere SPA-Hülle nie als echter Inhalt durchgeht. Das Gem zielt auf Ruby 3.0+ und kommt ohne Laufzeitabhängigkeiten, aufgebaut auf `net/http`, `json` und `securerandom` aus der Standardbibliothek. Antworten kommen als Structs mit snake_case-Lesern zurück.

<div class="alert alert-info">
<strong>RubyGems:</strong> <code>enconvert</code> · <strong>Quelle:</strong> <a href="https://github.com/conversionapi/ruby-sdk">conversionapi/ruby-sdk</a> · <strong>Ruby:</strong> 3.0.0 oder neuer · <strong>Laufzeitabhängigkeiten:</strong> keine
</div>

---

## Installation

```bash
gem install enconvert
```

Oder füge es deinem `Gemfile` hinzu:

```ruby
gem "enconvert"
```

```bash
bundle install
```

---

## Schnellstart

```ruby
require "enconvert"

client = Enconvert::Client.new(api_key: ENV.fetch("ENCONVERT_API_KEY"))

# Eine Live-Seite in ein PDF konvertieren und auf die Festplatte streamen.
result = client.convert_url_to_pdf("https://example.com", save_to: "page.pdf")
puts result.presigned_url

# Dieselbe Seite so lesen, wie es ein Agent tun sollte, mit angehängtem Quality-Score.
op = client.v2.perceive("https://example.com", outputs: %w[markdown structured])
puts op.outputs["markdown"].url, op.render_quality  # z. B. 0.93
```

Das Gem ist ausschließlich serverseitig. Dein privater API-Key muss auf dem Server bleiben, lies ihn also aus einer Umgebungsvariablen oder einem Secret-Manager und liefere ihn nie an einen Browser oder ein Mobile-Binary aus.

---

## Was der Client bereitstellt

`Enconvert::Client` trägt die Datei-Konvertierungs-Oberfläche direkt, und die gesamte Web-Intelligence-Oberfläche hängt an `client.v2`.

| Gruppe | Methoden | Rückgabe |
|-------|---------|---------|
| Einzelne URL | `convert_url_to_pdf`, `convert_url_to_screenshot`, `convert_url_to_markdown` | `ConversionResult` |
| Hochgeladene Datei | `convert_image`, `convert_document`, `convert_to_markdown`, `convert_to_pdf` | `ConversionResult` |
| Batch für ganze Website | `convert_website_to_pdf`, `convert_website_to_screenshot` | `BatchSubmission` |
| Status und Polling | `get_job_status`, `get_batch_status`, `wait_for_batch` | `JobStatus`, `BatchStatus` |
| Web-Intelligence | `client.v2.*`, 23 Methoden über sechs Fähigkeiten | V2-Structs |
| Format-Helfer | `Enconvert.valid_outputs_for`, `Enconvert::IMPLEMENTED_CONVERSIONS` | `Array`, `Set` |

Jeder Antworttyp ist ein `Struct`, erzeugt mit `keyword_init: true`, du liest Felder also als gewöhnliche Methoden: `result.presigned_url`, `op.render_quality`, `job.total_chunks`.

---

## Datei-Konvertierung

### convert_url_to_pdf

Rendere jede öffentliche URL zu einem PDF.

```ruby
result = client.convert_url_to_pdf(
  "https://example.com",
  single_page: false,
  pdf_options: { page_size: "A4", orientation: "landscape" },
  viewport_width: 1440,
  save_to: "report.pdf"
)

puts result.filename, result.file_size
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `save_to` | `String` | `nil` | Lokaler Pfad, in den das PDF geladen wird. Übergeordnete Verzeichnisse werden für dich angelegt. |
| `single_page` | `Boolean` | `true` | `true` erzeugt eine einzige fortlaufende Seite. `false` paginiert anhand von `pdf_options[:page_size]`. |
| `pdf_options` | `Hash` | `nil` | Seitengeometrie. Siehe [PDF-Optionen](#pdf-optionen). |
| `viewport_width` | `Integer` | `1920` | Breite des Browser-Viewports in Pixeln. |
| `viewport_height` | `Integer` | `1080` | Höhe des Browser-Viewports in Pixeln. |
| `load_media` | `Boolean` | `true` | Vor der Erfassung auf Bilder und Videos warten. |
| `enable_scroll` | `Boolean` | `true` | Von oben nach unten scrollen, damit Lazy-Loader auslösen. |
| `output_filename` | `String` | `nil` | Überschreibt den generierten Dateinamen. |
| `auth` | `Hash` | `nil` | HTTP-Basic-Zugangsdaten, zum Beispiel `{ username: "user", password: "pass" }`. |
| `cookies`, `headers` | `Array`, `Hash` | `nil` | Zu setzende Cookies und zusätzliche Request-Header. Alle drei Browser-Zugriffsfelder werden unverändert durchgereicht. |

<div class="alert alert-warning">
<strong>Kombiniere <code>auth</code> nicht mit einem <code>Authorization</code>-Header.</strong> Die API lehnt diesen Konflikt ab, entscheide dich also für eines von beiden.
</div>

### convert_url_to_screenshot

Erfasse ein ganzseitiges PNG einer beliebigen URL.

```ruby
client.convert_url_to_screenshot("https://example.com", viewport_width: 1440, save_to: "shot.png")
```

Akzeptiert dieselben Optionen für Viewport, Medien, Scrollen, Dateiname und Browser-Zugriff wie `convert_url_to_pdf`. `single_page` und `pdf_options` nimmt es nicht.

### convert_url_to_markdown

Extrahiere sauberes GitHub-Flavored Markdown aus jeder URL. Der Konverter entfernt Navigation, Fußzeilen, Werbung und Skripte, behält den Haupttext des Artikels und stellt YAML-Frontmatter mit Titel, Beschreibung, URL, Links und Bildern voran.

```ruby
client.convert_url_to_markdown("https://example.com/article", save_to: "article.md")
```

Nützlich, um eine RAG-Pipeline zu füttern, fremde Inhalte in ein CMS zu importieren oder ein Trainingskorpus aufzubauen.

### convert_image

Konvertiere zwischen `jpeg`, `png`, `svg`, `heic` und `webp` in jede Richtung oder rastere ein PDF zu JPEG.

```ruby
# Von einem Pfad auf der Festplatte.
client.convert_image("photo.heic", output_format: "webp", save_to: "photo.webp")

# Aus Bytes, die du bereits hältst.
bytes = File.binread("photo.heic")
client.convert_image({ data: bytes, filename: "photo.heic" }, output_format: "webp", save_to: "photo.webp")

# Die erste Seite eines PDFs rastern.
client.convert_image("invoice.pdf", output_format: "jpeg", save_to: "invoice.jpg")
```

Das Eingabeformat wird aus der Dateiendung aufgelöst, ein rohes `IO` ohne Dateinamen lässt sich hier also nicht verwenden. Übergib stattdessen einen Pfad oder einen `{ data:, filename: }`-Hash.

| Option | Typ | Erforderlich | Beschreibung |
|--------|------|----------|-------------|
| `output_format` | `String` | Ja | Zielformat. Die Aliasse `jpg`, `yml`, `htm` und `md` werden für dich normalisiert. |
| `save_to` | `String` | Nein | Lokaler Pfad, in den das Ergebnis geladen wird. |
| `output_filename` | `String` | Nein | Überschreibt den generierten Dateinamen. |

### convert_document

Konvertiere Dokumente und Datenformate. `output_format` ist standardmäßig `"pdf"`.

```ruby
# docx zu pdf
client.convert_document("report.docx", save_to: "report.pdf")

# json zu yaml
client.convert_document("data.json", output_format: "yaml", save_to: "data.yaml")

# markdown zu pdf mit eigener Seiteneinrichtung
client.convert_document(
  "README.md",
  output_format: "pdf",
  pdf_options: { page_size: "A4", margins: { top: 20, bottom: 20, left: 25, right: 25 } },
  save_to: "readme.pdf"
)
```

**Erkannte Eingabe-Erweiterungen:** `.doc`, `.docx`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.html`, `.htm`, `.odt`, `.ods`, `.odp`, `.ots`, `.pages`, `.numbers`, `.md`, `.markdown`, `.csv`, `.json`, `.xml`, `.yaml`, `.yml`, `.toml`.

EPUB hat kein eigenes Dokumentpaar. Schicke `.epub`-Dateien stattdessen durch `convert_to_pdf` oder `convert_to_markdown`.

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `output_format` | `String` | `"pdf"` | Zielformat. |
| `save_to` | `String` | `nil` | Lokaler Pfad, in den das Ergebnis geladen wird. |
| `output_filename` | `String` | `nil` | Überschreibt den generierten Dateinamen. |
| `pdf_options` | `Hash` | `nil` | Seiteneinrichtung, wird berücksichtigt, wenn die Ausgabe ein PDF ist. |

### convert_to_markdown

Konvertiere eine hochgeladene Datei fast jedes Dokumentformats in sauberes Markdown. Das Format wird auf dem Server erkannt.

```ruby
client.convert_to_markdown("handbook.docx", save_to: "handbook.md")
```

**Akzeptierte Eingaben:** PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT und MD sowie ältere und ODF-Office-Formate. Bilder werden an diesem Endpunkt nicht unterstützt.

Die Ausgabe ist eine einzelne, überschriften-bewusste `.md`-Datei, was sie zu einem guten Baustein für die RAG-Aufnahme macht: Ein semantischer Chunker kann anhand der Überschriften-Hierarchie des Dokuments trennen statt anhand willkürlicher Zeichenzahlen. Die einzigen Optionen sind `save_to:` und `output_filename:`.

### convert_to_pdf

Konvertiere eine hochgeladene Datei fast jedes Formats in ein PDF.

```ruby
# pptx zu pdf
client.convert_to_pdf("slides.pptx", save_to: "slides.pdf")

# pdf-Durchreichung, auf Graustufen normalisiert
client.convert_to_pdf("scan.pdf", pdf_options: { grayscale: true }, save_to: "gray.pdf")
```

**Akzeptierte Eingaben:** Office, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, reiner Text, Rasterbilder, SVG, EPUB oder ein bestehendes PDF, das durchgereicht und normalisiert wird.

<div class="alert alert-warning">
<strong>Nur <code>pdf_options[:grayscale]</code> wird hier berücksichtigt.</strong> Die übrigen Felder zur Seitengeometrie werden an diesem Endpunkt ignoriert. Wenn du volle Kontrolle über Seitengröße, Ausrichtung und Ränder brauchst, nimm <code>convert_document</code> oder <code>convert_url_to_pdf</code>.
</div>

Die Optionen sind `save_to:`, `output_filename:` und `pdf_options:` (nur Graustufen).

### convert_website_to_pdf und convert_website_to_screenshot

Ermittle jede Seite einer Website, konvertiere jede einzelne im Hintergrund und sammle die Ergebnisse als ein einziges ZIP. Beide Methoden arbeiten asynchron und geben sofort ein `BatchSubmission` zurück.

```ruby
batch = client.convert_website_to_pdf(
  "https://example.com",
  crawl_mode: "sitemap",
  exclude_patterns: ["/tag/"],
  notification_email: "me@example.com"
)

status = client.wait_for_batch(batch.batch_id, save_to: "site.zip")
puts "#{status.completed} of #{status.total} pages converted"
status.items.each { |item| puts "#{item.status}\t#{item.source_url}" }
```

`convert_website_to_screenshot` funktioniert identisch und erzeugt ein ZIP mit PNGs.

| Option | Typ | Beschreibung |
|--------|------|-------------|
| `crawl_mode` | `String` | Wie Seiten ermittelt werden, zum Beispiel `"sitemap"`. |
| `include_patterns`, `exclude_patterns` | `Array` | Nur URLs crawlen, die zu diesen Mustern passen, oder sie überspringen. |
| `notification_email` | `String` | E-Mail-Adresse, die benachrichtigt wird, wenn der Batch fertig ist. |
| `callback_url` | `String` | Webhook, der bei Abschluss aufgerufen wird. |
| `output_filename` | `String` | Überschreibt den generierten ZIP-Dateinamen. |
| `viewport_width`, `viewport_height`, `load_media`, `enable_scroll` | `Integer`, `Boolean` | Render-Verhalten pro Seite. Wird nur gesendet, wenn du es setzt, sonst wendet das Gateway seine eigenen Standardwerte an. |
| `auth`, `cookies`, `headers` | `Hash`, `Array`, `Hash` | Browser-Zugriff für geschützte Seiten. |
| `single_page`, `pdf_options` | `Boolean`, `Hash` | Nur für PDF-Batches. |

`wait_for_batch(batch_id, interval: 5, timeout: 1800, save_to: nil)` fragt ab, bis der Batch `"processing"` verlässt, und gibt dann den finalen `BatchStatus` zurück. Mit `save_to` lädt es zusätzlich das ZIP herunter. Es löst `Enconvert::APIError` mit Status `504` aus, wenn das Timeout erreicht wird, und mit Status `500`, wenn ein Batch ohne speicherbares ZIP endet.

### get_job_status

Frage einen einzelnen Konvertierungsjob per ID ab.

```ruby
status = client.get_job_status("job_abc123")
case status.status
when "success" then puts status.presigned_url
when "failed"  then warn status.error
else                puts "still processing"
end
```

Gibt einen `JobStatus` mit `status`, `presigned_url`, `object_key` und `error` zurück. Du musst ihn selten selbst aufrufen, weil das SDK bereits für dich abfragt. Siehe [Timeout-Recovery](#timeout-recovery).

### Unterstützte Konvertierungen

Das Gem trägt die vollständige Tabelle der implementierten `{input}-to-{output}`-Endpunkte und prüft jedes Paar lokal, ein nicht unterstütztes Paar löst also `Enconvert::Error` aus, bevor eine Anfrage deinen Prozess verlässt.

```ruby
Enconvert.valid_outputs_for("json")      # => ["csv", "toml", "xml", "yaml"]
Enconvert.valid_outputs_for("pdf")       # => ["jpeg"]
Enconvert::IMPLEMENTED_CONVERSIONS.size  # => 43
```

| Eingabe | Ausgaben |
|-------|---------|
| `json` | `csv`, `toml`, `xml`, `yaml` |
| `xml` | `csv`, `json` |
| `yaml` | `json` |
| `csv` | `json`, `xml` |
| `toml` | `json` |
| `markdown` | `html`, `pdf` |
| `html` | `pdf` |
| `doc`, `excel`, `ppt`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers` | `pdf` |
| `jpeg`, `png`, `svg`, `heic`, `webp` | untereinander, alle 20 Paare |
| `pdf` | `jpeg` |

Das sind 43 Endpunkte: 13 für strukturierten Text, 9 für Dokumente und 21 für Bildkonvertierungen.

---

## Web-Intelligence (V2)

`client.v2` verwandelt lebende Webseiten in agentenfertige Daten: rendern, aufzählen, suchen, extrahieren, aufnehmen und überwachen. Jedes Rendering trägt `render_quality`, einen Wert von 0.0 bis 1.0, der an jedem Lesevorgang hängt. Ein niedriger Wert bedeutet, dass die Seite nicht sauber gerendert wurde, typischerweise eine Challenge-Seite, eine Cookie-Wall, ein Login-Gate oder eine leere JavaScript-Hülle. Der Inhalt wird trotzdem zurückgegeben, aber markiert, zusammen mit einem `warnings`-Array und einem `deductions`-Hash, der sagt, welche Prüfungen angeschlagen haben. Lies den Wert, bevor du den Inhalt irgendwo ablegst, dann gelangt ein schlechter Lesevorgang nie unbemerkt in den Kontext deines Agenten.

Alle V2-Endpunkte verlangen einen privaten API-Key. Die Referenz auf Endpunktebene findest du in der [V2-Übersicht](/de/docs/v2-overview).

### Perceive

Rendere eine URL in genau die Artefakte, die du anforderst. `perceive` arbeitet synchron und gibt die abgeschlossene Operation mit frisch signierten Artefakt-URLs zurück.

```ruby
op = client.v2.perceive(
  "https://example.com",
  outputs: %w[markdown screenshot structured],
  extract: %w[tables metadata],
  only_main_content: true
)

puts op.operation_id, op.render_quality          # "per_...", dann 0.0 bis 1.0
puts op.outputs["markdown"].url                  # signierte Artefakt-URL
puts op.outputs["markdown"].expires_in           # Sekunden, bis sie nicht mehr funktioniert
puts op.structured, op.warnings.inspect          # einfacher Ruby-Hash, dann Array of String
```

Signiere die Artefakt-URLs einer früheren Operation jederzeit neu:

```ruby
again = client.v2.get_perceive_operation(op.operation_id)
```

Perceive bis zu 1000 URLs mit einem gemeinsamen Options-Block. Kleine Batches laufen inline und kommen abgeschlossen zurück. Größere kommen mit dem Status `"queued"` zurück, frage sie also per `job_id` ab:

```ruby
batch = client.v2.perceive_batch(
  ["https://a.example.com", "https://b.example.com"],
  outputs: %w[markdown],
  output_mode: "zip"
)

done = client.v2.get_perceive_batch(batch.job_id)
puts "#{done.completed}/#{done.total} complete, #{done.failed} failed"
puts done.zip&.url
```

Überspringe den Umweg über die signierte URL vollständig mit `perceive_direct`, wo der HTTP-Response-Body das Artefakt selbst ist:

```ruby
direct = client.v2.perceive_direct("https://example.com", outputs: %w[markdown])
puts direct.filename, direct.content_type, direct.content.bytesize
File.binwrite("page.md", direct.content)

# Ein gespeichertes Artefakt einer früheren Operation erneut herunterladen.
raw = client.v2.download_perceive_artifact(op.operation_id, output: "markdown")
```

`perceive_direct` braucht genau eine artefakterzeugende Ausgabe und löst andernfalls lokal `Enconvert::Error` aus. `structured` darf mitfahren, weil es serverseitig inline bleibt und nie gestreamt wird. `download_perceive_artifact` nimmt ein optionales `output:`, das du weglassen kannst, wenn die Operation genau ein Artefakt erzeugt hat, und benennen musst, wenn sie mehr als eines erzeugt hat; es gibt `410` zurück, sobald ein Artefakt seine Aufbewahrungsfrist überschritten hat.

| Option | Typ | Beschreibung |
|--------|------|-------------|
| `outputs` | `Array` | Beliebige aus `markdown`, `html_cleaned`, `html_raw`, `screenshot`, `screenshot_full_page`, `pdf`, `links`, `images`, `structured`. Standard serverseitig `["markdown", "structured"]`. |
| `extract` | `Array` | Beliebige aus `tables`, `prices`, `contacts`, `metadata`, `main_content`, `headings`, `structured_data`, `technologies`, `all`. |
| `schema` | `Hash` | Feldbeschreibungen für die strukturierte Extraktion. |
| `wait_for`, `wait_timeout_ms` | `String`, `Integer` | CSS-Selektor, auf den vor der Erfassung gewartet wird, und die Obergrenze für dieses Warten. |
| `js_code` | `String` | JavaScript, das vor der Erfassung in der Seite ausgeführt wird. |
| `viewport`, `mobile` | `Hash`, `Boolean` | Viewport-Maße und ob mit einem Mobile-Profil gerendert wird. |
| `headers`, `cookies`, `auth` | `Hash`, `Array`, `Hash` | Browser-Zugriff für geschützte Seiten. |
| `proxy_url` | `String` | Leitet das Rendering über deinen eigenen Proxy. |
| `geolocation` | `Hash` | Vorgetäuschte Geolokation für den Browser-Kontext. |
| `action_chain` | `Array` | Skriptgesteuerte Klicks, Scrolls und Eingaben vor der Erfassung. |
| `cache_mode` | `String` | `enabled`, `bypass` oder `refresh`. |
| `pdf_options` | `Hash` | Seitengeometrie, wenn `pdf` unter den Ausgaben ist. |
| `block_resources` | `Array` | Ressourcentypen, die blockiert werden, zum Beispiel `image`, `font`, `script`. |
| `respect_robots` | `Boolean` | Berücksichtigt `robots.txt`. |
| `only_main_content` | `Boolean` | Entfernt Navigation und Beiwerk aus der Markdown-Ausgabe. |
| `direct_download` | `Boolean` | Nur von `perceive` akzeptiert. `perceive_batch` lehnt es ab. |

Die vollständige Parameter-Semantik findest du unter [Perceive](/de/docs/v2-perceive) und [Parameter und Optionen](/de/docs/parameters-options).

### Discover

Zähle die URLs einer Website auf, ohne irgendetwas zu rendern. Es ist kein Browser beteiligt, daher ist es schnell und günstig.

```ruby
found = client.v2.discover(
  "https://example.com",
  mode: "hybrid",            # "sitemap", "crawl" oder "hybrid"
  max_urls: 200,
  max_depth: 3,
  exclude_patterns: ["/tag/"],
  same_domain_only: true,
  respect_robots: true
)

puts found.total, found.urls.first(10)
puts found.truncated        # true, wenn max_urls das Ergebnis begrenzt hat
puts found.sources.inspect  # woher jede URL stammt
```

Siehe [Discover](/de/docs/v2-discover) für das Verhalten je Modus.

### Lookup

Führe eine kategorisierte Websuche aus und perceive die besten Treffer auf Wunsch im selben Aufruf.

```ruby
search = client.v2.lookup(
  "best static site generators",
  category: "web",           # web, news, images, scholar, patents, maps
  num_results: 10,
  time_filter: "month",      # hour, day, week, month, year
  perceive_top: 3
)

search.results.each do |hit|
  puts hit.position, hit.title, hit.url
  puts hit.perceive&.render_quality  # vorhanden bei automatisch gerenderten Treffern
end

puts search.answer_box.inspect, search.knowledge_graph.inspect
```

Mit `perceive_top: 3` werden die ersten drei Treffer-URLs gerendert und tragen ein vollständiges `PerceiveResult` inline auf `hit.perceive`. Ihre Operations-IDs werden zusätzlich in `search.perceive_operation_ids` gesammelt. Mehr dazu unter [Lookup](/de/docs/v2-lookup).

### Distill

Schemagesteuerte strukturierte Extraktion. Gib genau eines von `urls:` oder `discover_from:` an, und `schema:` ist immer erforderlich. Das SDK löst lokal `Enconvert::Error` aus, wenn du das falsch machst.

```ruby
extraction = client.v2.distill(
  urls: ["https://example.com/pricing"],
  schema: { plans: "list of plan names with monthly prices" },
  css_schema: {
    base_selector: ".plan-card",
    fields: [
      { name: "name",  type: "text", selector: "h3" },
      { name: "price", type: "text", selector: ".price" }
    ]
  }
)

item = extraction.results.first
puts item.data.inspect
puts item.extraction_tier   # "css", "llm", "mixed" oder "none"
puts item.fields_from_css, item.fields_from_llm
```

Das optionale `css_schema` läuft zuerst und beantwortet alles, was es allein über Selektoren schafft. Nur die Felder, die es verfehlt, steigen in die Sprachmodell-Stufe auf, weshalb `fields_from_css` und `fields_from_llm` getrennt gemeldet werden.

Ermitteln und destillieren in einem Aufruf:

```ruby
client.v2.distill(
  discover_from: { url: "https://example.com", mode: "sitemap", max_pages: 10 },
  schema: { title: "page title", summary: "one-line summary" }
)
```

Die CSS-Feldtypen sind `text`, `attribute`, `html`, `regex`, `nested`, `list` und `nested_list`. Verschachtelte Feldlisten werden rekursiv serialisiert. Siehe [Distill](/de/docs/v2-distill).

### Ingest

Verwandle eine ganze Website oder einen Satz hochgeladener Dokumente über eine einzige Pipeline in gechunktes, RAG-fertiges JSONL. Ingest arbeitet immer asynchron.

```ruby
job = client.v2.ingest(
  mode: "sitemap",           # urls, sitemap, crawl
  url: "https://docs.example.com",
  max_pages: 100,
  chunk: { max_words: 512, sentence_overlap: 1 },
  webhook_url: "https://my.app/hooks/enconvert"
)

status = client.v2.get_ingest_job(job.job_id)
puts status.status                                   # queued, discovering, processing, completed, failed, canceled
puts status.pages_processed, status.total_chunks
puts status.output_url if status.status == "completed"  # JSONL
```

Der Modus `"urls"` nimmt eine `urls:`-Liste und lehnt `url:` ab. Jeder andere Modus nimmt eine Start-`url:` und lehnt `urls:` ab. Beide Regeln werden lokal erzwungen, bevor die Anfrage gesendet wird.

Dateien hochladen statt crawlen:

```ruby
file_job = client.v2.ingest_files(
  ["handbook.pdf", "notes.docx"],
  chunk: { max_words: 512, sentence_overlap: 1 }
)
```

`ingest_files` akzeptiert PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT und MD sowie ältere und ODF-Office-Formate. Jeder Eintrag kann ein Pfad, ein IO-artiges Objekt oder ein `{ data:, filename: }`-Hash sein.

Jobs und Webhook-Zustellung verwalten:

```ruby
list = client.v2.list_ingest_jobs(limit: 20)
list.jobs.each { |j| puts "#{j.job_id}\t#{j.status}\t#{j.total_chunks}" }
puts list.has_more

client.v2.cancel_ingest_job(job.job_id)   # idempotent

secret = client.v2.get_webhook_secret
puts secret.secret, secret.signature_header, secret.timestamp_header,
     secret.signature_scheme, secret.replay_tolerance_seconds

client.v2.rotate_webhook_secret            # alte Signaturen verifizieren sofort nicht mehr
client.v2.retry_ingest_webhook(job.job_id) # Webhook eines abgeschlossenen Jobs erneut zustellen
```

`retry_ingest_webhook` gibt ein `WebhookRetryResult` mit `delivered`, `attempts`, `status_code` und `detail` zurück. Es antwortet `409`, wenn der Job nicht abgeschlossen ist, und `400`, wenn für den Job kein Webhook konfiguriert ist. Siehe [Ingest](/de/docs/v2-ingest).

### Watch

Rendere eine URL in festem Takt erneut und lass dich benachrichtigen, wenn sie sich ändert.

```ruby
watcher = client.v2.create_watcher(
  "https://example.com/pricing",
  frequency_minutes: 60,     # Untergrenze eine Stunde
  diff_mode: "auto",         # auto, text, structured, tables, metadata
  webhook_url: "https://my.app/hooks/changes",
  notify_email: true
)

puts watcher.watcher_id, watcher.next_check_at

history = client.v2.get_watcher_snapshots(watcher.watcher_id, limit: 10)
history.snapshots.each do |snap|
  puts snap.checked_at, snap.has_changes, snap.similarity, snap.change_count, snap.changes.inspect
end

client.v2.list_watchers(limit: 20)
client.v2.get_watcher(watcher.watcher_id)
client.v2.update_watcher(watcher.watcher_id, status: "paused")
client.v2.update_watcher(watcher.watcher_id, webhook_url: "")  # löscht den Webhook
client.v2.delete_watcher(watcher.watcher_id)                   # Soft Delete, idempotent
```

`update_watcher` löst `Enconvert::Error` aus, wenn du es ohne zu aktualisierende Felder aufrufst. `delete_watcher` gibt den als gelöscht markierten Watcher mit Status `"deleted"` zurück, und ein gelöschter Watcher liest sich danach als `404`. Die vollständige Semantik steht unter [Watch](/de/docs/v2-watch).

---

## PDF-Optionen

`pdf_options` ist ein einfacher Hash mit Symbol-Schlüsseln, akzeptiert von `convert_url_to_pdf`, `convert_website_to_pdf`, `convert_document`, `convert_to_pdf` (nur Graustufen) und `client.v2.perceive`, wenn `pdf` unter den Ausgaben ist. Nur die Schlüssel, die du setzt, gehen über die Leitung.

```ruby
client.convert_url_to_pdf(
  "https://example.com",
  pdf_options: { page_size: "A4", orientation: "landscape", scale: 0.9,
                 margins: { top: 10, bottom: 10, left: 15, right: 15 } },
  save_to: "report.pdf"
)
```

| Schlüssel | Typ | Beschreibung |
|-----|------|-------------|
| `page_size` | `String` | `"A4"`, `"A3"`, `"Letter"`, `"Legal"` und Ähnliches. |
| `page_width` | `Number` | Explizite Seitenbreite als Alternative zu `page_size`. |
| `page_height` | `Number` | Explizite Seitenhöhe. |
| `orientation` | `String` | `"portrait"` oder `"landscape"`. |
| `margins` | `Hash` | `{ top:, bottom:, left:, right: }`, alle optional. |
| `scale` | `Number` | Render-Skalierung, zum Beispiel `0.9` für 90 Prozent. |
| `grayscale` | `Boolean` | Wandelt das PDF nachträglich in Graustufen um. |
| `header` | `Hash` | Kopfzeilentext je Seitenbereich. |
| `footer` | `Hash` | Fußzeilentext je Seitenbereich. |

---

## Fehlerbehandlung

Jeder Fehler ist ein `Enconvert::Error` oder eine seiner Unterklassen, `rescue`-Klauseln können also so breit oder so eng sein, wie du willst. Clientseitige Validierungsfehler, etwa ein nicht unterstütztes Konvertierungspaar oder ein fehlendes `schema`, lösen die Basisklasse `Enconvert::Error` aus, bevor eine HTTP-Anfrage gestellt wird.

```ruby
begin
  client.v2.perceive("https://example.com")
rescue Enconvert::AuthenticationError
  warn "Invalid or missing API key"
rescue Enconvert::QuotaError
  warn "Request was rejected with 402"
rescue Enconvert::RateLimitError
  warn "Too many requests, back off and retry"
rescue Enconvert::APIError => e
  warn "API error [#{e.status_code}]: #{e.message}"
rescue Enconvert::Error => e
  warn "Client-side validation failed: #{e.message}"
end
```

| Klasse | Ausgelöst bei | Statuscode |
|-------|-----------|-------------|
| `Enconvert::AuthenticationError` | Ungültiger, fehlender oder widerrufener API-Key | `401`, `403` |
| `Enconvert::QuotaError` | Ausgelöst bei HTTP 402 | `402` |
| `Enconvert::RateLimitError` | Zu viele Anfragen | `429` |
| `Enconvert::APIError` | Jede andere Antwort ab 400 | der tatsächliche Code |
| `Enconvert::Error` | Basisklasse, plus alle lokalen Validierungsfehler | keiner |

`APIError#status_code` liefert dir den HTTP-Status, und die Meldung wird aus dem Feld `detail` oder `error` des Response-Body gezogen, wenn dieser JSON ist. Die vollständige Zuordnung der Meldungen steht unter [Fehlercodes](/de/docs/error-codes).

---

## Timeout-Recovery

Ein langes URL-zu-PDF-Rendering oder eine große Dokumentkonvertierung kann das Reverse-Proxy-Timeout überdauern, selbst wenn die Konvertierung auf dem Server gelingt. Das SDK fängt das transparent auf, ohne Code auf deiner Seite.

1. Vor jeder Konvertierungsanfrage erzeugt der Client eine 32 Zeichen lange Job-ID und sendet sie als `job_id` im Body oder im Multipart-Formular.
2. Kommt die Anfrage mit einem Status ab 500 zurück, wechselt der Client dazu, `GET /v1/convert/status/{job_id}` alle 3 Sekunden abzufragen. Ein `404`, während die Job-Zeile noch geschrieben wird, wird ignoriert, und das Polling läuft weiter.
3. Sobald der Job `success` meldet, wird das Ergebnis zurückgegeben. Sobald er `failed` meldet, wird `Enconvert::APIError` mit Status `500` und der Fehlermeldung des Servers ausgelöst.
4. Die Polling-Frist beträgt 300 Sekunden. Danach löst der Client `Enconvert::APIError` mit Status `504` und der Meldung `Conversion timed out` aus.

Die vom Client erzeugte `job_id` wird außerdem in jede erfolgreiche Antwort übernommen, `result.job_id` ist also immer gefüllt, und du kannst `get_job_status` später selbst abfragen, wenn du möchtest.

<div class="alert alert-info">
<strong>Website-Batch-Übermittlungen nehmen nicht teil.</strong> <code>convert_website_to_pdf</code> und <code>convert_website_to_screenshot</code> erzeugen keine Job-Zeile, ein 5xx bedeutet dort also, dass die Übermittlung selbst fehlgeschlagen ist, und wird sofort gemeldet statt abgefragt.
</div>

---

## Konfiguration

```ruby
client = Enconvert::Client.new(
  api_key: ENV.fetch("ENCONVERT_API_KEY"),
  timeout: 300,
  base_url: "https://api.enconvert.com"
)
```

| Option | Typ | Standard | Beschreibung |
|--------|------|---------|-------------|
| `api_key` | `String` | erforderlich | Dein privater API-Key. Ein `nil`-Wert oder ein leerer Wert löst beim Erzeugen `Enconvert::Error` aus. |
| `timeout` | `Integer` | `300` | Open- und Read-Timeout in **Sekunden**, angewendet auf jede Anfrage. |
| `base_url` | `String` | `"https://api.enconvert.com"` | Basis-URL der API. Abschließende Schrägstriche werden entfernt. Überschreibe sie für ein selbst gehostetes Gateway. |

Anfragen authentifizieren sich mit dem Header `X-API-Key`. Wenn du `save_to` übergibst, umgeht der Download diesen Header bewusst, denn die vorsignierte Speicher-URL darf deinen API-Key nicht erhalten.

<div class="alert alert-warning">
<strong>Schreibe den API-Key niemals fest in den Code.</strong> Lies ihn aus einer Umgebungsvariablen, aus Rails Credentials oder aus deinem Secret-Manager. Wer deinen privaten Key hat, kann Konvertierungen in deinem Namen ausführen. Rotiere ihn im <a href="/de/dashboard">Dashboard</a>, falls er durchsickert.
</div>

---

## Ergebnisform

Jede Konvertierung einer einzelnen Datei oder einer einzelnen Seite gibt ein `Enconvert::ConversionResult` zurück:

```ruby
result = client.convert_document("report.docx", save_to: "report.pdf")

result.presigned_url            # signierte URL für die konvertierte Ausgabe
result.object_key               # Objekt-Key im Speicher
result.filename                 # serverseitiger Dateiname
result.file_size                # Bytes oder nil
result.conversion_time_seconds  # Sekunden oder nil
result.job_id                   # wird immer vom Client gefüllt
```

Die übrigen V1-Structs sind `JobStatus` (`status`, `presigned_url`, `object_key`, `error`), `BatchSubmission` (`batch_id`, `status`, `url_count`, `total_discovered`, `discovery_method`, `output_format`), `BatchStatus` (`batch_id`, `status`, `total`, `completed`, `failed`, `in_progress`, `output_mode`, `zip_download_url`, `items`) und `BatchItem` (`source_url`, `status`, `download_url`, `output_file_size`, `duration`).

Auf der V2-Seite ist `PerceiveResult` das entscheidende:

```ruby
op.operation_id                              # "per_..."
op.status                                    # queued, processing, completed, failed
op.url, op.url_final, op.content_hash
op.render_quality, op.cache_hit              # 0.0 bis 1.0 (oder nil) und ob es aus dem Cache kam
op.outputs                                   # Hash aus Name => V2OutputArtifact
op.structured, op.extraction_tier            # Hash (oder nil) und wie extrahiert wurde
op.tokens.input, op.tokens.output, op.cost_cents, op.duration_ms
op.error, op.warnings                        # String oder nil, und Array of String
op.status_code                               # HTTP-Status der Ursprungsseite
op.deductions, op.options_echo               # warum render_quality sank, und die verwendeten Optionen
```

Ein `V2OutputArtifact` trägt `url`, `object_key`, `size_bytes`, `content_type` und `expires_in` (standardmäßig 900 Sekunden), signierte Artefakt-URLs sind also kurzlebig. Signiere sie mit `get_perceive_operation` neu oder lade die Bytes mit `download_perceive_artifact` herunter. Von dir gelieferte Payloads wie Schemata, extrahierte Daten, überwachte Felder und Diff-Änderungen werden unverändert als einfache Ruby-Hashes durchgereicht.

Vorsignierte URLs aus V1-Konvertierungen sind ebenfalls temporär. Übergib `save_to`, wenn du die Bytes auf der Festplatte willst, oder kopiere sie für dauerhafte Ablage in deinen eigenen Bucket.

---

## Quelle und Issues

- **RubyGems:** [enconvert](https://rubygems.org/gems/enconvert)
- **GitHub:** [conversionapi/ruby-sdk](https://github.com/conversionapi/ruby-sdk)
- **Ruby:** 3.0.0 oder neuer, keine Laufzeitabhängigkeiten
- **Lizenz:** MIT
- **Weitere Sprachen:** [Alle SDKs](/de/docs/sdks)
- **API-Referenz:** [Endpunkt-Übersicht](/de/docs/endpoints-overview), [Authentifizierung](/de/docs/authentication)

---

## Häufig gestellte Fragen

### Wie konvertiere ich Dateien in Ruby mit einem Gem?

Führe `gem install enconvert` aus, baue einen Client mit `Enconvert::Client.new(api_key: ENV.fetch("ENCONVERT_API_KEY"))` und rufe eine Methode wie `convert_document`, `convert_image` oder `convert_url_to_pdf` auf. Übergib `save_to:`, und das SDK lädt das Ergebnis für dich an diesen Pfad und legt dabei übergeordnete Verzeichnisse an.

### Wie konvertiere ich DOCX in Ruby in ein PDF?

`client.convert_document("report.docx", save_to: "report.pdf")`. Das Ausgabeformat ist standardmäßig `"pdf"`, du brauchst `output_format:` also nur, wenn du etwas anderes willst. Derselbe Aufruf funktioniert für `.doc`, `.xls`, `.xlsx`, `.ppt`, `.pptx`, `.odt`, `.ods`, `.odp`, `.ots`, `.pages` und `.numbers`.

### Wie konvertiere ich eine URL in Ruby in ein PDF?

`client.convert_url_to_pdf("https://example.com", save_to: "page.pdf")`. Setze `single_page: false`, um zu paginieren statt eine fortlaufende Seite zu erzeugen, und übergib `pdf_options:` für Seitengröße, Ausrichtung, Ränder und Skalierung. Für eine Seite hinter HTTP-Basic-Auth ergänze `auth: { username: ..., password: ... }`.

### Wie konvertiere ich HEIC in Ruby in WebP?

`client.convert_image("photo.heic", output_format: "webp", save_to: "photo.webp")`. Das Eingabeformat ergibt sich aus der Dateiendung, und das Gem konvertiert frei zwischen `jpeg`, `png`, `svg`, `heic` und `webp`. Mit `output_format: "jpeg"` rastert es außerdem ein PDF zu JPEG.

### Wie scrape ich aus Ruby eine Webseite in Markdown?

Es gibt zwei Möglichkeiten. `client.convert_url_to_markdown(url, save_to: "article.md")` liefert dir sauberes GitHub-Flavored Markdown mit YAML-Frontmatter. `client.v2.perceive(url, outputs: %w[markdown])` liefert denselben Inhalt plus einen `render_quality`-Wert, Warnungen und optionale strukturierte Extraktion, und genau das willst du, wenn ein Agent das Ergebnis lesen soll.

### Was bedeutet render_quality und warum sollte ich es prüfen?

Es ist ein Wert von 0.0 bis 1.0, der an jedem V2-Lesevorgang hängt und widerspiegelt, wie sauber die Seite tatsächlich gerendert wurde. Eine Challenge-Seite, eine Cookie-Wall, ein Login-Gate, eine HTTP-Fehlerseite oder eine leere JavaScript-Hülle erhalten alle einen niedrigen Wert. Der Inhalt wird trotzdem zurückgegeben, damit du ihn prüfen kannst, und `warnings` und `deductions` erklären den Wert. Mach ihn zur Bedingung, bevor du den Text speicherst oder an ein Modell gibst.

### Wiederholt das Ruby SDK den Versuch, wenn eine lange Konvertierung das Timeout erreicht?

Ja. Jede Konvertierungsanfrage trägt eine vom Client erzeugte `job_id`, und wenn die Anfrage mit einem Status ab 500 scheitert, fragt das SDK `GET /v1/convert/status/{job_id}` alle 3 Sekunden für bis zu 300 Sekunden ab. Es gibt das Ergebnis zurück, sobald der Job gelingt, und löst `Enconvert::APIError` mit Status `504` aus, wenn die Frist verstreicht. Batch-Übermittlungen für ganze Websites sind ausgenommen, weil sie keine Job-Zeile haben.

### Was passiert, wenn ich ein Konvertierungspaar anfordere, das die API nicht implementiert?

Das Gem löst lokal `Enconvert::Error` aus, noch vor jedem Netzwerkaufruf, und die Meldung listet die gültigen Ausgaben für diese Eingabe auf. Dieselbe Tabelle kannst du selbst mit `Enconvert.valid_outputs_for("json")` prüfen, was `["csv", "toml", "xml", "yaml"]` zurückgibt.

### Kann ich das enconvert-Gem in Rails oder in einem Background-Job verwenden?

Ja, und ein Background-Job ist der richtige Ort dafür. Das Gem ist reines `net/http` ohne Laufzeitabhängigkeiten und ohne globalen Zustand, eine `Enconvert::Client`-Instanz lässt sich also gefahrlos pro Job bauen oder pro Prozess memoisieren. Lange Konvertierungen blockieren den aufrufenden Thread bis zum konfigurierten `timeout`, das standardmäßig bei 300 Sekunden liegt, halte sie also aus dem Web-Request-Zyklus heraus.

### Welche Ruby-Versionen unterstützt das SDK?

Ruby 3.0.0 und neuer, wie über `required_ruby_version` in der Gemspec deklariert. Es gibt keine Laufzeitabhängigkeiten, es installiert sich also in jede Bundler-Gruppe, ohne einen Abhängigkeitsbaum nach sich zu ziehen.

### Wie verifiziere ich die Signatur eines Ingest-Webhooks?

Rufe `client.v2.get_webhook_secret` auf, was das Signier-Secret des Projekts bei der ersten Nutzung erzeugt und es zusammen mit `signature_header`, `timestamp_header`, `signature_scheme` und `replay_tolerance_seconds` zurückgibt. Berechne den HMAC über den zugestellten Body und vergleiche ihn mit dem Signatur-Header. Nutze `rotate_webhook_secret`, um das alte Secret ungültig zu machen, und `retry_ingest_webhook(job_id)`, um die Benachrichtigung eines abgeschlossenen Jobs erneut zuzustellen.
