# Rust Client

Source: https://docs.evomi.com/integrations/rust-client/

The **Evomi Rust Client** provides a production-ready, async interface to Evomi's API with full type safety and idiomatic Rust patterns.

---

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
evomi-client = "0.1.2"
tokio = { version = "1", features = ["full"] }
```

---

## Quick Start

```rust
use evomi_client::Client;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Initialize with API key (or set EVOMI_API_KEY env var)
    let client = Client::new("your-api-key", None, None)?;

    // Scrape a URL
    let result = client.scrape("https://example.com", None).await?;
    println!("{:?}", result);

    Ok(())
}
```

---

## Authentication

Set your API key via environment variable:

```bash
export EVOMI_API_KEY="your-api-key"
```

Or pass it directly:

```rust
let client = Client::new("your-api-key", None, None)?;
```

Custom base URL (for testing):

```rust
let client = Client::new("your-api-key", Some("https://custom.evomi.com".to_string()), None)?;
```

---

## Scraping Operations

### scrape(url, options)

Scrape a single URL with configurable options.

```rust
use evomi_client::{Client, ScrapeOptions};

let result = client.scrape("https://example.com", Some(ScrapeOptions {
    mode: Some("auto".to_string()),
    content: Some("markdown".to_string()),
    device: Some("windows".to_string()),
    proxy_type: Some("residential".to_string()),
    proxy_country: Some("US".to_string()),
    proxy_session_id: Some("abc123".to_string()),
    wait_until: Some("domcontentloaded".to_string()),
    ai_enhance: Some(true),
    ai_prompt: Some("Extract product data".to_string()),
    ai_source: Some("markdown".to_string()),
    js_instructions: Some(vec![serde_json::json!({"click": ".load-more"})]),
    execute_js: Some("window.scrollTo(0, document.body.scrollHeight)".to_string()),
    wait_seconds: Some(2),
    screenshot: Some(false),
    pdf: Some(false),
    excluded_tags: Some(vec!["nav".to_string(), "footer".to_string()]),
    excluded_selectors: Some(vec![".ads".to_string()]),
    block_resources: Some(vec!["image".to_string(), "stylesheet".to_string()]),
    capture_headers: Some(true),
    async_mode: Some(false),
    config_id: Some("cfg_abc123".to_string()),
    scheme_id: Some("sch_abc123".to_string()),
    extract_scheme: Some(vec![serde_json::json!({"label": "title", "type": "content", "selector": "h1"})]),
    storage_id: Some("stor_abc123".to_string()),
    use_default_storage: Some(false),

    webhook: None,
    ..Default::default()
})).await?;
```

| Field | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `mode` | `Option<String>` | `"auto"` | Scraping mode: `"request"`, `"browser"`, `"auto"` |
| `content` | `Option<String>` | `"markdown"` | Output format: `"html"`, `"markdown"`, `"screenshot"`, `"pdf"` |
| `device` | `Option<String>` | `"windows"` | Device type: `"windows"`, `"macos"`, `"android"` |
| `proxy_type` | `Option<String>` | `"residential"` | Proxy type: `"datacenter"`, `"residential"` |
| `proxy_country` | `Option<String>` | `"US"` | Two-letter country code |
| `proxy_session_id` | `Option<String>` | — | Proxy session ID (6-8 chars) |
| `wait_until` | `Option<String>` | `"domcontentloaded"` | Wait condition |
| `ai_enhance` | `Option<bool>` | `false` | Enable AI extraction |
| `ai_prompt` | `Option<String>` | — | Prompt for AI extraction |
| `ai_source` | `Option<String>` | — | AI source |
| `ai_force_json` | `Option<bool>` | `true` | Force AI response to JSON |
| `js_instructions` | `Option<Vec<Value>>` | — | JS actions |
| `execute_js` | `Option<String>` | — | Raw JavaScript to execute |
| `wait_seconds` | `Option<u32>` | `0` | Seconds to wait after page load |
| `screenshot` | `Option<bool>` | `false` | Capture screenshot |
| `pdf` | `Option<bool>` | `false` | Capture PDF |
| `excluded_tags` | `Option<Vec<String>>` | — | HTML tags to remove |
| `excluded_selectors` | `Option<Vec<String>>` | — | CSS selectors to remove |
| `block_resources` | `Option<Vec<String>>` | — | Resource types to block |
| `additional_headers` | `Option<HashMap<String, String>>` | — | Extra HTTP headers |
| `capture_headers` | `Option<bool>` | `false` | Capture response headers |
| `network_capture` | `Option<Vec<Value>>` | — | Network capture filters |
| `async_mode` | `Option<bool>` | `false` | Return immediately with task ID |
| `config_id` | `Option<String>` | — | Saved config ID |
| `scheme_id` | `Option<String>` | — | Saved extraction schema ID |
| `extract_scheme` | `Option<Vec<Value>>` | — | Inline extraction schema |
| `storage_id` | `Option<String>` | — | Storage config ID |
| `use_default_storage` | `Option<bool>` | `false` | Use default storage |
| `delivery` | `Option<String>` | `"json"` | Response format: `"raw"` or `"json"` |
| `include_content` | `Option<bool>` | `true` | Include content in response |
| `webhook` | `Option<WebhookConfig>` | — | Webhook configuration |

### crawl(domain, options)

Crawl a website to discover and scrape multiple pages.

```rust
use evomi_client::CrawlOptions;

