Rust
Use ScreenshotAPI from Rust with the reqwest HTTP client.
Overview
These examples use reqwest, the most popular HTTP client for Rust. Add it to your Cargo.toml:
[dependencies]
reqwest = { version = "0.12", features = ["blocking"] }
tokio = { version = "1", features = ["full"] }Quick Start
Set your API key
export SCREENSHOTAPI_KEY="sk_live_your_key_here"Take a screenshot
use std::env;
use std::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("SCREENSHOTAPI_KEY")?;
let client = reqwest::Client::new();
let response = client
.get("https://screenshotapi.to/api/v1/screenshot")
.query(&[("url", "https://example.com")])
.header("x-api-key", &api_key)
.send()
.await?
.error_for_status()?;
let credits = response
.headers()
.get("x-credits-remaining")
.and_then(|v| v.to_str().ok())
.unwrap_or("unknown");
println!("Credits remaining: {credits}");
let bytes = response.bytes().await?;
fs::write("screenshot.png", &bytes)?;
Ok(())
}Client Struct
A reusable client with all screenshot options:
use reqwest::header::HeaderMap;
use std::collections::HashMap;
pub struct ScreenshotAPI {
api_key: String,
base_url: String,
client: reqwest::Client,
}
pub struct ScreenshotResult {
pub content: Vec<u8>,
pub content_type: String,
pub credits_remaining: i32,
pub screenshot_id: String,
pub duration_ms: i32,
}
#[derive(Default)]
pub struct CaptureOptions {
pub width: Option<u32>,
pub height: Option<u32>,
pub full_page: bool,
pub format: Option<String>,
pub quality: Option<u32>,
pub color_scheme: Option<String>,
pub wait_until: Option<String>,
pub wait_for_selector: Option<String>,
pub delay: Option<u32>,
}
impl ScreenshotAPI {
pub fn new(api_key: impl Into<String>) -> Self {
Self {
api_key: api_key.into(),
base_url: "https://screenshotapi.to".into(),
client: reqwest::Client::new(),
}
}
pub async fn capture(
&self,
url: &str,
opts: CaptureOptions,
) -> Result<ScreenshotResult, Box<dyn std::error::Error>> {
let mut params: Vec<(&str, String)> = vec![("url", url.to_string())];
if let Some(w) = opts.width { params.push(("width", w.to_string())); }
if let Some(h) = opts.height { params.push(("height", h.to_string())); }
if opts.full_page { params.push(("fullPage", "true".into())); }
if let Some(ref f) = opts.format { params.push(("type", f.clone())); }
if let Some(q) = opts.quality { params.push(("quality", q.to_string())); }
if let Some(ref cs) = opts.color_scheme { params.push(("colorScheme", cs.clone())); }
if let Some(ref wu) = opts.wait_until { params.push(("waitUntil", wu.clone())); }
if let Some(ref ws) = opts.wait_for_selector { params.push(("waitForSelector", ws.clone())); }
if let Some(d) = opts.delay { params.push(("delay", d.to_string())); }
let response = self
.client
.get(format!("{}/api/v1/screenshot", self.base_url))
.query(¶ms)
.header("x-api-key", &self.api_key)
.send()
.await?
.error_for_status()?;
let headers = response.headers().clone();
Ok(ScreenshotResult {
content: response.bytes().await?.to_vec(),
content_type: header_str(&headers, "content-type")
.unwrap_or("image/png".into()),
credits_remaining: header_int(&headers, "x-credits-remaining"),
screenshot_id: header_str(&headers, "x-screenshot-id")
.unwrap_or_default(),
duration_ms: header_int(&headers, "x-duration-ms"),
})
}
}
fn header_str(headers: &HeaderMap, name: &str) -> Option<String> {
headers.get(name)?.to_str().ok().map(String::from)
}
fn header_int(headers: &HeaderMap, name: &str) -> i32 {
header_str(headers, name)
.and_then(|v| v.parse().ok())
.unwrap_or(0)
}Usage
use std::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ScreenshotAPI::new(std::env::var("SCREENSHOTAPI_KEY")?);
let result = api.capture("https://github.com", CaptureOptions {
width: Some(1280),
height: Some(720),
format: Some("webp".into()),
quality: Some(85),
..Default::default()
}).await?;
fs::write("github.webp", &result.content)?;
println!("Credits remaining: {}", result.credits_remaining);
println!("Duration: {}ms", result.duration_ms);
Ok(())
}Common Patterns
Batch Screenshots
Capture multiple URLs concurrently with tokio::join!:
use futures::future::join_all;
use std::fs;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api = ScreenshotAPI::new(std::env::var("SCREENSHOTAPI_KEY")?);
let urls = vec![
"https://example.com",
"https://github.com",
"https://news.ycombinator.com",
];
let tasks: Vec<_> = urls
.iter()
.enumerate()
.map(|(i, url)| {
let api = &api;
async move {
match api.capture(url, CaptureOptions::default()).await {
Ok(result) => {
let filename = format!("screenshot-{i}.png");
fs::write(&filename, &result.content).ok();
println!("✓ {url} → {filename}");
}
Err(e) => eprintln!("✗ {url}: {e}"),
}
}
})
.collect();
join_all(tasks).await;
Ok(())
}Axum Handler
Serve screenshots in an Axum web application:
use axum::{extract::Query, http::StatusCode, response::IntoResponse};
use serde::Deserialize;
#[derive(Deserialize)]
struct ScreenshotParams {
url: String,
}
async fn screenshot_handler(
Query(params): Query<ScreenshotParams>,
) -> Result<impl IntoResponse, StatusCode> {
let api = ScreenshotAPI::new(std::env::var("SCREENSHOTAPI_KEY").unwrap());
let result = api
.capture(¶ms.url, CaptureOptions {
format: Some("webp".into()),
quality: Some(80),
..Default::default()
})
.await
.map_err(|_| StatusCode::BAD_GATEWAY)?;
Ok((
[(axum::http::header::CONTENT_TYPE, result.content_type),
(axum::http::header::CACHE_CONTROL, "public, max-age=3600".into())],
result.content,
))
}Error Handling
match api.capture("https://example.com", CaptureOptions::default()).await {
Ok(result) => {
std::fs::write("screenshot.png", &result.content)?;
}
Err(e) => {
let msg = e.to_string();
if msg.contains("402") {
eprintln!("Out of credits — purchase more at screenshotapi.to");
} else if msg.contains("403") {
eprintln!("Invalid API key — check your configuration");
} else {
eprintln!("Screenshot failed: {msg}");
}
}
}