leadtext
leadtext
leadtext
Invoices, receipts, statements
Auto-title papers
Matter-ready naming
Field, onboarding, reporting ops
Shot logs, call sheets, deliverables
Configs, audit logs, incidents
Transcripts, lesson plans, records
Invoices, product sheets, shipping
Leases, inspections, work orders
Receipts, reports, reimbursements
BOLs, PODs, carrier invoices
Official SDK
Rust
Async Rust with tokio runtime. Zero-cost abstractions, memory safety, and blazing fast performance for production workloads.
Rename PDFs, split multi-page documents, and extract structured data using the official Rust SDK for Renamed.to.
- 1Run cargo add renamed or add to Cargo.toml dependencies
- 2Use async/await with rename(), pdf_split(), or extract() on byte slices
- 3Handle errors with pattern matching on the strongly-typed Error enum
MIT licensed, open source on GitHub, published on crates.io.
On this page
Installation
cargo add renamed[dependencies]renamed = "0.1"Quickstart
Initialize the client and start making API calls.
use renamed::Client;use std::env;use tokio::fs;#[tokio::main]async fn main() -> Result<(), renamed::Error> { // Initialize the client let client = Client::new(env::var("RENAMED_API_KEY")?); // Rename a PDF let pdf_bytes = fs::read("invoice.pdf").await?; let result = client.rename(&pdf_bytes).await?; println!("{}", result.suggested_filename); // -> "2024-01-15_AcmeCorp_INV-1234.pdf" // Split a multi-page PDF let docs = fs::read("documents.pdf").await?; let job = client.pdf_split(&docs, SplitOptions { mode: "smart".into(), }).await?; println!("{:?}", job.documents); // Extract structured data let receipt = fs::read("receipt.pdf").await?; let data = client.extract(&receipt).await?; println!("{:?}", data); Ok(())}Rename a PDF
Pass a byte slice to get an AI-generated filename. Use custom instructions to control the naming format.
use renamed::{Client, RenameOptions};use std::env;use tokio::fs;#[tokio::main]async fn main() -> Result<(), renamed::Error> { // Initialize the client let client = Client::builder() .api_key(env::var("RENAMED_API_KEY")?) .timeout(std::time::Duration::from_secs(30)) .build()?; // Read the PDF file let pdf_bytes = fs::read("./documents/invoice.pdf").await?; // Rename the PDF let result = client.rename(&pdf_bytes).await?; println!("Suggested filename: {}", result.suggested_filename); println!("Confidence: {}", result.confidence); // Output: // Suggested filename: 2024-01-15_AcmeCorp_INV-1234.pdf // Confidence: 0.95 // Access extracted metadata if let Some(metadata) = &result.metadata { println!("Vendor: {:?}", metadata.get("vendor")); println!("Date: {:?}", metadata.get("date")); println!("Type: {:?}", metadata.get("type")); } // Use custom naming instructions let custom_result = client .rename_with_options( &pdf_bytes, RenameOptions { instructions: Some("Format: YYYY-MM-DD_VendorName_Amount".into()), ..Default::default() }, ) .await?; println!("{}", custom_result.suggested_filename); // -> "2024-01-15_AcmeCorp_$1250.pdf" Ok(())}Split a PDF
Split multi-page PDFs into separate documents. The API uses async jobs for large files—poll for completion or use the built-in wait helper.
use renamed::{Client, SplitOptions};use std::env;use std::time::Duration;use tokio::fs;#[tokio::main]async fn main() -> Result<(), renamed::Error> { let client = Client::builder() .api_key(env::var("RENAMED_API_KEY")?) .timeout(Duration::from_secs(300)) // 5 minutes for large files .build()?; // Read a multi-page PDF let pdf_bytes = fs::read("./documents/combined.pdf").await?; // Submit the split job with smart mode (AI detects document boundaries) let job = client .pdf_split( &pdf_bytes, SplitOptions { mode: "smart".into(), }, ) .await?; println!("Job ID: {}", job.job_id); println!("Status: {:?}", job.state); // Option 1: Wait for completion (built-in polling) let completed = job.wait_for_completion().await?; // Option 2: Manual polling for progress updates // loop { // let status = client.get_job(&job.job_id).await?; // println!("Progress: {}%", status.progress); // if status.state == JobState::Completed { // break; // } // tokio::time::sleep(Duration::from_secs(1)).await; // } // Process the split documents println!("Split into {} documents:", completed.documents.len()); for (i, doc) in completed.documents.iter().enumerate() { println!(" [{}] {} (pages {:?})", i + 1, doc.filename, doc.pages); // Output: // [1] invoice_1.pdf (pages [1, 2]) // [2] invoice_2.pdf (pages [3, 4]) // Download each split document let doc_bytes = client.download_file(&doc.download_url).await?; // Save to output directory let output_path = format!("./output/{}", doc.filename); fs::create_dir_all("./output").await?; fs::write(&output_path, doc_bytes).await?; println!(" Saved: {}", output_path); } Ok(())}Extract Data
Extract structured data from invoices, receipts, and other documents. Use serde for type-safe deserialization.
use renamed::Client;use serde::Deserialize;use std::env;use tokio::fs;// Define typed structs for invoice extraction#[derive(Debug, Deserialize)]#[serde(rename_all = "camelCase")]struct LineItem { description: String, quantity: i32, unit_price: f64,}#[derive(Debug, Deserialize)]#[serde(rename_all = "camelCase")]struct Invoice { invoice_number: String, vendor: String, date: String, line_items: Vec<LineItem>, subtotal: f64, tax: f64, total: f64,}#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new(env::var("RENAMED_API_KEY")?); // Read the PDF file let pdf_bytes = fs::read("./documents/invoice.pdf").await?; // Basic extraction (returns serde_json::Value) let data = client.extract(&pdf_bytes).await?; println!("Vendor: {}", data["vendor"]); println!("Date: {}", data["date"]); println!("Total: {}", data["total"]); // Output: // Vendor: "Acme Corp" // Date: "2024-01-15" // Total: 1250.0 // Typed extraction: deserialize to a struct let invoice: Invoice = serde_json::from_value(data)?; // Now you have fully typed access println!("Invoice #{} from {}", invoice.invoice_number, invoice.vendor); println!("Line items: {}", invoice.line_items.len()); for item in &invoice.line_items { println!( " - {}: {} x {:.2}", item.description, item.quantity, item.unit_price ); } println!("Total: {:.2}", invoice.total); // Output: // Invoice #INV-2024-1234 from Acme Corp // Line items: 2 // - Widget Pro: 5 x $199.00 // - Support Plan: 1 x $342.50 // Total: $1337.50 Ok(())}Framework Integrations
Common integration patterns for popular frameworks.
Axum Handler
Handle PDF uploads in an Axum web server with multipart form handling and proper error responses.
use axum::{ extract::{Multipart, State}, http::StatusCode, response::Json, routing::post, Router,};use renamed::{Client, Error, SplitOptions};use serde::Serialize;use std::sync::Arc;// Shared application statestruct AppState { renamed_client: Client,}#[derive(Serialize)]struct RenameResponse { suggested_filename: String, confidence: f64,}#[derive(Serialize)]struct ErrorResponse { error: String,}async fn rename_handler( State(state): State<Arc<AppState>>, mut multipart: Multipart,) -> Result<Json<RenameResponse>, (StatusCode, Json<ErrorResponse>)> { // Extract the file from multipart form let mut pdf_bytes = None; while let Some(field) = multipart .next_field() .await .map_err(|e| bad_request(&e.to_string()))? { if field.name() == Some("file") { pdf_bytes = Some( field .bytes() .await .map_err(|e| bad_request(&e.to_string()))?, ); } } let pdf_bytes = pdf_bytes.ok_or_else(|| bad_request("No file provided"))?; // Call the Renamed API match state.renamed_client.rename(&pdf_bytes).await { Ok(result) => Ok(Json(RenameResponse { suggested_filename: result.suggested_filename, confidence: result.confidence, })), Err(Error::RateLimited { retry_after }) => Err(( StatusCode::TOO_MANY_REQUESTS, Json(ErrorResponse { error: format!("Rate limited. Retry after {} seconds", retry_after), }), )), Err(e) => Err(( StatusCode::BAD_GATEWAY, Json(ErrorResponse { error: e.to_string(), }), )), }}#[derive(Serialize)]struct SplitResponse { job_id: String, documents: Vec<SplitDocument>,}#[derive(Serialize)]struct SplitDocument { filename: String, pages: Vec<u32>, download_url: String,}async fn split_handler( State(state): State<Arc<AppState>>, mut multipart: Multipart,) -> Result<Json<SplitResponse>, (StatusCode, Json<ErrorResponse>)> { let mut pdf_bytes = None; let mut mode = "smart".to_string(); while let Some(field) = multipart .next_field() .await .map_err(|e| bad_request(&e.to_string()))? { match field.name() { Some("file") => { pdf_bytes = Some( field .bytes() .await .map_err(|e| bad_request(&e.to_string()))?, ); } Some("mode") => { mode = field .text() .await .map_err(|e| bad_request(&e.to_string()))?; } _ => {} } } let pdf_bytes = pdf_bytes.ok_or_else(|| bad_request("No file provided"))?; // Start the split job let job = state .renamed_client .pdf_split(&pdf_bytes, SplitOptions { mode }) .await .map_err(|e| (StatusCode::BAD_GATEWAY, Json(ErrorResponse { error: e.to_string() })))?; // Wait for completion let completed = job .wait_for_completion() .await .map_err(|e| (StatusCode::BAD_GATEWAY, Json(ErrorResponse { error: e.to_string() })))?; Ok(Json(SplitResponse { job_id: completed.job_id, documents: completed .documents .into_iter() .map(|d| SplitDocument { filename: d.filename, pages: d.pages, download_url: d.download_url, }) .collect(), }))}fn bad_request(msg: &str) -> (StatusCode, Json<ErrorResponse>) { ( StatusCode::BAD_REQUEST, Json(ErrorResponse { error: msg.to_string(), }), )}#[tokio::main]async fn main() { // Initialize the client let renamed_client = Client::builder() .api_key(std::env::var("RENAMED_API_KEY").expect("RENAMED_API_KEY required")) .timeout(std::time::Duration::from_secs(300)) .build() .expect("Failed to build client"); let state = Arc::new(AppState { renamed_client }); // Build the router let app = Router::new() .route("/api/rename", post(rename_handler)) .route("/api/split", post(split_handler)) .with_state(state); // Run the server let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await.unwrap(); println!("Server running on http://localhost:3000"); axum::serve(listener, app).await.unwrap();}Actix-web Handler
Process PDF uploads with Actix-web using its multipart handling and extractors.
use actix_multipart::Multipart;use actix_web::{web, App, HttpResponse, HttpServer};use futures_util::StreamExt;use renamed::{Client, SplitOptions};use serde::Serialize;use std::sync::Arc;struct AppState { renamed_client: Client,}#[derive(Serialize)]struct RenameResponse { suggested_filename: String, confidence: f64,}async fn rename_handler( state: web::Data<Arc<AppState>>, mut payload: Multipart,) -> HttpResponse { let mut pdf_bytes: Option<Vec<u8>> = None; // Process multipart fields while let Some(Ok(mut field)) = payload.next().await { if field.name() == "file" { let mut bytes = Vec::new(); while let Some(Ok(chunk)) = field.next().await { bytes.extend_from_slice(&chunk); } pdf_bytes = Some(bytes); } } let pdf_bytes = match pdf_bytes { Some(bytes) => bytes, None => { return HttpResponse::BadRequest().json(serde_json::json!({ "error": "No file provided" })) } }; // Call the Renamed API match state.renamed_client.rename(&pdf_bytes).await { Ok(result) => HttpResponse::Ok().json(RenameResponse { suggested_filename: result.suggested_filename, confidence: result.confidence, }), Err(renamed::Error::RateLimited { retry_after }) => { HttpResponse::TooManyRequests() .insert_header(("Retry-After", retry_after.to_string())) .json(serde_json::json!({ "error": format!("Rate limited. Retry after {} seconds", retry_after) })) } Err(e) => HttpResponse::BadGateway().json(serde_json::json!({ "error": e.to_string() })), }}async fn extract_handler( state: web::Data<Arc<AppState>>, mut payload: Multipart,) -> HttpResponse { let mut pdf_bytes: Option<Vec<u8>> = None; while let Some(Ok(mut field)) = payload.next().await { if field.name() == "file" { let mut bytes = Vec::new(); while let Some(Ok(chunk)) = field.next().await { bytes.extend_from_slice(&chunk); } pdf_bytes = Some(bytes); } } let pdf_bytes = match pdf_bytes { Some(bytes) => bytes, None => { return HttpResponse::BadRequest().json(serde_json::json!({ "error": "No file provided" })) } }; match state.renamed_client.extract(&pdf_bytes).await { Ok(data) => HttpResponse::Ok().json(data), Err(e) => HttpResponse::BadGateway().json(serde_json::json!({ "error": e.to_string() })), }}#[actix_web::main]async fn main() -> std::io::Result<()> { let renamed_client = Client::builder() .api_key(std::env::var("RENAMED_API_KEY").expect("RENAMED_API_KEY required")) .build() .expect("Failed to build client"); let state = Arc::new(AppState { renamed_client }); println!("Server running on http://localhost:8080"); HttpServer::new(move || { App::new() .app_data(web::Data::new(state.clone())) .route("/api/rename", web::post().to(rename_handler)) .route("/api/extract", web::post().to(extract_handler)) }) .bind("0.0.0.0:8080")? .run() .await}Error Handling
Use pattern matching to handle specific error types. The SDK uses a strongly-typed Error enum.
use renamed::{Client, Error};use std::env;use std::time::Duration;use tokio::fs;#[tokio::main]async fn main() -> Result<(), Box<dyn std::error::Error>> { let client = Client::new(env::var("RENAMED_API_KEY")?); let pdf_bytes = fs::read("invoice.pdf").await?; match client.rename(&pdf_bytes).await { Ok(result) => { println!("Renamed to: {}", result.suggested_filename); } Err(Error::RateLimited { retry_after }) => { // Rate limited - wait and retry println!("Rate limited. Retry after {} seconds", retry_after); tokio::time::sleep(Duration::from_secs(retry_after)).await; // Retry the request... } Err(Error::InvalidFile { message }) => { // File is not a valid PDF or is corrupted println!("Invalid file: {}", message); } Err(Error::Authentication { message }) => { // API key is invalid or expired println!("Authentication failed: {}", message); } Err(Error::Api { code, message, details }) => { // Other API errors println!("API Error [{}]: {}", code, message); if let Some(d) = details { println!("Details: {}", d); } } Err(Error::Network { source }) => { // Network/connection errors println!("Network error: {}", source); } Err(Error::Timeout) => { // Request timed out println!("Request timed out"); } Err(e) => { // Other errors return Err(e.into()); } } Ok(())}// Using the ? operator with custom error handlingasync fn rename_with_retry( client: &Client, pdf_bytes: &[u8], max_retries: u32,) -> Result<renamed::RenameResult, Error> { let mut retries = 0; loop { match client.rename(pdf_bytes).await { Ok(result) => return Ok(result), Err(Error::RateLimited { retry_after }) if retries < max_retries => { retries += 1; println!( "Rate limited, retrying in {}s (attempt {}/{})", retry_after, retries, max_retries ); tokio::time::sleep(Duration::from_secs(retry_after)).await; } Err(e) => return Err(e), } }}Full documentation on GitHub
For more examples, advanced usage patterns, and detailed API documentation, see the full Rust SDK README on GitHub.
Read the Rust SDK docsFrequently asked questions
- What Rust version is required?
- The Rust SDK requires Rust 1.70 or later. It uses async/await syntax and requires the tokio runtime.
- Which async runtime does it use?
- The SDK is built on tokio for async I/O. It should work with other runtimes that are compatible with tokio.
- How are errors handled?
- The SDK uses a custom Error type that implements std::error::Error. Use pattern matching to handle specific error variants.
- Is the client thread-safe?
- Yes, the Client is Send + Sync and can be safely shared across threads using Arc. It uses an internal connection pool.
- Does it support custom HTTP clients?
- The SDK uses reqwest internally but allows configuration of timeouts, retries, and other HTTP settings via the builder pattern.