let result = client.crawl("example.com", Some(CrawlOptions {
    max_urls: Some(100),
    depth: Some(2),
    url_pattern: Some("/blog/.*".to_string()),
    scraper_config: Some(serde_json::json!({"mode": "browser", "output": "markdown"})),
    async_mode: Some(false),
    webhook: None,
})).await?;
```

| Field | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `max_urls` | `Option<u32>` | `100` | Maximum URLs to crawl |
| `depth` | `Option<u32>` | `2` | Crawl depth |
| `url_pattern` | `Option<String>` | — | Regex pattern to filter URLs |
| `scraper_config` | `Option<Value>` | — | Config for scraping each page |
| `async_mode` | `Option<bool>` | `false` | Return immediately with task ID |

### map_website(domain, options)

Discover URLs from a website via sitemaps, CommonCrawl, or crawling.

```rust
use evomi_client::MapOptions;

let result = client.map_website("example.com", Some(MapOptions {
    sources: Some(vec!["sitemap".to_string(), "commoncrawl".to_string()]),
    max_urls: Some(500),
    url_pattern: Some("/products/.*".to_string()),
    check_if_live: Some(false),
    depth: Some(1),
    async_mode: Some(false),
    webhook: None,
})).await?;
```

| Field | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `sources` | `Option<Vec<String>>` | `["sitemap", "commoncrawl"]` | Discovery sources |
| `max_urls` | `Option<u32>` | `500` | Maximum URLs to discover |
| `url_pattern` | `Option<String>` | — | Regex pattern to filter URLs |
| `check_if_live` | `Option<bool>` | `false` | Check if URLs are live |
| `depth` | `Option<u32>` | `1` | Crawl depth if using crawl source |
| `async_mode` | `Option<bool>` | `false` | Return immediately with task ID |

### search_domains(query, options)

Find domains by searching the web.

```rust
use evomi_client::SearchOptions;

// Single query
let result = client.search_domains("e-commerce platforms", Some(SearchOptions {
    max_urls: Some(20),
    region: Some("us-en".to_string()),
    webhook: None,
})).await?;

// Multiple queries (up to 10)
let result = client.search_domains(
    vec!["web scraping tools".to_string(), "data extraction".to_string()],
    Some(SearchOptions { max_urls: Some(20), ..Default::default() })
).await?;
```

| Field | Type | Default | Description |
| :--- | :--- | :--- | :--- |
| `max_urls` | `Option<u32>` | `20` | Max domains per query (max: 100) |
| `region` | `Option<String>` | `"us-en"` | Region for results |

### agent_request(message)

Send a natural language request to the AI agent.

```rust
let result = client.agent_request("Scrape example.com and extract all product prices").await?;
```

### get_task_status(task_id, task_type)

Check the status of an async task.

```rust
let result = client.get_task_status("abc123", "scrape").await?;
// task_type: "scrape" | "crawl" | "map" | "config_generate" | "schema"
```

---

## Config Management

Save and reuse scrape configurations.

### list_configs(options)

```rust
use evomi_client::ListOptions;

