SDK Rust de conversion de fichiers#

enconvert est la crate Rust officielle de l'API EnConvert. C'est un client bloquant bâti sur l'API blocking de reqwest : pas de runtime asynchrone, pas de tokio dans votre arbre de dépendances, et aucun .await dans votre code. Douze méthodes sur Enconvert couvrent la conversion de fichiers : URL vers PDF, captures d'écran, extraction Markdown, paires de formats d'images et de documents, anything-to-PDF et lots à l'échelle d'un site entier. Un second espace de noms, client.v2(), ajoute vingt-trois méthodes d'intelligence web : perceive, discover, lookup, distill, ingest et watch. Chaque struct d'options dérive Default, et chaque échec passe par une seule énumération d'erreur.

crates.io : enconvert (0.1.0) · Source : conversionapi/rust-sdk · Rust : édition 2021 · Licence : MIT

Installation#

cargo add enconvert

Ou ajoutez-la à la main. distill et l'extraction schema de perceive prennent un serde_json::Map<String, Value>, ajoutez donc aussi serde_json si vous comptez les utiliser :

[dependencies]
enconvert = "0.1"
serde_json = "1"

La crate embarque reqwest (blocking, json, multipart), serde, serde_json, thiserror et uuid. Il n'y a aucune feature optionnelle à activer.


Démarrage rapide#

use enconvert::{Enconvert, PerceiveOptions, PerceiveOutputName, UrlToPdfOptions};

fn main() -> Result<(), enconvert::Error> {
    let key = std::env::var("ENCONVERT_API_KEY").expect("ENCONVERT_API_KEY is not set");
    let client = Enconvert::new(key)?;

    // Convertit une URL en PDF et écrit le résultat directement sur le disque.
    let pdf = client.convert_url_to_pdf("https://example.com", UrlToPdfOptions {
        save_to: Some("page.pdf".into()),
        ..Default::default()
    })?;
    println!("{}", pdf.presigned_url);

    // Lit la même page comme votre agent devrait le faire, avec un score de qualité attaché.
    let op = client.v2().perceive("https://example.com", PerceiveOptions {
        outputs: Some(vec![PerceiveOutputName::Markdown, PerceiveOutputName::Structured]),
        ..Default::default()
    })?;
    println!("{:?}", op.render_quality); // p. ex. Some(0.93)
    Ok(())
}

Chaque méthode bloque le thread appelant, ce qui rend le SDK facile à intégrer dans une CLI, un script de build, un thread de travail ou un handler web synchrone. Tous les types publics sont réexportés à la racine de la crate : les extraits ci-dessous n'ont besoin de rien d'autre que use enconvert::{...} et du client ci-dessus.

N'appelez pas le SDK depuis un thread d'un runtime asynchrone. Le client bloquant de reqwest ne peut pas être piloté depuis un thread déjà détenu par un réacteur Tokio (ou équivalent). Depuis du code asynchrone, enveloppez chaque appel dans tokio::task::spawn_blocking.

Ce que le client expose#

Surface Accessible via Ce qu'elle couvre
Conversion de fichiers client.<method> 12 méthodes : URL vers PDF, capture d'écran et Markdown ; paires d'images et de documents ; anything-to-Markdown et anything-to-PDF ; lots à l'échelle d'un site ; statut des jobs et des lots
Intelligence web client.v2().<method> 23 méthodes réparties sur perceive, discover, lookup, distill, ingest et watch
Tables de formats valid_outputs_for, IMPLEMENTED_CONVERSIONS Les 43 endpoints {input}-to-{output} implémentés, vérifiés côté client avant l'envoi d'une requête
Erreurs enconvert::Error Une énumération, neuf variantes, avec is_authentication, is_quota, is_rate_limit, is_server_error et status_code

Rien n'est un builder et rien n'est async. La forme d'appel idiomatique est un littéral de struct suivi de ..Default::default().


Conversion de fichiers#

convert_url_to_pdf#

Effectue le rendu de n'importe quelle URL publique en PDF.

use enconvert::{PdfOptions, PdfOrientation, UrlToPdfOptions};

let result = client.convert_url_to_pdf("https://example.com", UrlToPdfOptions {
    single_page: Some(false),
    pdf_options: Some(PdfOptions {
        page_size: Some("A4".to_string()),
        orientation: Some(PdfOrientation::Landscape),
        ..Default::default()
    }),
    save_to: Some("report.pdf".into()),
    ..Default::default()
})?;
println!("{} ({:?} bytes)", result.filename, result.file_size);
Champ Type Défaut Description
save_to Option<PathBuf> - Chemin local où écrire le PDF. Les répertoires parents sont créés automatiquement.
single_page Option<bool> true true produit une seule page continue. false pagine en utilisant pdf_options.page_size.
pdf_options Option<PdfOptions> - Géométrie de page, échelle, niveaux de gris, en-tête et pied de page. Voir Options PDF.
render.viewport_width / render.viewport_height Option<u32> 1920 / 1080 Fenêtre d'affichage du navigateur, en pixels.
render.load_media / render.enable_scroll Option<bool> true Attend les images et les vidéos, et fait défiler la page de haut en bas pour déclencher le chargement paresseux.
render.output_filename Option<String> auto Remplace le nom de fichier généré.
render.auth / render.cookies / render.headers voir les types - Authentification HTTP Basic, jusqu'à 50 cookies injectés, jusqu'à 20 en-têtes de requête supplémentaires.

