Enconvert Ruby SDK
Honest eyes for your AI agent — the Ruby SDK for Enconvert. Ruby 3.0+.
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.
Wiring an agent (Claude, Cursor, Windsurf, n8n, …)? The MCP server is the native path —
npx @enconvert/mcp setup. This SDK is the programmatic REST path for everything else.
Install
gem install enconvert
Or add to your Gemfile:
gem "enconvert"
Quick Start
require "enconvert"
client = Enconvert::Client.new(api_key: "sk_...")
# Read a page the way your agent should — with a quality score attached.
op = client.v2.perceive("https://example.com", outputs: %w[markdown structured])
puts op.outputs["markdown"].url, op.render_quality # e.g. 0.93
V2 — agent-ready data (client.v2)
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).
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.
Perceive — render a URL into artifacts
op = client.v2.perceive(
"https://example.com",
outputs: %w[markdown screenshot structured],
extract: %w[tables metadata]
)
puts op.render_quality # honesty score, 0.0–1.0
puts op.outputs["markdown"].url # 15-min signed URL
puts op.structured
# Re-sign artifact URLs later:
again = client.v2.get_perceive_operation(op.operation_id)
# Batch (<=1000 URLs; small batches run inline, larger return "queued" — poll):
batch = client.v2.perceive_batch(
["https://a.com", "https://b.com"],
outputs: ["markdown"],
output_mode: "zip"
)
done = client.v2.get_perceive_batch(batch.job_id)
Discover — enumerate a site's URLs (no rendering)
found = client.v2.discover(
"https://example.com",
mode: "hybrid", # "sitemap" | "crawl" | "hybrid"
max_urls: 200,
exclude_patterns: ["/tag/"]
)
puts found.total, found.urls
Lookup — web search with optional auto-perceive
search = client.v2.lookup(
"best static site generators",
category: "web", # web | news | images | scholar | patents | maps
num_results: 10,
perceive_top: 3 # auto-render top 3 results (uses perceive quota)
)
search.results.each { |hit| puts hit.title, hit.url, hit.perceive&.render_quality }
Distill — schema-driven structured extraction
extraction = client.v2.distill(
urls: ["https://example.com/pricing"],
schema: { plans: "list of plan names with monthly prices" },
css_schema: { # optional free CSS pass before the LLM tier
base_selector: ".plan-card",
fields: [
{ name: "name", type: "text", selector: "h3" },
{ name: "price", type: "text", selector: ".price" }
]
}
)
puts extraction.results.first.data, extraction.results.first.extraction_tier
# Or discover-then-distill:
client.v2.distill(
discover_from: { url: "https://example.com", mode: "sitemap", max_pages: 10 },
schema: { title: "page title", summary: "one-line summary" }
)
Ingest — site or files to RAG-ready JSONL (always async)
Turn a whole site — or a set of uploaded documents — into chunked, RAG-ready JSONL through one pipeline.
# From a site:
job = client.v2.ingest(
mode: "sitemap",
url: "https://docs.example.com",
max_pages: 100,
chunk: { max_words: 512, sentence_overlap: 1 },
webhook_url: "https://my.app/hooks/enconvert"
)
# Or from uploaded files (PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, legacy/ODF office):
file_job = client.v2.ingest_files(
["handbook.pdf", "notes.docx"],
chunk: { max_words: 512, sentence_overlap: 1 }
)
status = client.v2.get_ingest_job(job.job_id) # poll
puts status.output_url if status.status == "completed" # JSONL
client.v2.list_ingest_jobs(limit: 20)
client.v2.cancel_ingest_job(job.job_id) # idempotent
# Webhook signing (HMAC):
secret = client.v2.get_webhook_secret
puts secret.secret, secret.signature_header
client.v2.rotate_webhook_secret # invalidates old secret
client.v2.retry_ingest_webhook(job.job_id) # re-deliver
Watch — recurring change monitoring
watcher = client.v2.create_watcher(
"https://example.com/pricing",
frequency_minutes: 60, # hourly floor
diff_mode: "auto", # auto | text | structured | tables | metadata
webhook_url: "https://my.app/hooks/changes",
notify_email: true
)
client.v2.list_watchers
client.v2.get_watcher(watcher.watcher_id)
client.v2.get_watcher_snapshots(watcher.watcher_id, limit: 10)
client.v2.update_watcher(watcher.watcher_id, status: "paused")
client.v2.update_watcher(watcher.watcher_id, webhook_url: "") # clears webhook
client.v2.delete_watcher(watcher.watcher_id) # soft-delete, idempotent
V2 error handling
begin
client.v2.ingest(mode: "sitemap", url: "https://example.com")
rescue Enconvert::QuotaError
warn "Upgrade plan or wait for quota reset"
end
File conversion
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.
Anything to Markdown / PDF
# Any document → clean Markdown (a RAG-ingestion building block):
client.convert_to_markdown("report.docx", save_to: "report.md")
# PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, and legacy/ODF office. (Images not supported.)
# Almost anything → PDF:
client.convert_to_pdf("slides.pptx", save_to: "slides.pdf")
# office/ODF/Pages/Numbers/RTF/CSV, HTML, Markdown, text, images, SVG, EPUB, or a PDF passthrough.
# Only pdf_options[:grayscale] is honored on this endpoint:
client.convert_to_pdf("scan.pdf", pdf_options: { grayscale: true }, save_to: "gray.pdf")
Image conversion
result = client.convert_image("photo.heic", output_format: "webp", save_to: "photo.webp")
Any pair among jpeg, png, svg, heic, webp — plus PDF rasterization (pdf → jpeg). Unsupported pairs raise before any request is made:
Enconvert.valid_outputs_for("json") # => ["csv", "toml", "xml", "yaml"]
Enconvert.valid_outputs_for("pdf") # => ["jpeg"]
Document & data conversion
client.convert_document("report.docx", save_to: "report.pdf")
client.convert_document("data.json", output_format: "yaml", save_to: "data.yaml")
client.convert_document("notes.md", output_format: "html", save_to: "notes.html")
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.)
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.
Supported conversions
| Input | Outputs |
|---|---|
| json | csv, toml, xml, yaml |
| xml | csv, json |
| yaml | json |
| csv | json, xml |
| toml | json |
| markdown | html, pdf |
| html | |
| doc, excel, ppt, odt, ods, odp, ots, pages, numbers | |
| jpeg, png, svg, heic, webp | each other (all 20 pairs) |
| jpeg |
URL to PDF / Screenshot / Markdown
client.convert_url_to_pdf("https://example.com", save_to: "page.pdf")
client.convert_url_to_screenshot("https://example.com", viewport_width: 1440, save_to: "shot.png")
client.convert_url_to_markdown("https://example.com/article", save_to: "article.md")
Website to PDF / Screenshot (whole-site batch)
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.
batch = client.convert_website_to_pdf("https://example.com", crawl_mode: "sitemap")
status = client.wait_for_batch(batch.batch_id, save_to: "site.zip")
puts "#{status.completed} of #{status.total} pages converted"
convert_website_to_screenshot works the same way and produces a ZIP of PNGs.
PDF options & authenticated pages
client.convert_url_to_pdf(
"https://internal.example.com/report",
pdf_options: { page_size: "A4", orientation: "landscape", margins: { top: 10, bottom: 10 } },
auth: { username: "user", password: "pass" }, # or cookies / headers, plan-gated
save_to: "report.pdf"
)
Do not combine auth with an Authorization header — the API rejects the conflict.
Job status (async polling)
status = client.get_job_status("job_abc123")
puts status.presigned_url if status.status == "success"
Error Handling
begin
client.v2.perceive("https://example.com")
rescue Enconvert::AuthenticationError
warn "Invalid API key"
rescue Enconvert::QuotaError
warn "Plan feature off or quota exhausted"
rescue Enconvert::RateLimitError
warn "Too many requests — slow down"
rescue Enconvert::APIError => e
warn "API error [#{e.status_code}]: #{e.}"
end
Configuration
client = Enconvert::Client.new(
api_key: "sk_...",
timeout: 300, # seconds, default
base_url: "https://api.enconvert.com" # override, default shown
)
Get an API Key
Sign up at enconvert.com. Free tier: 100 ops/month, no credit card.
License
MIT