let configs = client.list_configs(Some(ListOptions {
    page: Some(1),
    per_page: Some(20),
    sort_by: Some("created_at".to_string()),
    sort_order: Some("desc".to_string()),
})).await?;
```

### create_config(name, config)

```rust
let config = client.create_config("Product Scraper", serde_json::json!({
    "mode": "browser",
    "output": "markdown"
})).await?;
```

### get_config(config_id)

```rust
let config = client.get_config("cfg_abc123").await?;
```

### update_config(config_id, name, config)

```rust
let config = client.update_config("cfg_abc123", Some("New Name"), Some(serde_json::json!({
    "mode": "request"
}))).await?;
```

### delete_config(config_id)

```rust
let result = client.delete_config("cfg_abc123").await?;
```

### generate_config(name, prompt)

Generate a scrape config from natural language using AI.

```rust
let config = client.generate_config(
    "Amazon Scraper",
    "Scrape product title and price from Amazon product pages"
).await?;
```

---

## Schema Management

Define reusable structured data extraction schemas.

### list_schemas(options)

```rust
let schemas = client.list_schemas(Some(ListOptions {
    page: Some(1),
    per_page: Some(20),
    sort_by: Some("created_at".to_string()),
    sort_order: Some("desc".to_string()),
})).await?;
```

### create_schema(name, config, options)

```rust
use evomi_client::CreateSchemaOptions;

let schema = client.create_schema("Product Schema", serde_json::json!({
    "url": "https://example.com/product",
    "extract_scheme": [
        {"label": "title", "type": "content", "selector": "h1"},
        {"label": "price", "type": "content", "selector": ".price"}
    ]
}), Some(CreateSchemaOptions {
    test: Some(true),
    fix: Some(false),
    extract_prompt: None,
})).await?;
```

### get_schema(scheme_id)

```rust
let schema = client.get_schema("sch_abc123").await?;
```

### update_schema(scheme_id, name, config, options)

```rust
let schema = client.update_schema("sch_abc123", "Updated Schema", serde_json::json!({
    "url": "...",
    "extract_scheme": [...]
}), Some(CreateSchemaOptions {
    test: Some(true),
    ..Default::default()
})).await?;
```

### delete_schema(scheme_id)

```rust
let result = client.delete_schema("sch_abc123").await?;
```

### get_schema_status(scheme_id)

```rust
let status = client.get_schema_status("sch_abc123").await?;
```

---

## Schedule Management

Run scrape configs on a recurring schedule.

### list_schedules(options, active_only)

```rust
let schedules = client.list_schedules(Some(ListOptions {
    page: Some(1),
    per_page: Some(20),
    ..Default::default()
}), Some(false)).await?;
```

### create_schedule(name, config_id, interval_minutes, options)

```rust
use evomi_client::CreateScheduleOptions;

let schedule = client.create_schedule(
    "Daily Price Check",
    "cfg_abc123",
    1440,  // Daily (in minutes)
    Some(CreateScheduleOptions {
        start_time: Some("09:00".to_string()),
        stop_on_error: Some(true),
        webhook: None,
    })
).await?;
```

### get_schedule(schedule_id)

```rust
let schedule = client.get_schedule("sched_abc123").await?;
```

### update_schedule(schedule_id, name, config_id, interval_minutes, stop_on_error)

```rust
let schedule = client.update_schedule(
    "sched_abc123",
    Some("New Name"),
    None,
    Some(720),
    None
).await?;
```

### delete_schedule(schedule_id)

```rust
let result = client.delete_schedule("sched_abc123").await?;
```

### toggle_schedule(schedule_id)

```rust
let result = client.toggle_schedule("sched_abc123").await?;
```

### list_schedule_runs(schedule_id, page, per_page)

```rust
let runs = client.list_schedule_runs("sched_abc123", Some(1), Some(20)).await?;
```

---

## Storage Management

Connect cloud storage to automatically save scrape results.

### list_storage_configs()

```rust
let configs = client.list_storage_configs().await?;
```

### create_storage_config(name, storage_type, config, options)

```rust
use evomi_client::CreateStorageOptions;

// S3-compatible storage
let storage = client.create_storage_config(
    "My S3",
    "s3_compatible",
    serde_json::json!({
        "bucket": "my-bucket",
        "region": "us-east-1",
        "access_key": "...",
        "secret_key": "..."
    }),
    Some(CreateStorageOptions {
        set_as_default: Some(true),
    })
).await?;

// Google Cloud Storage
let storage = client.create_storage_config(
    "My GCS",
    "gcs",
    serde_json::json!({
        "bucket": "my-bucket",
        "credentials_json": "..."
    }),
    None
).await?;