Le bloc render est un UrlRenderOptions, partagé par toutes les conversions d'URL et de sites. Ne combinez pas auth avec un en-tête Authorization : l'API rejette le conflit.

convert_url_to_screenshot#

Capture un PNG de n'importe quelle URL. UrlToScreenshotOptions porte le même bloc render plus save_to, et rien d'autre.

use enconvert::{UrlRenderOptions, UrlToScreenshotOptions};

client.convert_url_to_screenshot("https://example.com", UrlToScreenshotOptions {
    render: UrlRenderOptions { viewport_width: Some(1440), ..Default::default() },
    save_to: Some("shot.png".into()),
})?;

convert_url_to_markdown#

Extrait un Markdown GitHub-Flavored propre à partir d'une URL. La navigation, les pieds de page, les publicités et les scripts sont supprimés, le corps principal de l'article est conservé, et un frontmatter YAML (titre, description, url, liens, images) est ajouté en tête.

use enconvert::UrlToMarkdownOptions;

client.convert_url_to_markdown("https://example.com/article", UrlToMarkdownOptions {
    save_to: Some("article.md".into()),
    ..Default::default()
})?;

Utile pour les pipelines RAG, les imports CMS et la collecte de données d'entraînement. Pour une lecture scorée avec artefacts et extraction structurée en un seul appel, utilisez plutôt perceive.

convert_image#

Convertit entre jpeg, png, svg, heic et webp (les 20 paires ordonnées), ou rastérise un PDF en JPEG.

use enconvert::{ConvertImageOptions, NamedFile};

// Depuis un chemin : le format d'entrée provient de l'extension.
client.convert_image("photo.heic", ConvertImageOptions {
    output_format: "webp".to_string(),
    save_to: Some("photo.webp".into()),
    ..Default::default()
})?;

// Depuis des octets : fournissez un nom de fichier pour que l'extension reste lisible.
let data = std::fs::read("photo.heic")?;
client.convert_image(
    NamedFile { data, filename: "photo.heic".to_string(), content_type: None },
    ConvertImageOptions { output_format: "png".to_string(), ..Default::default() },
)?;

output_format est le seul champ obligatoire ; save_to et output_filename sont facultatifs. Le premier argument accepte tout ce qui se convertit en FileInput : un chemin &str ou String, un &Path ou PathBuf, un Vec<u8> ou &[u8] d'octets bruts, ou un NamedFile lorsque vous disposez d'octets et souhaitez déclarer vous-même le nom de fichier et le type de contenu.

convert_document#

Convertit des documents et des formats de données. output_format vaut "pdf" par défaut, et yml, htm, md et jpg sont normalisés vers leurs noms canoniques. save_to, output_filename et pdf_options sont facultatifs.

use enconvert::{ConvertDocumentOptions, PdfOptions};

// docx vers pdf (le format de sortie par défaut)
client.convert_document("report.docx", ConvertDocumentOptions {
    save_to: Some("report.pdf".into()),
    ..Default::default()
})?;

// json vers yaml
client.convert_document("data.json", ConvertDocumentOptions {
    output_format: Some("yaml".to_string()),
    save_to: Some("data.yaml".into()),
    ..Default::default()
})?;

// markdown vers pdf avec une mise en page personnalisée
client.convert_document("README.md", ConvertDocumentOptions {
    pdf_options: Some(PdfOptions { page_size: Some("A4".to_string()), ..Default::default() }),
    ..Default::default()
})?;

Extensions d'entrée reconnues : .doc, .docx, .xls, .xlsx, .ppt, .pptx, .html, .htm, .odt, .ods, .odp, .ots, .pages, .numbers, .md, .markdown, .csv, .json, .xml, .yaml, .yml, .toml.

Format d'entrée Sorties valides
json csv, toml, xml, yaml
xml csv, json
csv json, xml
yaml, toml json
markdown html, pdf
html pdf
doc, excel, ppt, odt, ods, odp, ots, pages, numbers pdf
jpeg, png, svg, heic, webp les uns vers les autres, les 20 paires
pdf jpeg

EPUB n'a pas de paire documentaire dédiée. Faites plutôt passer les fichiers .epub par convert_to_pdf ou convert_to_markdown.

convert_image et convert_document résolvent tous deux le format d'entrée à partir de l'extension du fichier et vérifient la paire dans la table côté client : une paire non implémentée renvoie donc Error::UnsupportedConversion avec la liste des sorties valides, avant qu'aucune requête HTTP ne soit émise. Vous pouvez interroger cette même table directement :

use enconvert::{valid_outputs_for, IMPLEMENTED_CONVERSIONS};

println!("{:?}", valid_outputs_for("json"));   // ["csv", "toml", "xml", "yaml"]
println!("{}", IMPLEMENTED_CONVERSIONS.len()); // 43

convert_to_markdown#

Envoyez un document de presque n'importe quel type et récupérez du Markdown propre : PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT et MD, ainsi que les formats bureautiques hérités ou ODF. Le format est détecté côté serveur, aucune vérification d'extension n'a donc lieu côté client. Les images ne sont pas prises en charge.

use enconvert::ConvertToMarkdownOptions;

