Skip to main content
The Rust SDK provides convenient, async-first access to the Dodo Payments REST API from applications written in Rust. It offers strongly typed requests and responses, built-in pagination helpers, and configurable timeouts and environments.

Installation

Add the SDK to your project with Cargo:
cargo add dodopayments
Or add it to your Cargo.toml manually:
[dependencies]
dodopayments = "1.106.0"
tokio = { version = "1", features = ["full"] }
serde_json = "1"
futures = "0.3" # required for streaming paginated results
The SDK requires Rust 1.75 or later, leveraging modern async features for optimal performance.

Quick Start

The client reads your API key from the DODO_PAYMENTS_API_KEY environment variable by default. Initialize the client and create your first checkout session:
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: "product_id".to_string(),
                quantity: 1,
                addons: None,
                amount: None,
                credit_entitlements: None,
            }]),
            ..Default::default()
        })
        .await?;

    println!("{result:?}");
    Ok(())
}
Always store your API keys securely using environment variables. Never hardcode them in your source code.

Core Features

Async First

Built on Tokio and reqwest with async/await support throughout

Strong Typing

Strongly typed requests and responses for compile-time safety

Auto-Pagination

Stream every item across pages or advance one page at a time

Configurable

Configure environments, timeouts, and base URLs per client

Configuration

Environment Variables

By default, Client::from_env() reads your API key from the DODO_PAYMENTS_API_KEY environment variable and uses the default base URL unless you set DODO_PAYMENTS_BASE_URL:
export DODO_PAYMENTS_API_KEY="your_api_key"
export DODO_PAYMENTS_BASE_URL="https://test.dodopayments.com" # optional
You can also configure the client explicitly. Client::new returns a Result, so unwrap it with ? inside a function that returns dodopayments::Result:
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

NameBase URL
live_modehttps://live.dodopayments.com
test_modehttps://test.dodopayments.com
The default base URL is https://live.dodopayments.com. Select another environment with the Environment enum instead of hard-coding URLs:
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 via from_env() while targeting a non-default environment, override it on the config:
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 per client:
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)),
)?;

Common Operations

Create a Checkout Session

Generate a checkout session:
let session = client
    .checkout_sessions()
    .create()
    .body(dodopayments::models::CheckoutSessionsCreateParams {
        product_cart: Some(vec![dodopayments::models::ProductItemReq {
            product_id: "prod_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:?}");

Manage Customers

Create and retrieve customer information:
// Create a customer
let customer = client
    .customers()
    .create()
    .body(dodopayments::models::CustomerCreateParams {
        email: "customer@example.com".to_string(),
        name: "John Doe".to_string(),
        ..Default::default()
    })
    .await?;

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

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

Handle Subscriptions

Create and manage recurring subscriptions:
let subscription = client
    .subscriptions()
    .create()
    .body(dodopayments::models::SubscriptionCreateParams {
        billing: dodopayments::models::BillingAddress {
            country: "US".to_string(),
            city: "San Francisco".to_string(),
            state: "CA".to_string(),
            street: "1 Market St".to_string(),
            zipcode: "94105".to_string(),
        },
        customer: dodopayments::models::CustomerRequest::AttachExisting(
            dodopayments::models::AttachExistingCustomer {
                customer_id: "cus_123".to_string(),
            },
        ),
        product_id: "pdt_456".to_string(),
        quantity: 1,
        ..Default::default()
    })
    .await?;

println!("{subscription:?}");
billing requires at minimum the two-letter ISO country code. customer is a CustomerRequest enum — pass AttachExisting for an existing customer or New for a new one. Amount fields such as product_price are in the lowest currency denomination (e.g., 2500 = $25.00 USD).

Usage-Based Billing

Ingest Usage Events

Track custom events:
let response = client
    .usage_events()
    .ingest()
    .body(dodopayments::models::UsageEventsIngestParams {
        events: vec![dodopayments::models::EventInput {
            event_id: "api_call_12345".to_string(),
            customer_id: "cus_abc123".to_string(),
            event_name: "api_request".to_string(),
            ..Default::default()
        }],
    })
    .await?;

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

List Usage Events

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. Stream every item across all pages with into_stream:
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:?}");
}
Or advance one page at a time with get_next_page:
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 represented by the dodopayments::Error enum. Match on it to handle API errors distinctly from transport errors:
let result = client
    .checkout_sessions()
    .create()
    .body(dodopayments::models::CheckoutSessionsCreateParams {
        product_cart: Some(vec![dodopayments::models::ProductItemReq {
            product_id: "product_id".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 not yet exposed as a typed method, use the low-level request builder, which applies authentication and the base URL:
let response = client
    .request(reqwest::Method::GET, "/some/path")
    .send()
    .await?;

Resources

GitHub Repository

View source code and contribute

Crates.io

View the published crate and versions

API Reference

Complete API documentation

Discord Community

Get help and connect with developers

Support

Need help with the Rust SDK?

Contributing

We welcome contributions! Check the contributing guidelines to get started.
Last modified on June 20, 2026