// Azure Blob Storage
let storage = client.create_storage_config(
    "My Azure",
    "azure_blob",
    serde_json::json!({
        "container": "my-container",
        "connection_string": "..."
    }),
    None
).await?;
```

### update_storage_config(storage_id, name, config, set_as_default)

```rust
let storage = client.update_storage_config(
    "stor_abc123",
    Some("Renamed Storage"),
    None,
    Some(true)
).await?;
```

### delete_storage_config(storage_id)

```rust
let result = client.delete_storage_config("stor_abc123").await?;
```

---

## Public API

Access proxy credentials and related data.

### get_public_api()

Access the Evomi Public API to get proxy credentials and related data.

```rust
let data = client.get_public_api().await?;
// Returns proxy credentials and product information
```

### get_proxy_data()

Get detailed information about your proxy products.

```rust
let data = client.get_proxy_data().await?;
// Returns: {"products": {"rp": {...}, "sdc": {...}, "mp": {...}}, ...}
```

### get_targeting_options()

Get available targeting parameters for different proxy types.

```rust
let options = client.get_targeting_options().await?;
```

### get_scraper_data()

Get information about your Scraper API access.

```rust
let data = client.get_scraper_data().await?;
```

### get_browser_data()

Get information about your Browser API access.

```rust
let data = client.get_browser_data().await?;
```

### rotate_session(session_id, product)

Force an IP address change for an existing proxy session.

```rust
let result = client.rotate_session("abc12345", "rp").await?;
// product: "rpc", "rp", "sdc", "mp"
```

### generate_proxies(product, options)

Generate proxy strings with specific targeting parameters.

```rust
use evomi_client::GenerateProxiesOptions;

let proxies = client.generate_proxies("rp", Some(GenerateProxiesOptions {
    countries: Some("US,GB,DE".to_string()),
    city: Some("New York".to_string()),
    session: Some("sticky".to_string()),
    amount: Some(10),
    protocol: Some("http".to_string()),
    lifetime: Some(30),
    adblock: Some(true),
    ..Default::default()
})).await?;
// Returns plain text, one proxy per line
```

---

## Account Info

### get_account_info()

```rust
let info = client.get_account_info().await?;
println!("{:?}", info.get("credits_remaining"));
```

---

## Webhooks

Webhooks allow you to receive notifications when scraping operations complete, fail, or start. Supports Discord, Slack, and custom endpoints.

### WebhookConfig

```rust
use evomi_client::WebhookConfig;

// Discord webhook
let webhook = WebhookConfig::new("https://discord.com/api/webhooks/...")
    .webhook_type("discord")
    .events(vec!["completed".to_string(), "failed".to_string()]);

// Custom webhook with HMAC signature
let webhook = WebhookConfig::new("https://your-server.com/webhook")
    .webhook_type("custom")
    .events(vec!["completed".to_string()])
    .secret("your-secret-key");
```

### Using Webhooks with Scrape

```rust
let webhook = WebhookConfig::new("https://discord.com/api/webhooks/...")
    .webhook_type("discord")
    .events(vec!["completed".to_string(), "failed".to_string()]);

let result = client.scrape("https://example.com", Some(ScrapeOptions {
    mode: Some("browser".to_string()),
    webhook: Some(webhook),
    ..Default::default()
})).await?;
```

### Using Webhooks with Schedules

```rust
let webhook = WebhookConfig::new("https://hooks.slack.com/services/...")
    .webhook_type("slack")
    .events(vec!["completed".to_string()]);

let schedule = client.create_schedule(
    "Daily Monitor",
    "cfg_abc123",
    1440,
    Some(CreateScheduleOptions {
        webhook: Some(webhook),
        ..Default::default()
    })
).await?;
```

---

## Proxy String Builder

Evomi provides a proxy network you can use with any HTTP client. Build proxy strings for `reqwest` or any other library:

### Automatic Proxy Configuration

<!--email_off-->
```rust
use evomi_client::types::{ProxyType, ProxyProtocol, ProxyBuildOptions};

// Build a proxy string for US residential proxy
let proxy_string = client.build_proxy_string(
    ProxyType::Residential,
    Some(ProxyBuildOptions::new()
        .country("US")
        .session("abc12345")
        .protocol(ProxyProtocol::Http))
).await?;
println!("{}", proxy_string);
// Output: http://user:pass_country-US_session-abc12345@rp.evomi.com:1000
```
<!--email_on-->

### Manual Proxy Configuration

```rust
use evomi_client::proxy::ProxyConfig;
use evomi_client::types::{ProxyType, ProxyProtocol, ResidentialMode};

let config = ProxyConfig::new()
    .proxy_type(ProxyType::Residential)
    .protocol(ProxyProtocol::Http)
    .country("US")
    .city("New York")
    .credentials("username", "password");