client.convert_to_markdown("handbook.pdf", ConvertToMarkdownOptions {
    save_to: Some("handbook.md".into()),
    ..Default::default()
})?;

La sortie est un unique fichier .md qui respecte la hiérarchie des titres, ce qui en fait une bonne brique de base pour le découpage RAG : un découpeur sémantique peut segmenter sur la hiérarchie de titres propre au document plutôt que sur un nombre de caractères arbitraire. Il n'y a pas d'options PDF ici, seulement save_to et output_filename.

convert_to_pdf#

Envoyez à peu près n'importe quoi et récupérez un PDF : bureautique, ODF, Pages, Numbers, RTF, CSV, HTML, Markdown, texte brut, images matricielles, SVG, EPUB, ou un PDF existant en passthrough.

use enconvert::{ConvertToPdfOptions, PdfOptions};

client.convert_to_pdf("slides.pptx", ConvertToPdfOptions {
    save_to: Some("slides.pdf".into()),
    ..Default::default()
})?;

// PDF en entrée, PDF en niveaux de gris en sortie.
client.convert_to_pdf("scan.pdf", ConvertToPdfOptions {
    pdf_options: Some(PdfOptions { grayscale: Some(true), ..Default::default() }),
    save_to: Some("scan-gray.pdf".into()),
    ..Default::default()
})?;
Seul pdf_options.grayscale est pris en compte ici. L'endpoint anything-to-PDF détecte l'entrée automatiquement et applique sa propre géométrie. Pour contrôler la taille de page, l'orientation, les marges, l'échelle, les en-têtes et les pieds de page, utilisez convert_url_to_pdf ou convert_document avec une entrée HTML ou Markdown.

convert_website_to_pdf et convert_website_to_screenshot#

Découvrez toutes les pages d'un site, convertissez-les en arrière-plan et récupérez une archive ZIP unique. Les deux méthodes sont exclusivement asynchrones : elles renvoient un BatchSubmission, jamais un fichier terminé.

use enconvert::{CrawlMode, WaitForBatchOptions, WebsiteConversionOptions, WebsiteToPdfOptions};

let batch = client.convert_website_to_pdf("https://example.com", WebsiteToPdfOptions {
    website: WebsiteConversionOptions {
        crawl_mode: Some(CrawlMode::Sitemap),
        exclude_patterns: Some(vec!["/blog/tag/".to_string()]),
        notification_email: Some("[email protected]".to_string()),
        ..Default::default()
    },
    ..Default::default()
})?;

// Bloque jusqu'à ce que le lot se stabilise, puis enregistre le ZIP.
let status = client.wait_for_batch(&batch.batch_id, WaitForBatchOptions {
    save_to: Some("site.zip".into()),
    ..Default::default()
})?;
println!("{} of {} pages converted", status.completed, status.total);
Champ Type Défaut Description
website.crawl_mode Option<CrawlMode> Auto Auto, Sitemap (sitemap.xml uniquement) ou Full (sitemap plus une exploration en largeur).
website.include_patterns / website.exclude_patterns Option<Vec<String>> - N'explore que les URL correspondant à ces motifs, ou les ignore. Mode d'exploration complète.
website.notification_email / website.callback_url Option<String> propriétaire du projet / - Adresse notifiée par e-mail, et webhook appelé, à la fin du lot.
website.render UrlRenderOptions - Mêmes champs de fenêtre d'affichage, médias, défilement, authentification, cookies et en-têtes que les conversions d'URL unique.
single_page / pdf_options voir ci-dessus - Lots PDF uniquement.

convert_website_to_screenshot prend un WebsiteToScreenshotOptions, un alias de type pour WebsiteConversionOptions, et produit un ZIP de PNG. wait_for_batch interroge get_batch_status toutes les interval_ms (5000 par défaut) jusqu'à ce que le lot quitte l'état Processing, puis télécharge éventuellement le ZIP. Si timeout_ms (1 800 000 par défaut, soit 30 minutes) s'écoule avant, la méthode renvoie Error::Api { status: 504, .. }.

get_job_status et get_batch_status#

use enconvert::JobStatusValue;

let status = client.get_job_status("job_abc123")?;
match status.status {
    JobStatusValue::Success => println!("{:?}", status.presigned_url),
    JobStatusValue::Failed => eprintln!("{:?}", status.error),
    other => println!("still running: {other:?}"),
}

let batch = client.get_batch_status("batch_abc123")?; // compteurs agrégés et éléments par URL
println!("{}/{} done, {} failed", batch.completed, batch.total, batch.failed);
Vous avez rarement besoin de get_job_status directement. Le SDK l'interroge pour vous lorsqu'une conversion synchrone renvoie une 5xx. Voir Récupération après timeout.

Intelligence web (V2)#

Tout ce qui se trouve sous client.v2() transforme des pages web en données prêtes pour un agent. Le point commun de chaque lecture est render_quality, un score de 0.0 à 1.0 attaché à chaque page rendue. Un score faible signifie que la page n'a pas été rendue proprement : page anti-bot, mur de cookies ou de connexion, coquille SPA vide, ou erreur HTTP. Le contenu revient quand même, mais il revient signalé, avec une table deductions nommée et une liste warnings : une mauvaise lecture n'entre donc jamais discrètement dans le contexte de votre agent. Les résultats de perceive, distill, lookup et watch le portent tous. Commencez par la vue d'ensemble V2 pour les concepts derrière les six capacités.

