Official SDK

Rust

Async Rust with tokio runtime. Zero-cost abstractions, memory safety, and blazing fast performance for production workloads.

Rust 1.70+MIT licensed

Rename PDFs, split multi-page documents, and extract structured data using the official Rust SDK for Renamed.to.

  1. 1Run cargo add renamed or add to Cargo.toml dependencies
  2. 2Use async/await with rename(), pdf_split(), or extract() on byte slices
  3. 3Handle errors with pattern matching on the strongly-typed Error enum

MIT licensed, open source on GitHub, published on crates.io.

Installation

Shell
cargo add renamed

Quickstart

Initialize the client and start making API calls.

main.rs
Rust
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.

rename.rs
Rust
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.

split.rs
Rust
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.

extract.rs
Rust
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.

main.rs
Rust
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.

main.rs
Rust
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.

errors.rs
Rust
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 docs

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

Related resources

Other languages