enconvert / enconvert-php
PHP SDK for Enconvert — read any page or file into agent-ready Markdown, JSON, or screenshots (scored 0.0-1.0), plus file conversion. V2 perception + MCP.
Requires
- php: >=8.1
- guzzlehttp/guzzle: ^7.8
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-09-17 14:46:22 UTC
README
Honest eyes for your AI agent — the PHP SDK for Enconvert. PHP 8.1+.
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
composer require enconvert/enconvert-php
Quick Start
use Enconvert\Client; $client = new Client('sk_...'); // Read a page the way your agent should — with a quality score attached. $op = $client->v2->perceive('https://example.com', [ 'outputs' => ['markdown', 'structured'], ]); echo $op->outputs['markdown']->url, ' ', $op->renderQuality; // 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 throws QuotaException (HTTP 402). $client->v2 is a public property; $client->v2() is an equivalent accessor method.
Every render carries renderQuality (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' => ['markdown', 'screenshot', 'structured'], 'extract' => ['tables', 'metadata'], ]); echo $op->renderQuality; // honesty score, 0.0–1.0 echo $op->outputs['markdown']->url; // 15-min signed URL print_r($op->structured); // Re-sign artifact URLs later: $again = $client->v2->getPerceiveOperation($op->operationId); // Batch (<=1000 URLs; small batches run inline, larger return "queued" — poll): $batch = $client->v2->perceiveBatch(['https://a.com', 'https://b.com'], [ 'outputs' => ['markdown'], 'outputMode' => 'zip', ]); $done = $client->v2->getPerceiveBatch($batch->jobId); // Direct download — stream one artifact's raw bytes instead of the JSON envelope // (exactly one artifact-producing output; metadata arrives via headers): $direct = $client->v2->perceiveDirect('https://example.com', [ 'outputs' => ['pdf'], ]); file_put_contents($direct->filename, $direct->content); // Re-download a stored artifact later (output optional when the operation has only one): $saved = $client->v2->downloadPerceiveArtifact($direct->operationId, 'pdf');
Discover — enumerate a site's URLs (no rendering)
$found = $client->v2->discover('https://example.com', [ 'mode' => 'hybrid', // "sitemap" | "crawl" | "hybrid" 'maxUrls' => 200, 'excludePatterns' => ['/tag/'], ]); echo $found->total; print_r($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 'numResults' => 10, 'perceiveTop' => 3, // auto-render top 3 results (uses perceive quota) ]); foreach ($search->results as $hit) { echo $hit->title, ' ', $hit->url, ' ', $hit->perceive?->renderQuality, "\n"; }
Distill — schema-driven structured extraction
$extraction = $client->v2->distill([ 'urls' => ['https://example.com/pricing'], 'schema' => ['plans' => 'list of plan names with monthly prices'], 'cssSchema' => [ // optional free CSS pass before the LLM tier 'baseSelector' => '.plan-card', 'fields' => [ ['name' => 'name', 'type' => 'text', 'selector' => 'h3'], ['name' => 'price', 'type' => 'text', 'selector' => '.price'], ], ], ]); print_r($extraction->results[0]->data); echo $extraction->results[0]->extractionTier; // Or discover-then-distill: $client->v2->distill([ 'discoverFrom' => ['url' => 'https://example.com', 'mode' => 'sitemap', 'maxPages' => 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', 'maxPages' => 100, 'chunk' => ['maxWords' => 512, 'sentenceOverlap' => 1], 'webhookUrl' => 'https://my.app/hooks/enconvert', ]); // Or from uploaded files (PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, legacy/ODF office): $fileJob = $client->v2->ingestFiles(['handbook.pdf', 'notes.docx'], [ 'chunk' => ['maxWords' => 512, 'sentenceOverlap' => 1], ]); $status = $client->v2->getIngestJob($job->jobId); // poll if ($status->status === 'completed') { echo $status->outputUrl; // JSONL } $client->v2->listIngestJobs(['limit' => 20]); $client->v2->cancelIngestJob($job->jobId); // idempotent // Webhook signing (HMAC): $secretInfo = $client->v2->getWebhookSecret(); $client->v2->rotateWebhookSecret(); // invalidates old secret $client->v2->retryIngestWebhook($job->jobId); // re-deliver
Watch — recurring change monitoring
$watcher = $client->v2->createWatcher('https://example.com/pricing', [ 'frequencyMinutes' => 60, // hourly floor 'diffMode' => 'auto', // auto | text | structured | tables | metadata 'webhookUrl' => 'https://my.app/hooks/changes', 'notifyEmail' => true, ]); $client->v2->listWatchers(); $client->v2->getWatcher($watcher->watcherId); $client->v2->getWatcherSnapshots($watcher->watcherId, ['limit' => 10]); $client->v2->updateWatcher($watcher->watcherId, ['status' => 'paused']); $client->v2->updateWatcher($watcher->watcherId, ['webhookUrl' => '']); // clears webhook $client->v2->deleteWatcher($watcher->watcherId); // soft-delete, idempotent
V2 error handling
use Enconvert\Exception\QuotaException; try { $client->v2->ingest(['urls' => ['https://example.com']]); } catch (QuotaException $e) { echo 'Upgrade plan or wait for quota reset'; }
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->convertToMarkdown('report.docx', ['saveTo' => 'report.md']); // PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT/MD, and legacy/ODF office. (Images not supported.) // Almost anything → PDF: $client->convertToPdf('slides.pptx', ['saveTo' => 'slides.pdf']); // office/ODF/Pages/Numbers/RTF/CSV, HTML, Markdown, text, images, SVG, EPUB, or a PDF passthrough. // Only pdfOptions.grayscale is honored on this endpoint: $client->convertToPdf('scan.pdf', ['pdfOptions' => ['grayscale' => true], 'saveTo' => 'gray.pdf']);
Image Conversion
$result = $client->convertImage('photo.heic', [ 'outputFormat' => 'webp', 'saveTo' => 'photo.webp', ]);
Any pair among jpeg, png, svg, heic, webp — plus PDF rasterization:
$client->convertImage('scan.pdf', ['outputFormat' => 'jpeg', 'saveTo' => 'scan.jpeg']);
Raw bytes are also accepted, wrapped with an explicit filename (needed to resolve the input format and MIME type):
$client->convertImage( ['data' => $rawBytes, 'filename' => 'photo.heic'], ['outputFormat' => 'webp', 'saveTo' => 'photo.webp'] );
Document Conversion
$client->convertDocument('report.docx', ['saveTo' => 'report.pdf']); $client->convertDocument('data.json', ['outputFormat' => 'yaml', 'saveTo' => 'data.yaml']); $client->convertDocument('notes.md', ['outputFormat' => 'html', 'saveTo' => 'notes.html']);
Supported inputs: doc/docx, xls/xlsx, ppt/pptx, odt, ods, odp, ots, pages, numbers, html, markdown, csv, json, xml, yaml, toml. (EPUB has no dedicated document pair — use convertToPdf / convertToMarkdown.)
The SDK validates every {input}-to-{output} pair against the conversions the API actually implements and throws immediately — with the list of valid outputs for that input — instead of sending a doomed request. Introspect programmatically:
use Enconvert\Formats; Formats::validOutputsFor('json'); // ["csv", "toml", "xml", "yaml"] Formats::validOutputsFor('pdf'); // ["jpeg"] Formats::IMPLEMENTED_CONVERSIONS; // the full set of 43 "{input}-to-{output}" pairs
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->convertUrlToPdf('https://example.com', ['saveTo' => 'page.pdf']); $client->convertUrlToScreenshot('https://example.com', ['viewportWidth' => 1440, 'saveTo' => 'shot.png']); $client->convertUrlToMarkdown('https://example.com/article', ['saveTo' => 'article.md']);
Website to PDF / Screenshot (whole-site batch)
Discover every page of a website (via sitemap, or full crawl on Pro/Business plans), convert each one in the background, and receive a single ZIP. Requires a private API key with crawl access.
$batch = $client->convertWebsiteToPdf('https://example.com', [ 'crawlMode' => 'sitemap', // "auto" (default) | "sitemap" | "full" 'excludePatterns' => ['/blog/tag/'], // full crawl mode only ]); echo $batch->batchId, ' ', $batch->urlCount, ' ', $batch->discoveryMethod; // Block until done and save the ZIP: $status = $client->waitForBatch($batch->batchId, ['saveTo' => 'site.zip']); echo $status->completed, ' of ', $status->total, ' pages converted'; // Or poll yourself: $s = $client->getBatchStatus($batch->batchId); if ($s->status !== 'processing') { echo $s->zipDownloadUrl; }
convertWebsiteToScreenshot works the same way and produces a ZIP of PNGs.
PDF Options & Authenticated Pages
$result = $client->convertUrlToPdf('https://example.com', [ 'pdfOptions' => [ 'pageSize' => 'A4', // or custom dimensions via pageWidth + pageHeight 'orientation' => 'landscape', 'margins' => ['top' => 10, 'bottom' => 10, 'left' => 15, 'right' => 15], 'header' => ['content' => 'Quarterly Report', 'height' => 15], 'footer' => ['content' => 'Confidential', 'height' => 12], ], 'saveTo' => 'report.pdf', ]);
All URL and website conversions accept HTTP Basic Auth, cookies, and custom headers for pages behind a login:
$client->convertUrlToPdf('https://internal.example.com/report', [ 'auth' => ['username' => 'user', 'password' => 'pass'], // or cookies / headers: 'cookies' => [['name' => 'session', 'value' => 'abc123', 'domain' => 'internal.example.com']], 'headers' => ['X-Tenant' => 'acme'], 'saveTo' => 'report.pdf', ]);
Do not combine auth with an Authorization header — the API rejects the conflict.
Job Status (async polling)
$status = $client->getJobStatus('job_abc123'); if ($status->status === 'success') { echo $status->presignedUrl; }
Error Handling
use Enconvert\Client; use Enconvert\Exception\ApiException; use Enconvert\Exception\AuthenticationException; use Enconvert\Exception\RateLimitException; try { $client->convertUrlToPdf('https://example.com'); } catch (AuthenticationException $e) { echo 'Invalid API key'; } catch (RateLimitException $e) { echo 'Too many requests — slow down'; } catch (ApiException $e) { echo "API error [{$e->getStatusCode()}]: {$e->getMessage()}"; }
AuthenticationException, QuotaException, and RateLimitException all extend ApiException, which extends the base EnconvertException.
Configuration
$client = new Client('sk_...', [ 'timeout' => 300, // seconds, default (idiomatic PHP/Guzzle unit; node-sdk uses milliseconds) 'base_url' => 'https://api.enconvert.com', // override, e.g. for a self-hosted gateway ]);
Get an API Key
Sign up at enconvert.com to get your API key.
License
MIT