Perceive#

Effectue le rendu d'une URL dans les artefacts que vous demandez. Synchrone : l'appel renvoie une opération terminée avec des URL d'artefacts signées pour 15 minutes. Voir perceive.

use enconvert::{PerceiveExtractName, PerceiveOptions, PerceiveOutputName};

let op = client.v2().perceive("https://example.com", PerceiveOptions {
    outputs: Some(vec![PerceiveOutputName::Markdown, PerceiveOutputName::Screenshot]),
    extract: Some(vec![PerceiveExtractName::Tables, PerceiveExtractName::Metadata]),
    ..Default::default()
})?;

println!("{:?}", op.render_quality);
println!("{:?}", op.deductions); // p. ex. {"http_error": 0.7} quand quelque chose cloche
println!("{:?}", op.outputs.get("markdown").and_then(|a| a.url.as_ref()));
println!("{:?}", op.structured);

// Les URL d'artefacts sont resignées à chaque lecture du statut.
client.v2().get_perceive_operation(&op.operation_id)?;
Option Type Défaut Description
outputs Option<Vec<PerceiveOutputName>> markdown, structured Markdown, HtmlCleaned, HtmlRaw, Screenshot, ScreenshotFullPage, Pdf, Links, Images, Structured.
extract Option<Vec<PerceiveExtractName>> - Tables, Prices, Contacts, Metadata, MainContent, Headings, StructuredData, Technologies, All.
schema Option<Map<String, Value>> - Schéma JSON pour l'extraction structurée.
only_main_content Option<bool> true Supprime la navigation, l'en-tête, le pied de page et les bandeaux de cookies de l'artefact Markdown et de l'extrait main_content.
wait_for / wait_timeout_ms / js_code voir les types - / 30000 / - Un sélecteur CSS (éventuellement css:...) ou js:<expr> à attendre, la durée d'attente (0 à 60000), et le JavaScript à exécuter après la navigation (20000 caractères maximum).
viewport / mobile Option<PerceiveViewport> / Option<bool> 1920x1080 Largeur de 320 à 3840, hauteur de 240 à 2160, ou un profil d'appareil mobile.
cache_mode / block_resources voir les types Enabled / - Enabled met en cache pendant 1 heure, Bypass l'ignore, Refresh refait le rendu ; plus les types de ressources que le navigateur ne doit pas charger.
headers, cookies, auth, respect_robots, pdf_options voir UrlRenderOptions et PdfOptions - Mêmes formes que les options de rendu V1. pdf_options ne compte que lorsque outputs inclut Pdf.

perceive_direct renvoie un artefact unique en octets bruts au lieu d'une enveloppe JSON. Il faut demander exactement une sortie produisant un artefact, et le SDK le vérifie localement avec Error::InvalidInput avant tout envoi.

use enconvert::{PerceiveOptions, PerceiveOutputName};

let direct = client.v2().perceive_direct("https://example.com", PerceiveOptions {
    outputs: Some(vec![PerceiveOutputName::Pdf]),
    ..Default::default()
})?;
std::fs::write(direct.filename.as_deref().unwrap_or("page.pdf"), &direct.content)?;

// Retélécharge un artefact stocké plus tard. Passez None si l'opération n'en a produit qu'un.
let saved = client
    .v2()
    .download_perceive_artifact(&direct.operation_id, Some(PerceiveOutputName::Pdf))?;
println!("{} bytes", saved.content.len());

PerceiveDirectResult porte content, content_type, filename, operation_id, object_key, cache_hit, render_quality, source_status_code, content_hash et warnings_count, tous lus depuis les en-têtes de réponse. Un Error::Api { status: 410, .. } renvoyé par download_perceive_artifact signifie que l'artefact a dépassé sa fenêtre de rétention.

perceive_batch accepte jusqu'à 1000 URL et un bloc d'options partagé. Les petits lots s'exécutent en ligne ; les plus gros reviennent avec le statut Queued, interrogez donc get_perceive_batch avec le job_id.

use enconvert::{PerceiveBatchOptions, PerceiveBatchOutputMode, PerceiveOptions, PerceiveOutputName};

let render = PerceiveOptions {
    outputs: Some(vec![PerceiveOutputName::Markdown]),
    ..Default::default()
};
let batch = client.v2().perceive_batch(
    vec!["https://example.com/a".to_string(), "https://example.com/b".to_string()],
    PerceiveBatchOptions { render, output_mode: Some(PerceiveBatchOutputMode::Zip) },
)?;

let done = client.v2().get_perceive_batch(&batch.job_id)?;
for item in &done.items {
    println!("{} {:?}", item.url, item.render_quality);
}

Discover#

Énumère les URL d'un site sans aucun rendu navigateur. Voir discover.

use enconvert::{DiscoverMode, DiscoverOptions};

let found = client.v2().discover("https://example.com", DiscoverOptions {
    mode: Some(DiscoverMode::Hybrid),
    max_urls: Some(200),
    exclude_patterns: Some(vec!["/tag/".to_string()]),
    ..Default::default()
})?;
println!("{} urls, truncated: {}", found.total, found.truncated);

