> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dodopayments.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Rust

> Use the Dodo Payments Rust SDK, an async client built on Tokio and reqwest, to create checkout sessions, customers, subscriptions, and usage events.

The Rust SDK gives async Rust applications typed access to the Dodo Payments REST API. It's built on Tokio and reqwest, uses typed request and response structs, streams paginated results, and retries failed requests.

## Installation

Add the SDK to your project with Cargo:

```bash theme={null}
cargo add dodopayments
```

Or add it to your `Cargo.toml` manually:

```toml theme={null}
[dependencies]
dodopayments = "1.119.0"
tokio = { version = "1", features = ["full"] }
serde_json = "1"
futures = "0.3" # required for streaming paginated results
```

<Info>
  The SDK requires Rust 1.75 or later.
</Info>

## Quick Start

`Client::from_env()` reads your API key from the `DODO_PAYMENTS_API_KEY` environment variable. Create a client, then create a checkout session:

```rust theme={null}
use dodopayments::Client;

#[tokio::main]
async fn main() -> dodopayments::Result<()> {
    let client = Client::from_env()?;

    let result = client
        .checkout_sessions()
        .create()
        .body(dodopayments::models::CheckoutSessionsCreateParams {
            product_cart: Some(vec![dodopayments::models::ProductItemReq {
                product_id: "pdt_123".to_string(),
                quantity: 1,
                addons: None,
                amount: None,
                credit_entitlements: None,
            }]),
            ..Default::default()
        })
        .await?;

    println!("{result:?}");
    Ok(())
}
```

If `DODO_PAYMENTS_API_KEY` isn't set, `Client::from_env()` returns an `Error::Config`. The client connects to live mode unless you choose another environment, as shown in [Environments](#environments). A test mode API key works only in test mode.

<Warning>
  Keep API keys in environment variables or a secrets manager. Never hardcode them in your source code.
</Warning>

## Core Features

<CardGroup cols={2}>
  <Card title="Async First" icon="bolt">
    Built on Tokio and reqwest, with `async`/`await` for every request.
  </Card>

  <Card title="Strong Typing" icon="shield-check">
    Typed request and response structs for compile-time checks.
  </Card>

  <Card title="Auto-Pagination" icon="layer-group">
    Stream every item across pages, or move one page at a time.
  </Card>

  <Card title="Configurable" icon="sliders">
    Set the environment, base URL, timeout, and retry count for each client.
  </Card>
</CardGroup>

## Configuration

### Environment Variables

`Client::from_env()` reads your API key from `DODO_PAYMENTS_API_KEY`. It uses the live mode URL unless you set `DODO_PAYMENTS_BASE_URL`:

```bash theme={null}
export DODO_PAYMENTS_API_KEY="your_api_key"
export DODO_PAYMENTS_BASE_URL="https://test.dodopayments.com" # optional
```

The Rust SDK doesn't read `DODO_PAYMENTS_WEBHOOK_KEY` and has no method that verifies webhook signatures. To verify them, follow [Webhooks](/developer-resources/webhooks).

You can also configure the client explicitly. `Client::new` returns a `Result`, so unwrap it with `?` inside a function that returns `dodopayments::Result`:

```rust theme={null}
use dodopayments::{Client, ClientConfig};

#[tokio::main]
async fn main() -> dodopayments::Result<()> {
    let client = Client::new(
        ClientConfig::new("https://live.dodopayments.com").with_api_key("My API Key"),
    )?;
    println!("{}", client.base_url());
    Ok(())
}
```

### Environments

The SDK has two environments:

| Name | Base URL |
| - | - |
| `live_mode` | `https://live.dodopayments.com` |
| `test_mode` | `https://test.dodopayments.com` |

The default base URL is `https://live.dodopayments.com`. To select another environment, use the `Environment` enum instead of a hard-coded URL:

```rust theme={null}
use dodopayments::{Client, ClientConfig, Environment};

let client = Client::new(
    ClientConfig::from_environment(Environment::TestMode).with_api_key("My API Key"),
)?;
```

