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 renamedQuickstart
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.