mode vaut Hybrid par défaut (Sitemap et Crawl sont les alternatives), max_urls vaut 100 (1 à 1000), max_depth vaut 2 (1 à 5), et same_domain_only vaut true. include_patterns puis exclude_patterns appliquent un filtrage par expressions régulières, 50 au maximum chacun, et respect_robots respecte robots.txt. Le résultat porte urls, total, pages_crawled, truncated, robots_respected, ainsi qu'une table sources des comptages bruts par source.

Lookup#

Lance une recherche web catégorisée et, si vous le souhaitez, effectue le rendu des premiers résultats dans le même appel. Voir lookup.

use enconvert::{LookupCategory, LookupOptions};

let search = client.v2().lookup("best static site generators", LookupOptions {
    category: Some(LookupCategory::Web),
    num_results: Some(10),
    country: Some("us".to_string()),
    perceive_top: Some(3),
    ..Default::default()
})?;

for hit in &search.results {
    let quality = hit.perceive.as_ref().and_then(|p| p.render_quality);
    println!("{:?} {:?} {quality:?}", hit.title, hit.url);
}

category vaut Web par défaut (News, Images, Scholar, Patents et Maps sont les autres valeurs), num_results vaut 10 (1 à 100), page vaut 1 (1 à 10), et autocorrect vaut true. country et locale prennent des codes comme "us" et "en", location prend du texte libre comme "Austin, Texas", et time_filter accepte Hour, Day, Week, Month ou Year. perceive_top (0 à 10, 0 par défaut) effectue automatiquement le rendu des N premières URL de résultat et attache un PerceiveResult complet à chaque occurrence. Le résultat porte aussi answer_box, knowledge_graph et perceive_operation_ids.

Distill#

Extraction structurée pilotée par schéma : donnez-lui une forme, récupérez cette forme pour chaque URL. Une passe CSS optionnelle s'exécute d'abord, et tout ce qu'elle manque est escaladé vers le niveau LLM. Voir distill.

use enconvert::{CssField, CssFieldType, CssSchema, DistillOptions};
use serde_json::json;

// CssField n'a pas de Default parce que `field_type` est obligatoire.
fn text_field(name: &str, selector: &str) -> CssField {
    CssField {
        name: name.into(), field_type: CssFieldType::Text, selector: Some(selector.into()),
        attribute: None, pattern: None, default: None, transform: None, fields: None,
    }
}

let distilled = client.v2().distill(DistillOptions {
    urls: Some(vec!["https://example.com/pricing".to_string()]),
    schema: json!({ "plans": "list of plan names with monthly prices" })
        .as_object().unwrap().clone(),
    css_schema: Some(CssSchema {
        base_selector: ".plan-card".to_string(),
        fields: vec![text_field("name", "h3"), text_field("price", ".price")],
        name: None,
        target_field: None,
    }),
    ..Default::default()
})?;

for item in &distilled.results {
    println!("{:?} tier {:?} css {} llm {}",
        item.data, item.extraction_tier, item.fields_from_css, item.fields_from_llm);
}

Ou découvrez d'abord un site et distillez chaque page trouvée :

use enconvert::{DistillDiscoverFrom, DistillOptions};
use serde_json::json;

client.v2().distill(DistillOptions {
    discover_from: Some(DistillDiscoverFrom {
        max_pages: Some(20), // 1 à 50, plafonne à la fois la découverte et la distillation
        ..DistillDiscoverFrom::new("https://example.com")
    }),
    schema: json!({ "title": "page title" }).as_object().unwrap().clone(),
    ..Default::default()
})?;

Fournissez exactement l'un des deux : urls (50 au maximum) ou discover_from. Les deux, ou aucun, renvoie Error::InvalidInput avant l'envoi de toute requête. schema est obligatoire et est soit un objet JSON Schema, soit une table plate {field: description}. CssFieldType couvre Text, Attribute, Html, Regex, Nested, List et NestedList ; transform accepte Lowercase, Uppercase ou Strip ; l'imbrication est plafonnée à une profondeur de 5.

Ingest#

Transforme un site entier, ou une pile de documents envoyés, en JSONL découpé et prêt pour le RAG à travers un seul pipeline. Ingest est toujours asynchrone. Voir ingest.

use enconvert::{
    IngestChunkOptions, IngestFilesOptions, IngestMode, IngestOptions, IngestStatus, V2ListOptions,
};

// Depuis un site.
let job = client.v2().ingest(IngestOptions {
    mode: Some(IngestMode::Sitemap),
    url: Some("https://docs.example.com".to_string()),
    max_pages: Some(100),
    chunk: Some(IngestChunkOptions { max_words: Some(512), sentence_overlap: Some(1) }),
    webhook_url: Some("https://my.app/hooks/enconvert".to_string()),
    ..Default::default()
})?;

// Ou depuis des fichiers envoyés : PDF, DOCX, PPTX, XLSX, CSV, HTML, EPUB, TXT et MD,
// ainsi que les documents bureautiques hérités ou ODF.
let files = vec!["handbook.pdf".into(), "notes.docx".into()];
client.v2().ingest_files(files, IngestFilesOptions::default())?;

// Interroger, lister, annuler.
let status = client.v2().get_ingest_job(&job.job_id)?;
if status.status == IngestStatus::Completed {
    println!("{:?}", status.output_url); // URL signée vers le JSONL
}
client.v2().list_ingest_jobs(V2ListOptions { limit: Some(50), ..Default::default() })?;
client.v2().cancel_ingest_job(&job.job_id)?; // idempotent