To keep reading the API key from `DODO_PAYMENTS_API_KEY` with `from_env()` but target another environment, override the environment on the config:

```rust theme={null}
use dodopayments::{Client, ClientConfig, Environment};

let client = Client::new(ClientConfig::from_env()?.with_environment(Environment::TestMode))?;
```

### Timeouts

The default request timeout is 30 seconds. Override it for a client with `with_timeout`:

```rust theme={null}
use std::time::Duration;
use dodopayments::{Client, ClientConfig};

let client = Client::new(
    ClientConfig::new("https://live.dodopayments.com")
        .with_api_key("My API Key")
        .with_timeout(Duration::from_secs(60)),
)?;
```

The client retries connection errors and responses with status 408, 409, 429, or 500 and above. It retries twice by default, with exponential backoff, and waits for the `Retry-After` header when the API sends one. To change the retry count, call `with_max_retries` on the `ClientConfig`, for example `.with_max_retries(0)` to turn retries off.

## Common Operations

The examples in this section use the `client` from [Quick Start](#quick-start).

### Create a Checkout Session

Create a checkout session with a return URL:

```rust theme={null}
let session = client
    .checkout_sessions()
    .create()
    .body(dodopayments::models::CheckoutSessionsCreateParams {
        product_cart: Some(vec![dodopayments::models::ProductItemReq {
            product_id: "pdt_123".to_string(),
            quantity: 1,
            addons: None,
            amount: None,
            credit_entitlements: None,
        }]),
        return_url: Some("https://yourdomain.com/return".to_string()),
        ..Default::default()
    })
    .await?;

println!("{session:?}");
```

Redirect the customer to `session.checkout_url`. Each checkout URL works once and expires after 24 hours. For every session option, see [Checkout Sessions](/developer-resources/checkout-session).

### Manage Customers

Create a customer with an email address and name, then retrieve it by ID:

```rust theme={null}
// Create a customer
let customer = client
    .customers()
    .create()
    .body(dodopayments::models::CustomersCreateParams {
        email: Some("customer@example.com".to_string()),
        name: Some("John Doe".to_string()),
        ..Default::default()
    })
    .await?;

// Retrieve a customer
let customer = client
    .customers()
    .retrieve()
    .customer_id("cus_123")
    .await?;

println!("{customer:?}");
```

### Handle Subscriptions

Create a subscription for an existing customer.

<Warning>
  `POST /subscriptions` (the SDK's `subscriptions().create()` method) is **deprecated**. It still works for existing integrations, but new integrations should create subscriptions through a [Checkout Session](/developer-resources/checkout-session).
</Warning>

```rust theme={null}
let subscription = client
    .subscriptions()
    .create()
    .body(dodopayments::models::SubscriptionsCreateParams {
        billing: Some(Box::new(dodopayments::models::BillingAddress {
            country: Box::new(dodopayments::models::CountryCode::Us),
            city: Some("San Francisco".to_string()),
            state: Some("CA".to_string()),
            street: Some("1 Market St".to_string()),
            zipcode: Some("94105".to_string()),
        })),
        customer: Some(Box::new(
            dodopayments::models::CustomerRequest::AttachExistingCustomer(Box::new(
                dodopayments::models::AttachExistingCustomer {
                    customer_id: "cus_123".to_string(),
                },
            )),
        )),
        product_id: Some("pdt_456".to_string()),
        quantity: Some(1),
        ..Default::default()
    })
    .await?;

println!("{subscription:?}");
```

<Info>
  `billing` requires only `country`, a `CountryCode` enum variant such as `CountryCode::Us`. `customer` is a `CustomerRequest` enum: pass `AttachExistingCustomer` for an existing customer or `NewCustomer` to create one. To charge an [on-demand subscription](/developer-resources/ondemand-subscriptions), call `client.subscriptions().charge().subscription_id(...)` with a `SubscriptionsChargeParams` body. Amount fields such as `product_price` are in the smallest currency unit (for example, `2500` is \$25.00).
</Info>

## Usage-Based Billing

### Ingest Usage Events

Send usage events for a customer:

```rust theme={null}
let response = client
    .usage_events()
    .ingest()
    .body(dodopayments::models::UsageEventsIngestParams {
        events: Some(vec![dodopayments::models::EventInput {
            customer_id: "cus_abc123".to_string(),
            event_id: "api_call_12345".to_string(),
            event_name: "api_request".to_string(),
            metadata: None,
            timestamp: None,
        }]),
        ..Default::default()
    })
    .await?;

println!("{response:?}");
```

The `event_id` is the idempotency key, so give each event a unique value. If `timestamp` is `None`, the event uses the current time.

### List Usage Events

List events filtered by customer and event name. The filters go in a JSON query object:

```rust theme={null}
let events = client
    .usage_events()
    .list()
    .query(serde_json::json!({
        "customer_id": "cus_abc123",
        "event_name": "api_request",
    }))
    .await?;

for event in &events.items {
    println!("{event:?}");
}
```

## Pagination

List endpoints return a typed page whose `items` field holds the current page of results. To stream every item across all pages, call `into_stream`:

```rust theme={null}
use futures::StreamExt;

let mut items = Box::pin(
    client
        .payments()
        .list()
        .query(serde_json::json!({}))
        .await?
        .into_stream(),
);

while let Some(item) = items.next().await {
    let item = item?;
    println!("{item:?}");
}
```

To move one page at a time, call `get_next_page`. It returns `None` after the last page:

```rust theme={null}
let mut page = client
    .payments()
    .list()
    .query(serde_json::json!({}))
    .await?;

loop {
    for item in &page.items {
        println!("{item:?}");
    }
    match page.get_next_page().await? {
        Some(next) => page = next,
        None => break,
    }
}
```

## Error Handling

Every method returns a `dodopayments::Result<T>`. Failures are variants of the `dodopayments::Error` enum: `Api` for an error status from the API, `Http` for transport errors, `Json` for serialization errors, `Config` for configuration errors, and `MissingPathParam` or `MissingBody` for incomplete requests. Match on it to handle API errors separately from transport errors:

```rust theme={null}
let result = client
    .checkout_sessions()
    .create()
    .body(dodopayments::models::CheckoutSessionsCreateParams {
        product_cart: Some(vec![dodopayments::models::ProductItemReq {
            product_id: "pdt_123".to_string(),
            quantity: 1,
            addons: None,
            amount: None,
            credit_entitlements: None,
        }]),
        ..Default::default()
    })
    .await;

match result {
    Ok(value) => println!("{value:?}"),
    Err(dodopayments::Error::Api { status, message }) => {
        eprintln!("API returned {status}: {message}");
    }
    Err(err) => eprintln!("request failed: {err}"),
}
```

## Undocumented Endpoints

To call an endpoint that has no typed method, use the low-level `request` builder. It applies authentication and the base URL. To name `reqwest::Method`, add `reqwest` 0.12 to your dependencies:

```rust theme={null}
let response = client
    .request(reqwest::Method::GET, "/some/path")
    .send()
    .await?;
```

## Resources

<CardGroup cols={2}>
  <Card title="GitHub Repository" icon="github" href="https://github.com/dodopayments/dodopayments-rust">
    Source code, releases, and the full method list.
  </Card>

  <Card title="Crates.io" icon="cube" href="https://crates.io/crates/dodopayments">
    The published crate and its versions.
  </Card>

  <Card title="API Reference" icon="book" href="/api-reference/introduction">
    Every endpoint, parameter, and response.
  </Card>

  <Card title="Discord Community" icon="discord" href="https://discord.gg/bYqAp4ayYh">
    Ask questions and talk with other developers.
  </Card>
</CardGroup>

## Support

For help with the Rust SDK:

* **Discord**: Join the [community server](https://discord.gg/bYqAp4ayYh) for real-time help.
* **Email**: Contact [support@dodopayments.com](mailto:support@dodopayments.com).
* **GitHub**: Open an issue on the [repository](https://github.com/dodopayments/dodopayments-rust/issues).

## Contributing

To contribute, read the [contributing guidelines](https://github.com/dodopayments/dodopayments-rust/blob/main/CONTRIBUTING.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.