let proxy_string = config.build_proxy_string();
```

### Proxy Configuration Options

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `proxy_type` | ProxyType | `Residential` | Type: `Residential`, `Datacenter`, `Mobile` |
| `protocol` | ProxyProtocol | `Http` | Protocol: `Http`, `Https`, `Socks5` |
| `country` | String | — | Two-letter ISO country code (e.g., "US", "DE") |
| `city` | String | — | City name (spaces replaced with dots) |
| `region` | String | — | State/region name |
| `continent` | String | — | Continent: africa, asia, europe, north.america, oceania, south.america |
| `isp` | String | — | ISP shortcode (e.g., "att", "comcast") |
| `session` | String | — | Sticky session ID (6-10 alphanumeric chars) |
| `hardsession` | String | — | Hard session ID (6-10 alphanumeric chars) |
| `lifetime` | u32 | — | Session duration in minutes (default 30, max 1440) |
| `mode` | ResidentialMode | — | Residential mode: `Speed`, `Quality` |

### Expert Settings

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `latency` | u32 | — | Max latency in ms |
| `fraudscore` | u32 | — | Max fraud score |
| `device` | String | — | Device type: "windows", "unix", "apple" |
| `activesince` | u32 | — | Min connection minutes |
| `asn` | String | — | ASN filter |
| `zip` | String | — | Zipcode targeting |
| `http3` | bool | `false` | Enable HTTP3/QUIC |
| `udp` | bool | `false` | UDP support (Enterprise only) |
| `extended` | bool | `false` | Extended pool (cannot combine with other expert filters) |

### Proxy Endpoints & Ports

| Type | HTTP | HTTPS | SOCKS5 |
|------|------|-------|--------|
| Residential | `rp.evomi.com:1000` | `rp.evomi-proxy.com:1001` | `rp.evomi.com:1002` |
| Datacenter | `dcp.evomi.com:2000` | `dcp.evomi-proxy.com:2001` | `dcp.evomi.com:2002` |
| Mobile | `mp.evomi.com:3000` | `mp.evomi-proxy.com:3001` | `mp.evomi.com:3002` |

HTTPS endpoints use the `evomi-proxy.com` domain — that is the hostname our TLS certificate covers, and it reaches the same servers. A client that verifies certificates will reject the `evomi.com` form on the HTTPS port.

### Proxy String Format

```
{protocol}://{username}:{password}_{params}@{endpoint}:{port}
```

Example: <!--email_off-->`http://user:pass_country-US_session-abc12345@rp.evomi.com:1000`<!--email_on-->

### Proxy Types

| Type | Endpoint | Use Case |
|------|----------|----------|
| Residential | `rp.evomi.com:1000` | Browser-compatible workflows and broad site coverage |
| Datacenter | `dcp.evomi.com:2000` | Fast, high-volume requests |
| Mobile | `mp.evomi.com:3000` | Highest trust, mobile-specific targets |

---

## Error Handling

```rust
use evomi_client::Error;

match client.scrape("https://example.com", None).await {
    Ok(result) => println!("{:?}", result),
    Err(Error::MissingApiKey) => eprintln!("API key not set"),
    Err(Error::HttpError(e)) => eprintln!("HTTP error: {}", e),
    Err(Error::JsonError(e)) => eprintln!("JSON error: {}", e),
    Err(Error::ApiError(msg)) => eprintln!("API error: {}", msg),
    Err(e) => eprintln!("Other error: {}", e),
}
```

---

## Credits & Pricing

All operations consume credits:

| Operation | Cost |
| :--- | :--- |
| Base request | 1 credit |
| Browser mode | 5x multiplier |
| Residential proxy | 2x multiplier |
| AI enhancement | +30 credits |
| Screenshot/PDF | +1 credit each |

Credit usage is returned in the result:

```rust
println!("{:?}", result.get("_credits_used"));
println!("{:?}", result.get("_credits_remaining"));
```

---

## Requirements

- Rust 1.70+
- Tokio runtime

---

## Resources

| Resource | Link |
| :--- | :--- |
| crates.io Package | [crates.io/crates/evomi-client](https://crates.io/crates/evomi-client) |
| Evomi Website | [evomi.com](https://evomi.com) |
| API Documentation | [docs.evomi.com](https://docs.evomi.com/overview/) |

---

## Benefits

- **Async/Await** — Built on Tokio for async operations
- **Full API Coverage** — All Evomi endpoints supported
- **Type Safe** — Strong typing with idiomatic Rust patterns
- **Builder Pattern** — Ergonomic configuration with builder methods