mode vaut Urls par défaut, ce qui exige une liste urls non vide (1000 au maximum) et rejette url ; Sitemap et Crawl font l'inverse et exigent l'url de départ. Cet appariement est validé côté client, un décalage renvoie donc immédiatement Error::InvalidInput. max_pages vaut 50 par défaut (1 à 1000), max_depth vaut 2 (1 à 5), same_domain_only vaut true, chunk.max_words vaut 512 (32 à 4000), et chunk.sentence_overlap vaut 1 (0 à 10). include_patterns, exclude_patterns, respect_robots, wait_for et wait_timeout_ms se comportent comme sur discover et perceive. Les webhooks de fin sont signés en HMAC :

let secret = client.v2().get_webhook_secret()?;
println!("{} {} {}", secret.secret, secret.signature_header, secret.signature_scheme);
client.v2().rotate_webhook_secret()?;            // les anciennes signatures cessent aussitôt d'être valides
client.v2().retry_ingest_webhook("ing_abc123")?; // renvoie le webhook d'un job terminé

Watch#

Refait le rendu d'une URL à intervalle fixe et vous signale ce qui a changé. Voir watch.

use enconvert::{
    SnapshotListOptions, V2ListOptions, WatchCreateOptions, WatchDiffMode, WatchUpdateStatus,
    WatcherUpdate,
};

let watcher = client.v2().create_watcher("https://example.com/pricing", WatchCreateOptions {
    frequency_minutes: Some(60),
    diff_mode: Some(WatchDiffMode::Auto),
    webhook_url: Some("https://my.app/hooks/changes".to_string()),
    notify_email: Some(true),
    ..Default::default()
})?;
println!("{} next check {:?}", watcher.watcher_id, watcher.next_check_at);

// Lit l'historique des vérifications, de la plus récente à la plus ancienne.
let history = client
    .v2()
    .get_watcher_snapshots(&watcher.watcher_id, SnapshotListOptions { limit: Some(10) })?;
for snap in &history.snapshots {
    println!("{} changed: {} similarity: {:?}", snap.checked_at, snap.has_changes, snap.similarity);
}
client.v2().list_watchers(V2ListOptions::default())?;
client.v2().get_watcher(&watcher.watcher_id)?;

// Le met en pause. Passer Some(String::new()) pour webhook_url effacerait le webhook.
client.v2().update_watcher(&watcher.watcher_id, WatcherUpdate {
    status: Some(WatchUpdateStatus::Paused),
    ..Default::default()
})?;
client.v2().delete_watcher(&watcher.watcher_id)?; // suppression logique, idempotente

frequency_minutes vaut 60 par défaut et accepte de 60 à 43200 : le plancher horaire est donc strict. diff_mode vaut Auto par défaut et accepte aussi Text, Structured, Tables et Metadata, track_fields restreignant le moteur de diff à un sous-ensemble de champs ou de sélecteurs. notify_email vaut true par défaut et notifie le propriétaire du projet par e-mail en cas de changement ; webhook_url ajoute un webhook de changement signé en HMAC.

update_watcher exige au moins un champ et renvoie Error::InvalidInput pour un WatcherUpdate entièrement à None. Les entrées changes des snapshots contiennent du contenu de page non fiable : échappez-les avant tout affichage.


Options PDF#

PdfOptions est partagé par convert_url_to_pdf, convert_document, convert_to_pdf et la sortie Pdf de perceive. Chaque champ est facultatif et n'est envoyé que lorsqu'il est défini.

use enconvert::{PdfHeaderFooter, PdfMargins, PdfOptions, PdfOrientation, UrlToPdfOptions};

fn block(text: &str, height: f64) -> PdfHeaderFooter {
    PdfHeaderFooter { content: Some(text.into()), height: Some(height) }
}

client.convert_url_to_pdf("https://example.com", UrlToPdfOptions {
    pdf_options: Some(PdfOptions {
        page_size: Some("A4".to_string()),
        orientation: Some(PdfOrientation::Landscape),
        margins: Some(PdfMargins {
            top: Some(10.0), bottom: Some(10.0), left: Some(15.0), right: Some(15.0),
        }),
        scale: Some(0.9),
        header: Some(block("Quarterly Report", 15.0)),
        footer: Some(block("Confidential", 12.0)),
        ..Default::default()
    }),
    ..Default::default()
})?;
Champ Type Description
page_size Option<String> "A4", "A3", "Letter", "Legal", et ainsi de suite.
page_width / page_height Option<f64> Dimensions personnalisées. Définies ensemble, elles remplacent page_size.
orientation Option<PdfOrientation> Portrait ou Landscape.
margins Option<PdfMargins> top, bottom, left, right, chacun facultatif.
scale Option<f64> Échelle de rendu, par exemple 0.9 pour 90 pour cent.
grayscale Option<bool> Post-traite le PDF en niveaux de gris.
header / footer Option<PdfHeaderFooter> content (2000 caractères maximum) et height.

La sémantique complète des paramètres se trouve dans paramètres et options.


Gestion des erreurs#

Il n'y a qu'un seul type d'erreur, enconvert::Error. Il implémente std::error::Error via thiserror, ? se propage donc dans n'importe quel Box<dyn Error> ou anyhow::Result.

use enconvert::{Error, UrlToPdfOptions};

