enconvert 0.0.1 → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 803e8f4b068717fa3813eb4266afb426ac0d70da63378556a69f231a52c02bb0
4
- data.tar.gz: 261b1c69bd73b44f0be655a70820549108f784bafd2e9f0c21f74f0e76cf265a
3
+ metadata.gz: 7245afabefb24e84a42769dc04a0dfe1693db85f6a98576dcd1d68a6c3fe4722
4
+ data.tar.gz: 5a5aecb734881a6e44680d749efc8813894bc9e75acf26871c6992a35fdaa9ef
5
5
  SHA512:
6
- metadata.gz: 8a94b8bfdd0429848d27857da9e74a585ac553589843ba5f183b0f53847b26e2fe33a08b42cd46837dedf74fa61668776f6e619713a1d70440fbb84eb5703d8e
7
- data.tar.gz: db4826110029dbe06a0ccd1bcd769d944bb286c56f61644a569c93a54fe9199bd221b0987a800cb6e53b79d2b0703e5a629255a4278e3d1a67b9e6fc753c0e1b
6
+ metadata.gz: '008838d207976db15e7bdf12df5fde2b3820ac20141421ed2d880d80d9808891005fc7f5e7e85af8f1fd9861bfd59ca28c82c0f3f1df7bf9c0394b6835c617fa'
7
+ data.tar.gz: c220ba5c98cc41e920aff61f59310ab3c846f831bdf679bf6ba182150780b120906b22b2af5ea147932a8ecda9a6e404a74a7999127649616415742abce58064
data/README.md CHANGED
@@ -1,308 +1,316 @@
1
- # Enconvert Ruby SDK
2
-
3
- Honest eyes for your AI agent — the Ruby SDK for [Enconvert](https://enconvert.com). Ruby 3.0+.
4
-
5
- Read any web page or file into clean Markdown, JSON, or screenshots, and get a `render_quality` score (0.0–1.0) on **every** read — so a blocked, challenge, or empty-SPA page comes back flagged with a low score and warnings, never mistaken for real content. Perceive, discover, look up, distill, ingest, and watch the web; convert 40+ file and document formats through the same key.
6
-
7
- > Wiring an agent (Claude, Cursor, Windsurf, n8n, …)? The [MCP server](https://enconvert.com/mcp) is the native path — `npx @enconvert/mcp setup`. This SDK is the programmatic REST path for everything else.
8
-
9
- ## Install
10
-
11
- ```bash
12
- gem install enconvert
13
- ```
14
-
15
- Or add to your Gemfile:
16
-
17
- ```ruby
18
- gem "enconvert"
19
- ```
20
-
21
- ## Quick Start
22
-
23
- ```ruby
24
- require "enconvert"
25
-
26
- client = Enconvert::Client.new(api_key: "sk_...")
27
-
28
- # Read a page the way your agent should — with a quality score attached.
29
- op = client.v2.perceive("https://example.com", outputs: %w[markdown structured])
30
- puts op.outputs["markdown"].url, op.render_quality # e.g. 0.93
31
- ```
32
-
33
- ---
34
-
35
- # V2 — agent-ready data (`client.v2`)
36
-
37
- The V2 namespace turns web pages into agent-ready data: render, search, extract, ingest, and monitor. All V2 endpoints require a **private API key** and are plan-gated — a disabled feature or exhausted monthly quota raises `Enconvert::QuotaError` (HTTP 402).
38
-
39
- Every render carries `render_quality` (0.0–1.0). A low score means the page didn't render cleanly (challenge page, cookie wall, empty shell); the content is still returned, flagged, so a bad read never quietly enters your agent's context.
40
-
41
- ### Perceive — render a URL into artifacts
42
-
43
- ```ruby
44
- op = client.v2.perceive(
45
- "https://example.com",
46
- outputs: %w[markdown screenshot structured],
47
- extract: %w[tables metadata]
48
- )
49
- puts op.render_quality # honesty score, 0.0–1.0
50
- puts op.outputs["markdown"].url # 15-min signed URL
51
- puts op.structured
52
-
53
- # Re-sign artifact URLs later:
54
- again = client.v2.get_perceive_operation(op.operation_id)
55
-
56
- # Batch (<=1000 URLs; small batches run inline, larger return "queued" — poll):
57
- batch = client.v2.perceive_batch(
58
- ["https://a.com", "https://b.com"],
59
- outputs: ["markdown"],
60
- output_mode: "zip"
61
- )
62
- done = client.v2.get_perceive_batch(batch.job_id)
63
- ```
64
-
65
- ### Discover enumerate a site's URLs (no rendering)
66
-
67
- ```ruby
68
- found = client.v2.discover(
69
- "https://example.com",
70
- mode: "hybrid", # "sitemap" | "crawl" | "hybrid"
71
- max_urls: 200,
72
- exclude_patterns: ["/tag/"]
73
- )
74
- puts found.total, found.urls
75
- ```
76
-
77
- ### Lookup — web search with optional auto-perceive
78
-
79
- ```ruby
80
- search = client.v2.lookup(
81
- "best static site generators",
82
- category: "web", # web | news | images | scholar | patents | maps
83
- num_results: 10,
84
- perceive_top: 3 # auto-render top 3 results (uses perceive quota)
85
- )
86
- search.results.each { |hit| puts hit.title, hit.url, hit.perceive&.render_quality }
87
- ```
88
-
89
- ### Distill schema-driven structured extraction
90
-
91
- ```ruby
92
- extraction = client.v2.distill(
93
- urls: ["https://example.com/pricing"],
94
- schema: { plans: "list of plan names with monthly prices" },
95
- css_schema: { # optional free CSS pass before the LLM tier
96
- base_selector: ".plan-card",
97
- fields: [
98
- { name: "name", type: "text", selector: "h3" },
99
- { name: "price", type: "text", selector: ".price" }
100
- ]
101
- }
102
- )
103
- puts extraction.results.first.data, extraction.results.first.extraction_tier
104
-
105
- # Or discover-then-distill:
106
- client.v2.distill(
107
- discover_from: { url: "https://example.com", mode: "sitemap", max_pages: 10 },
108
- schema: { title: "page title", summary: "one-line summary" }
109
- )
110
- ```
111
-
112
- ### Ingest — site or files to RAG-ready JSONL (always async)
113
-
114
- Turn a whole site — or a set of uploaded documents — into chunked, RAG-ready JSONL through one pipeline.
115
-
116
- ```ruby
117
- # From a site:
118
- job = client.v2.ingest(
119
- mode: "sitemap",
120
- url: "https://docs.example.com",
121
- max_pages: 100,
122
- chunk: { max_words: 512, sentence_overlap: 1 },
123
- webhook_url: "https://my.app/hooks/enconvert"
124
- )
125
-
126
- # Or from uploaded files (PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, legacy/ODF office):
127
- file_job = client.v2.ingest_files(
128
- ["handbook.pdf", "notes.docx"],
129
- chunk: { max_words: 512, sentence_overlap: 1 }
130
- )
131
-
132
- status = client.v2.get_ingest_job(job.job_id) # poll
133
- puts status.output_url if status.status == "completed" # JSONL
134
-
135
- client.v2.list_ingest_jobs(limit: 20)
136
- client.v2.cancel_ingest_job(job.job_id) # idempotent
137
-
138
- # Webhook signing (HMAC):
139
- secret = client.v2.get_webhook_secret
140
- puts secret.secret, secret.signature_header
141
- client.v2.rotate_webhook_secret # invalidates old secret
142
- client.v2.retry_ingest_webhook(job.job_id) # re-deliver
143
- ```
144
-
145
- ### Watch — recurring change monitoring
146
-
147
- ```ruby
148
- watcher = client.v2.create_watcher(
149
- "https://example.com/pricing",
150
- frequency_minutes: 60, # hourly floor
151
- diff_mode: "auto", # auto | text | structured | tables | metadata
152
- webhook_url: "https://my.app/hooks/changes",
153
- notify_email: true
154
- )
155
-
156
- client.v2.list_watchers
157
- client.v2.get_watcher(watcher.watcher_id)
158
- client.v2.get_watcher_snapshots(watcher.watcher_id, limit: 10)
159
- client.v2.update_watcher(watcher.watcher_id, status: "paused")
160
- client.v2.update_watcher(watcher.watcher_id, webhook_url: "") # clears webhook
161
- client.v2.delete_watcher(watcher.watcher_id) # soft-delete, idempotent
162
- ```
163
-
164
- ### V2 error handling
165
-
166
- ```ruby
167
- begin
168
- client.v2.ingest(mode: "sitemap", url: "https://example.com")
169
- rescue Enconvert::QuotaError
170
- warn "Upgrade plan or wait for quota reset"
171
- end
172
- ```
173
-
174
- ---
175
-
176
- # File conversion
177
-
178
- The same key also converts 40+ formats. Two "anything → X" endpoints auto-detect the input; the format-specific endpoints below give you a validated, typed path.
179
-
180
- ### Anything to Markdown / PDF
181
-
182
- ```ruby
183
- # Any document → clean Markdown (a RAG-ingestion building block):
184
- client.convert_to_markdown("report.docx", save_to: "report.md")
185
- # PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, and legacy/ODF office. (Images not supported.)
186
-
187
- # Almost anything → PDF:
188
- client.convert_to_pdf("slides.pptx", save_to: "slides.pdf")
189
- # office/ODF/Pages/Numbers/RTF/CSV, HTML, Markdown, text, images, SVG, EPUB, or a PDF passthrough.
190
- # Only pdf_options[:grayscale] is honored on this endpoint:
191
- client.convert_to_pdf("scan.pdf", pdf_options: { grayscale: true }, save_to: "gray.pdf")
192
- ```
193
-
194
- ### Image conversion
195
-
196
- ```ruby
197
- result = client.convert_image("photo.heic", output_format: "webp", save_to: "photo.webp")
198
- ```
199
-
200
- Any pair among `jpeg`, `png`, `svg`, `heic`, `webp` — plus PDF rasterization (`pdf` → `jpeg`). Unsupported pairs raise before any request is made:
201
-
202
- ```ruby
203
- Enconvert.valid_outputs_for("json") # => ["csv", "toml", "xml", "yaml"]
204
- Enconvert.valid_outputs_for("pdf") # => ["jpeg"]
205
- ```
206
-
207
- ### Document & data conversion
208
-
209
- ```ruby
210
- client.convert_document("report.docx", save_to: "report.pdf")
211
- client.convert_document("data.json", output_format: "yaml", save_to: "data.yaml")
212
- client.convert_document("notes.md", output_format: "html", save_to: "notes.html")
213
- ```
214
-
215
- Supported inputs: `doc`/`docx`, `xls`/`xlsx`, `ppt`/`pptx`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers`, `html`, `markdown`, `csv`, `json`, `xml`, `yaml`, `toml`. (EPUB → use `convert_to_pdf` / `convert_to_markdown`.)
216
-
217
- The SDK validates every `{input}-to-{output}` pair against the conversions the API actually implements and raises immediately — with the list of valid outputs for that input — instead of sending a doomed request.
218
-
219
- ### Supported conversions
220
-
221
- | Input | Outputs |
222
- |-------|---------|
223
- | json | csv, toml, xml, yaml |
224
- | xml | csv, json |
225
- | yaml | json |
226
- | csv | json, xml |
227
- | toml | json |
228
- | markdown | html, pdf |
229
- | html | pdf |
230
- | doc, excel, ppt, odt, ods, odp, ots, pages, numbers | pdf |
231
- | jpeg, png, svg, heic, webp | each other (all 20 pairs) |
232
- | pdf | jpeg |
233
-
234
- ### URL to PDF / Screenshot / Markdown
235
-
236
- ```ruby
237
- client.convert_url_to_pdf("https://example.com", save_to: "page.pdf")
238
- client.convert_url_to_screenshot("https://example.com", viewport_width: 1440, save_to: "shot.png")
239
- client.convert_url_to_markdown("https://example.com/article", save_to: "article.md")
240
- ```
241
-
242
- ### Website to PDF / Screenshot (whole-site batch)
243
-
244
- Discover every page of a website (sitemap, or full crawl on higher plans), convert each in the background, and receive a single ZIP. Requires a private API key with crawl access.
245
-
246
- ```ruby
247
- batch = client.convert_website_to_pdf("https://example.com", crawl_mode: "sitemap")
248
- status = client.wait_for_batch(batch.batch_id, save_to: "site.zip")
249
- puts "#{status.completed} of #{status.total} pages converted"
250
- ```
251
-
252
- `convert_website_to_screenshot` works the same way and produces a ZIP of PNGs.
253
-
254
- ### PDF options & authenticated pages
255
-
256
- ```ruby
257
- client.convert_url_to_pdf(
258
- "https://internal.example.com/report",
259
- pdf_options: { page_size: "A4", orientation: "landscape", margins: { top: 10, bottom: 10 } },
260
- auth: { username: "user", password: "pass" }, # or cookies / headers, plan-gated
261
- save_to: "report.pdf"
262
- )
263
- ```
264
-
265
- Do not combine `auth` with an `Authorization` header — the API rejects the conflict.
266
-
267
- ### Job status (async polling)
268
-
269
- ```ruby
270
- status = client.get_job_status("job_abc123")
271
- puts status.presigned_url if status.status == "success"
272
- ```
273
-
274
- ---
275
-
276
- ## Error Handling
277
-
278
- ```ruby
279
- begin
280
- client.v2.perceive("https://example.com")
281
- rescue Enconvert::AuthenticationError
282
- warn "Invalid API key"
283
- rescue Enconvert::QuotaError
284
- warn "Plan feature off or quota exhausted"
285
- rescue Enconvert::RateLimitError
286
- warn "Too many requests — slow down"
287
- rescue Enconvert::APIError => e
288
- warn "API error [#{e.status_code}]: #{e.message}"
289
- end
290
- ```
291
-
292
- ## Configuration
293
-
294
- ```ruby
295
- client = Enconvert::Client.new(
296
- api_key: "sk_...",
297
- timeout: 300, # seconds, default
298
- base_url: "https://api.enconvert.com" # override, default shown
299
- )
300
- ```
301
-
302
- ## Get an API Key
303
-
304
- Sign up at [enconvert.com](https://enconvert.com). Free tier: 100 ops/month, no credit card.
305
-
306
- ## License
307
-
308
- MIT
1
+ # Enconvert Ruby SDK
2
+
3
+ Honest eyes for your AI agent — the Ruby SDK for [Enconvert](https://enconvert.com). Ruby 3.0+.
4
+
5
+ Read any web page or file into clean Markdown, JSON, or screenshots, and get a `render_quality` score (0.0–1.0) on **every** read — so a blocked, challenge, or empty-SPA page comes back flagged with a low score and warnings, never mistaken for real content. Perceive, discover, look up, distill, ingest, and watch the web; convert 40+ file and document formats through the same key.
6
+
7
+ > Wiring an agent (Claude, Cursor, Windsurf, n8n, …)? The [MCP server](https://enconvert.com/mcp) is the native path — `npx @enconvert/mcp setup`. This SDK is the programmatic REST path for everything else.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ gem install enconvert
13
+ ```
14
+
15
+ Or add to your Gemfile:
16
+
17
+ ```ruby
18
+ gem "enconvert"
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ```ruby
24
+ require "enconvert"
25
+
26
+ client = Enconvert::Client.new(api_key: "sk_...")
27
+
28
+ # Read a page the way your agent should — with a quality score attached.
29
+ op = client.v2.perceive("https://example.com", outputs: %w[markdown structured])
30
+ puts op.outputs["markdown"].url, op.render_quality # e.g. 0.93
31
+ ```
32
+
33
+ ---
34
+
35
+ # V2 — agent-ready data (`client.v2`)
36
+
37
+ The V2 namespace turns web pages into agent-ready data: render, search, extract, ingest, and monitor. All V2 endpoints require a **private API key** and are plan-gated — a disabled feature or exhausted monthly quota raises `Enconvert::QuotaError` (HTTP 402).
38
+
39
+ Every render carries `render_quality` (0.0–1.0). A low score means the page didn't render cleanly (challenge page, cookie wall, empty shell); the content is still returned, flagged, so a bad read never quietly enters your agent's context.
40
+
41
+ ### Perceive — render a URL into artifacts
42
+
43
+ ```ruby
44
+ op = client.v2.perceive(
45
+ "https://example.com",
46
+ outputs: %w[markdown screenshot structured],
47
+ extract: %w[tables metadata]
48
+ )
49
+ puts op.render_quality # honesty score, 0.0–1.0
50
+ puts op.outputs["markdown"].url # 15-min signed URL
51
+ puts op.structured
52
+
53
+ # Re-sign artifact URLs later:
54
+ again = client.v2.get_perceive_operation(op.operation_id)
55
+
56
+ # Batch (<=1000 URLs; small batches run inline, larger return "queued" — poll):
57
+ batch = client.v2.perceive_batch(
58
+ ["https://a.com", "https://b.com"],
59
+ outputs: ["markdown"],
60
+ output_mode: "zip"
61
+ )
62
+ done = client.v2.get_perceive_batch(batch.job_id)
63
+
64
+ # Direct download: the response body IS the artifact bytes (no signed URL).
65
+ # Requires exactly one artifact-producing output; metadata rides in headers.
66
+ direct = client.v2.perceive_direct("https://example.com", outputs: ["markdown"])
67
+ puts direct.filename, direct.content_type, direct.content.bytesize
68
+
69
+ # Re-download a stored artifact from an earlier operation:
70
+ raw = client.v2.download_perceive_artifact(op.operation_id, output: "markdown")
71
+ ```
72
+
73
+ ### Discover — enumerate a site's URLs (no rendering)
74
+
75
+ ```ruby
76
+ found = client.v2.discover(
77
+ "https://example.com",
78
+ mode: "hybrid", # "sitemap" | "crawl" | "hybrid"
79
+ max_urls: 200,
80
+ exclude_patterns: ["/tag/"]
81
+ )
82
+ puts found.total, found.urls
83
+ ```
84
+
85
+ ### Lookup — web search with optional auto-perceive
86
+
87
+ ```ruby
88
+ search = client.v2.lookup(
89
+ "best static site generators",
90
+ category: "web", # web | news | images | scholar | patents | maps
91
+ num_results: 10,
92
+ perceive_top: 3 # auto-render top 3 results (uses perceive quota)
93
+ )
94
+ search.results.each { |hit| puts hit.title, hit.url, hit.perceive&.render_quality }
95
+ ```
96
+
97
+ ### Distill — schema-driven structured extraction
98
+
99
+ ```ruby
100
+ extraction = client.v2.distill(
101
+ urls: ["https://example.com/pricing"],
102
+ schema: { plans: "list of plan names with monthly prices" },
103
+ css_schema: { # optional free CSS pass before the LLM tier
104
+ base_selector: ".plan-card",
105
+ fields: [
106
+ { name: "name", type: "text", selector: "h3" },
107
+ { name: "price", type: "text", selector: ".price" }
108
+ ]
109
+ }
110
+ )
111
+ puts extraction.results.first.data, extraction.results.first.extraction_tier
112
+
113
+ # Or discover-then-distill:
114
+ client.v2.distill(
115
+ discover_from: { url: "https://example.com", mode: "sitemap", max_pages: 10 },
116
+ schema: { title: "page title", summary: "one-line summary" }
117
+ )
118
+ ```
119
+
120
+ ### Ingest — site or files to RAG-ready JSONL (always async)
121
+
122
+ Turn a whole site — or a set of uploaded documents — into chunked, RAG-ready JSONL through one pipeline.
123
+
124
+ ```ruby
125
+ # From a site:
126
+ job = client.v2.ingest(
127
+ mode: "sitemap",
128
+ url: "https://docs.example.com",
129
+ max_pages: 100,
130
+ chunk: { max_words: 512, sentence_overlap: 1 },
131
+ webhook_url: "https://my.app/hooks/enconvert"
132
+ )
133
+
134
+ # Or from uploaded files (PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, legacy/ODF office):
135
+ file_job = client.v2.ingest_files(
136
+ ["handbook.pdf", "notes.docx"],
137
+ chunk: { max_words: 512, sentence_overlap: 1 }
138
+ )
139
+
140
+ status = client.v2.get_ingest_job(job.job_id) # poll
141
+ puts status.output_url if status.status == "completed" # JSONL
142
+
143
+ client.v2.list_ingest_jobs(limit: 20)
144
+ client.v2.cancel_ingest_job(job.job_id) # idempotent
145
+
146
+ # Webhook signing (HMAC):
147
+ secret = client.v2.get_webhook_secret
148
+ puts secret.secret, secret.signature_header
149
+ client.v2.rotate_webhook_secret # invalidates old secret
150
+ client.v2.retry_ingest_webhook(job.job_id) # re-deliver
151
+ ```
152
+
153
+ ### Watch — recurring change monitoring
154
+
155
+ ```ruby
156
+ watcher = client.v2.create_watcher(
157
+ "https://example.com/pricing",
158
+ frequency_minutes: 60, # hourly floor
159
+ diff_mode: "auto", # auto | text | structured | tables | metadata
160
+ webhook_url: "https://my.app/hooks/changes",
161
+ notify_email: true
162
+ )
163
+
164
+ client.v2.list_watchers
165
+ client.v2.get_watcher(watcher.watcher_id)
166
+ client.v2.get_watcher_snapshots(watcher.watcher_id, limit: 10)
167
+ client.v2.update_watcher(watcher.watcher_id, status: "paused")
168
+ client.v2.update_watcher(watcher.watcher_id, webhook_url: "") # clears webhook
169
+ client.v2.delete_watcher(watcher.watcher_id) # soft-delete, idempotent
170
+ ```
171
+
172
+ ### V2 error handling
173
+
174
+ ```ruby
175
+ begin
176
+ client.v2.ingest(mode: "sitemap", url: "https://example.com")
177
+ rescue Enconvert::QuotaError
178
+ warn "Upgrade plan or wait for quota reset"
179
+ end
180
+ ```
181
+
182
+ ---
183
+
184
+ # File conversion
185
+
186
+ The same key also converts 40+ formats. Two "anything → X" endpoints auto-detect the input; the format-specific endpoints below give you a validated, typed path.
187
+
188
+ ### Anything to Markdown / PDF
189
+
190
+ ```ruby
191
+ # Any document clean Markdown (a RAG-ingestion building block):
192
+ client.convert_to_markdown("report.docx", save_to: "report.md")
193
+ # PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, and legacy/ODF office. (Images not supported.)
194
+
195
+ # Almost anything → PDF:
196
+ client.convert_to_pdf("slides.pptx", save_to: "slides.pdf")
197
+ # office/ODF/Pages/Numbers/RTF/CSV, HTML, Markdown, text, images, SVG, EPUB, or a PDF passthrough.
198
+ # Only pdf_options[:grayscale] is honored on this endpoint:
199
+ client.convert_to_pdf("scan.pdf", pdf_options: { grayscale: true }, save_to: "gray.pdf")
200
+ ```
201
+
202
+ ### Image conversion
203
+
204
+ ```ruby
205
+ result = client.convert_image("photo.heic", output_format: "webp", save_to: "photo.webp")
206
+ ```
207
+
208
+ Any pair among `jpeg`, `png`, `svg`, `heic`, `webp` — plus PDF rasterization (`pdf` → `jpeg`). Unsupported pairs raise before any request is made:
209
+
210
+ ```ruby
211
+ Enconvert.valid_outputs_for("json") # => ["csv", "toml", "xml", "yaml"]
212
+ Enconvert.valid_outputs_for("pdf") # => ["jpeg"]
213
+ ```
214
+
215
+ ### Document & data conversion
216
+
217
+ ```ruby
218
+ client.convert_document("report.docx", save_to: "report.pdf")
219
+ client.convert_document("data.json", output_format: "yaml", save_to: "data.yaml")
220
+ client.convert_document("notes.md", output_format: "html", save_to: "notes.html")
221
+ ```
222
+
223
+ Supported inputs: `doc`/`docx`, `xls`/`xlsx`, `ppt`/`pptx`, `odt`, `ods`, `odp`, `ots`, `pages`, `numbers`, `html`, `markdown`, `csv`, `json`, `xml`, `yaml`, `toml`. (EPUB → use `convert_to_pdf` / `convert_to_markdown`.)
224
+
225
+ The SDK validates every `{input}-to-{output}` pair against the conversions the API actually implements and raises immediately — with the list of valid outputs for that input — instead of sending a doomed request.
226
+
227
+ ### Supported conversions
228
+
229
+ | Input | Outputs |
230
+ |-------|---------|
231
+ | json | csv, toml, xml, yaml |
232
+ | xml | csv, json |
233
+ | yaml | json |
234
+ | csv | json, xml |
235
+ | toml | json |
236
+ | markdown | html, pdf |
237
+ | html | pdf |
238
+ | doc, excel, ppt, odt, ods, odp, ots, pages, numbers | pdf |
239
+ | jpeg, png, svg, heic, webp | each other (all 20 pairs) |
240
+ | pdf | jpeg |
241
+
242
+ ### URL to PDF / Screenshot / Markdown
243
+
244
+ ```ruby
245
+ client.convert_url_to_pdf("https://example.com", save_to: "page.pdf")
246
+ client.convert_url_to_screenshot("https://example.com", viewport_width: 1440, save_to: "shot.png")
247
+ client.convert_url_to_markdown("https://example.com/article", save_to: "article.md")
248
+ ```
249
+
250
+ ### Website to PDF / Screenshot (whole-site batch)
251
+
252
+ Discover every page of a website (sitemap, or full crawl on higher plans), convert each in the background, and receive a single ZIP. Requires a private API key with crawl access.
253
+
254
+ ```ruby
255
+ batch = client.convert_website_to_pdf("https://example.com", crawl_mode: "sitemap")
256
+ status = client.wait_for_batch(batch.batch_id, save_to: "site.zip")
257
+ puts "#{status.completed} of #{status.total} pages converted"
258
+ ```
259
+
260
+ `convert_website_to_screenshot` works the same way and produces a ZIP of PNGs.
261
+
262
+ ### PDF options & authenticated pages
263
+
264
+ ```ruby
265
+ client.convert_url_to_pdf(
266
+ "https://internal.example.com/report",
267
+ pdf_options: { page_size: "A4", orientation: "landscape", margins: { top: 10, bottom: 10 } },
268
+ auth: { username: "user", password: "pass" }, # or cookies / headers, plan-gated
269
+ save_to: "report.pdf"
270
+ )
271
+ ```
272
+
273
+ Do not combine `auth` with an `Authorization` header — the API rejects the conflict.
274
+
275
+ ### Job status (async polling)
276
+
277
+ ```ruby
278
+ status = client.get_job_status("job_abc123")
279
+ puts status.presigned_url if status.status == "success"
280
+ ```
281
+
282
+ ---
283
+
284
+ ## Error Handling
285
+
286
+ ```ruby
287
+ begin
288
+ client.v2.perceive("https://example.com")
289
+ rescue Enconvert::AuthenticationError
290
+ warn "Invalid API key"
291
+ rescue Enconvert::QuotaError
292
+ warn "Plan feature off or quota exhausted"
293
+ rescue Enconvert::RateLimitError
294
+ warn "Too many requests — slow down"
295
+ rescue Enconvert::APIError => e
296
+ warn "API error [#{e.status_code}]: #{e.message}"
297
+ end
298
+ ```
299
+
300
+ ## Configuration
301
+
302
+ ```ruby
303
+ client = Enconvert::Client.new(
304
+ api_key: "sk_...",
305
+ timeout: 300, # seconds, default
306
+ base_url: "https://api.enconvert.com" # override, default shown
307
+ )
308
+ ```
309
+
310
+ ## Get an API Key
311
+
312
+ Sign up at [enconvert.com](https://enconvert.com). Free tier: 100 ops/month, no credit card.
313
+
314
+ ## License
315
+
316
+ MIT