match client.convert_url_to_pdf("https://example.com", UrlToPdfOptions::default()) {
    Ok(result) => println!("{}", result.presigned_url),
    Err(e) if e.is_authentication() => eprintln!("check ENCONVERT_API_KEY"),
    Err(e) if e.is_rate_limit() => eprintln!("too many requests, back off"),
    Err(e) if e.is_server_error() => eprintln!("gateway problem, retry later"),
    Err(Error::UnsupportedConversion(msg)) => eprintln!("{msg}"),
    Err(Error::Api { status, message }) => eprintln!("API error [{status}]: {message}"),
    Err(e) => return Err(e),
}
Variante Renvoyée sur Code de statut
Error::Authentication(String) Clé API invalide, absente ou révoquée 401, 403
Error::Quota(String) Levée sur un HTTP 402 402
Error::RateLimit(String) Limite de débit dépassée 429
Error::Api { status, message } Toute autre réponse 4xx ou 5xx le code réel
Error::UnsupportedConversion(String) Une paire {input}-to-{output} que l'API n'implémente pas, détectée côté client -
Error::InvalidInput(String) Arguments incorrects détectés avant la requête, par exemple une clé API vide -
Error::Http(reqwest::Error) Échec de connexion, de TLS, de timeout ou de décodage de la réponse issu du transport, lorsqu'il est présent
Error::Io(std::io::Error) Lecture d'un fichier à envoyer, ou écriture d'un téléchargement -
Error::Json(serde_json::Error) Sérialisation d'un corps de requête -

Quatre prédicats gardent les bras de match courts : is_authentication(), is_quota(), is_rate_limit() et is_server_error(). status_code() renvoie un Option<u16> pour chaque variante qui en porte un. La table complète des messages se trouve dans la référence des codes d'erreur.


Récupération après timeout#

Les rendus URL vers PDF longs et les conversions de gros documents peuvent dépasser le timeout d'un reverse proxy même lorsque la conversion réussit côté serveur. Le SDK s'en sort tout seul :

  1. Avant chaque requête, il génère un UUID et l'envoie comme job_id dans le corps ou dans le formulaire multipart.
  2. Si cette requête revient en 5xx sous forme d'Error::Api, le SDK bascule silencieusement sur l'interrogation de GET /v1/convert/status/{job_id} toutes les 3 secondes.
  3. Dès que le job passe à success, il renvoie le résultat. Dès qu'il passe à failed, il renvoie une Error::Api portant le message du serveur.
  4. Le délai d'interrogation est de 5 minutes. Au-delà, vous obtenez Error::Api { status: 504, message: "Conversion timed out" }.

La récupération couvre convert_url_to_pdf, convert_url_to_screenshot, convert_url_to_markdown, convert_image, convert_document, convert_to_markdown et convert_to_pdf. Elle ne couvre délibérément pas les soumissions de lots de sites : celles-ci n'ont pas de ligne de job par unité, une 5xx y signifie donc que la soumission elle-même a échoué, et elle remonte directement. Les endpoints V2 répondent directement et n'ont pas non plus de repli par job. Les réponses qui omettent job_id reçoivent celui généré par le client, result.job_id est donc toujours quelque chose que vous pouvez passer plus tard à get_job_status.


Configuration#

use enconvert::Enconvert;
use std::time::Duration;

let client = Enconvert::with_options(
    std::env::var("ENCONVERT_API_KEY").expect("ENCONVERT_API_KEY is not set"),
    Some("https://api.enconvert.com"), // remplacement de l'URL de base
    Some(Duration::from_secs(300)),    // timeout de requête
)?;
println!("enconvert {}", enconvert::VERSION);
Argument Type Défaut Description
api_key impl Into<String> obligatoire Clé API privée. Une chaîne vide renvoie Error::InvalidInput, tout comme une clé contenant des caractères d'en-tête invalides.
base_url Option<&str> https://api.enconvert.com Les barres obliques finales sont supprimées.
timeout Option<Duration> 300 secondes Appliqué à l'ensemble de la requête.

Enconvert::new(api_key) est un raccourci pour with_options(api_key, None, None).

Ne codez jamais la clé API en dur. Lisez-la depuis une variable d'environnement ou un gestionnaire de secrets. Le SDK marque la valeur de l'en-tête X-API-Key comme sensible pour qu'elle reste hors des sorties de débogage, et il télécharge les URL signées via un second client HTTP non authentifié pour que votre clé ne soit jamais envoyée au stockage objet. Générez et faites tourner vos clés dans le tableau de bord, et consultez l'authentification pour les types de clés.

Forme du résultat#

Chaque méthode de conversion de fichier unique renvoie un ConversionResult :

pub struct ConversionResult {
    pub presigned_url: String,
    pub object_key: String,
    pub filename: String,
    pub file_size: Option<u64>,
    pub conversion_time_seconds: Option<f64>,
    pub job_id: Option<String>,
}

Téléchargez le fichier vous-même depuis presigned_url, ou passez save_to et laissez le SDK l'écrire pour vous. Les URL signées expirent : stockez donc dans votre propre bucket tout ce que vous devez conserver.

Les chemins asynchrones renvoient leurs propres formes. JobStatus porte status (Processing, Success, Failed ou Unknown(String)), presigned_url, object_key et error. BatchSubmission porte batch_id, status, url_count, total_discovered, discovery_method et output_format. BatchStatus ajoute les compteurs total, completed, failed et in_progress, ainsi que output_mode, zip_download_url et un Vec<BatchItem> de lignes par URL.

Les lectures V2 renvoient un PerceiveResult, dont la table outputs est indexée par nom de sortie ("markdown", "screenshot_full_page", et ainsi de suite) avec des valeurs V2OutputArtifact { url, object_key, size_bytes, content_type, expires_in }. Ces URL d'artefacts sont signées pour 15 minutes et resignées à chaque appel de get_perceive_operation. À côté d'elles se trouvent render_quality, status_code, deductions, cache_hit, structured, extraction_tier, tokens, cost_cents, duration_ms, warnings et options_echo. Chaque énumération de réponse porte une variante Unknown(String) : une valeur de statut ajoutée sur le serveur après la publication de votre build se parse donc proprement au lieu d'échouer.


Sources et signalements#


Questions fréquentes#

Comment convertir des fichiers en Rust avec un paquet crates.io ?#

Exécutez cargo add enconvert, construisez un client avec Enconvert::new(api_key)?, puis appelez une méthode comme convert_url_to_pdf, convert_image ou convert_document. Chaque struct d'options dérive Default : vous écrivez donc un littéral de struct avec ..Default::default() et ne renseignez que les champs qui vous intéressent. Passez save_to pour que le SDK écrive la sortie directement sur le disque.

Le SDK Rust a-t-il besoin de tokio ou d'un runtime asynchrone ?#

Non. Il est bâti sur l'API blocking de reqwest : chaque méthode bloque le thread appelant, et il n'y a ni .await, ni exécuteur, ni tokio dans votre arbre de dépendances. Si votre application est déjà asynchrone, appelez le SDK depuis tokio::task::spawn_blocking plutôt que directement sur un thread de réacteur, car le client bloquant de reqwest ne peut pas s'exécuter dans un contexte de runtime asynchrone.

Comment convertir une URL en PDF en Rust ?#

Appelez convert_url_to_pdf("https://example.com", UrlToPdfOptions { save_to: Some("page.pdf".into()), ..Default::default() }). Mettez single_page: Some(false) pour paginer au lieu de produire une seule page continue, et passez pdf_options pour la taille de page, l'orientation, les marges, l'échelle, les niveaux de gris, les en-têtes et les pieds de page.

Comment convertir un DOCX en PDF en Rust ?#

Appelez convert_document("report.docx", ConvertDocumentOptions { save_to: Some("report.pdf".into()), ..Default::default() }). PDF est la sortie par défaut, output_format peut donc rester non défini. La même méthode gère XLSX, PPTX, ODT, ODS, ODP, OTS, Pages, Numbers, HTML et Markdown vers PDF, ainsi que les paires de formats de données comme JSON vers YAML et CSV vers XML.

Comment convertir du HEIC en WebP en Rust ?#

Appelez convert_image("photo.heic", ConvertImageOptions { output_format: "webp".to_string(), ..Default::default() }). Le format d'entrée provient de l'extension du fichier, et les 20 paires ordonnées entre jpeg, png, svg, heic et webp sont implémentées, plus pdf vers jpeg pour la rastérisation. Une paire non prise en charge renvoie Error::UnsupportedConversion avant tout appel réseau.

Comment récupérer une page web en Markdown propre depuis Rust ?#

De deux façons. convert_url_to_markdown renvoie un fichier Markdown unique avec un frontmatter YAML. client.v2().perceive(url, PerceiveOptions { outputs: Some(vec![PerceiveOutputName::Markdown]), ..Default::default() }) renvoie le même contenu avec un score render_quality, des déductions nommées, des avertissements, et éventuellement des captures d'écran, des liens ou une extraction structurée issus du même rendu. Utilisez perceive quand un agent va lire le résultat.

Comment savoir si une page a réellement été rendue ?#

Lisez render_quality, un score de 0.0 à 1.0 présent sur chaque lecture V2. Un score faible signifie que le rendu n'a pas été propre : page anti-bot, mur de cookies ou de connexion, coquille SPA vide, ou erreur HTTP. La table deductions nomme chaque pénalité qui s'est appliquée, status_code donne le statut HTTP en amont, et warnings liste ce qui a mal tourné. Le contenu est quand même renvoyé, simplement signalé.

Que se passe-t-il quand une conversion dure plus longtemps que le timeout du proxy ?#

Le SDK s'en charge. Chaque requête porte un job_id généré par le client ; si la requête renvoie une 5xx, le SDK interroge GET /v1/convert/status/{job_id} toutes les 3 secondes pendant 5 minutes au maximum et renvoie le résultat terminé comme si de rien n'était. Passé ce délai, vous obtenez Error::Api { status: 504, message: "Conversion timed out" }. Les soumissions de lots de sites entiers font exception, et wait_for_batch est l'outil adapté dans ce cas.

Puis-je utiliser le SDK Rust depuis un navigateur ou une cible WASM ?#

Non. Il s'authentifie avec une clé API privée qui ne doit jamais être livrée à un client, et il dépend du client bloquant de reqwest, avec du TLS natif et des threads, dont aucun n'existe sur wasm32-unknown-unknown. Exécutez-le sur un serveur, dans une CLI ou dans un worker, et faites dialoguer votre frontend avec votre propre backend.