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

# Rust SDK

> Official Rust SDK for the Alguna Public API: installation, authentication, resources, pagination, and error handling.

`alguna` is the official Rust client for the Alguna Public API. It has one async method for each endpoint, with request and response types generated from the versioned OpenAPI spec. Use it from Rust services where you would otherwise write HTTP calls by hand. It sets the `Authorization` and `Alguna-Version` headers, maps failed responses to typed errors, sends an idempotency key on writes, and retries a call only when a retry is safe. The objects it handles are described in [How Alguna works](/docs/concepts/how-alguna-works).

<Note>
  The SDK is for servers only. API keys are secret, so never ship them to a browser or a client app. To embed a purchase flow in your frontend, use [Embedded Checkout](/docs/hosted/embedded-checkout). Your server creates the session with this SDK, and your frontend mounts it.
</Note>

***

## How it works

1. **Create an API key** under **Settings → Connections → Developers → API keys**. A key looks like `<key_id>.<key_secret>`. It is a server secret.
2. **Build the client** with the key. Every request carries `Authorization: Bearer` and the `Alguna-Version` header.
3. **Call resources** (`client.customers`, `client.subscriptions`, and so on). A method returns a typed response, or an `ApiError`.

## Installation

```bash theme={null}
cargo add alguna
cargo add tokio --features macros,rt-multi-thread
```

The crate needs an async runtime such as `tokio`. It uses `rustls` for TLS. The crate is [`alguna` on crates.io](https://crates.io/crates/alguna). The API docs are on [docs.rs](https://docs.rs/alguna). The source is [alguna-io/alguna-rust](https://github.com/alguna-io/alguna-rust) on GitHub.

```rust theme={null}
use alguna::prelude::*;
use alguna::ApiClientBuilder;

fn client() -> Result<Alguna, ApiError> {
    // Reads the API key from ALGUNA_API_KEY when you do not call api_key.
    ApiClientBuilder::default()
        .api_version("2026-04-01") // recommended: pin the version you integrate against
        .build()
}
```

If there is no key, `build()` returns `ApiError::Configuration` and sends nothing.

***

## Quick start: customer → subscription → invoice → portal session

```rust theme={null}
use alguna::prelude::*;
use alguna::ApiClientBuilder;

#[tokio::main]
async fn main() -> Result<(), ApiError> {
    let client = ApiClientBuilder::default().build()?;

    // 1. Create a customer
    let customer = client
        .customers
        .create(
            &CreateCustomerRequest {
                name: "Acme Corp".to_string(),
                currency: "USD".to_string(),
                ..Default::default()
            },
            None,
        )
        .await?;

    // 2. Start a subscription from a plan you made in the dashboard or API
    let subscription = client
        .subscriptions
        .create(
            &CreateSubscriptionRequest {
                customer_id: customer.id.clone(),
                plan_id: Some("<plan_id>".to_string()),
                auto_activate: Some(true),
                ..Default::default()
            },
            None,
        )
        .await?;
    println!("subscription {}", subscription.id);

    // 3. Issue a one-off invoice
    let invoice = client
        .invoices
        .create(
            &CreateInvoiceRequest {
                customer_id: Some(customer.id.clone()),
                currency: Some("USD".to_string()),
                line_items: Some(vec![CreateInvoiceLineItemRequest {
                    description: Some("Onboarding".to_string()),
                    unit_price: "500.00".to_string(),
                    quantity: "1".to_string(),
                    ..Default::default()
                }]),
                ..Default::default()
            },
            None,
        )
        .await?;
    println!("invoice {}", invoice.id);

    // 4. Open a customer portal session so the customer can pay and manage billing
    let session = client
        .portal
        .create_session(
            &CreateCustomerPortalSessionRequest {
                customer_id: Some(customer.id.clone()),
                ..Default::default()
            },
            None,
        )
        .await?;
    println!("{}", session.url); // send the customer here before session.expires_at
    Ok(())
}
```

<Note>
  A subscription starts in `draft` status. Pass `auto_activate: Some(true)` (as above) or call `client.subscriptions.activate` when you are ready to go live. See [Creating a Subscription](/docs/api-reference/v2/examples/creating-a-subscription) for the full set of patterns: plans, overrides and bundles all work the same way through the SDK.
</Note>

***

## Resources

Each API resource is a field of the client. The client is generated from the versioned spec, so every public operation has a method. A method takes a reference to its request and an optional `RequestOptions`. Method names are in snake\_case. Highlights:

| Resource | Methods |
| - | - |
| `client.customers` | `create`, `get`, `list`, `list_paginated`, `update` |
| `client.subscriptions` | `create`, `get`, `list`, `list_paginated`, `update`, `activate`, `cancel`, `delete`, `download_pdf`, `get_revenue` |
| `client.invoices` | `create`, `get`, `list`, `list_paginated`, `update`, `mark_as_paid`, `void`, `generate_pdf`, line items |
| `client.portal` | `create_session` for the [customer portal](/docs/hosted/customer-portal) |
| `client.events` | `ingest` usage events |
| `client.credits` | `check`, `track` |

Other resources include `products`, `plans`, `bundles`, `subscription_versions`, `subscription_changes`, `checkout_sessions`, `wallets`, `wallet_grants`, `credit_notes`, `payments`, `refunds`, `quotes` and `metrics`. Every method name follows the operation it wraps. The full list is in the [API reference](/docs/api-reference/v2/overview).

Request and response types are in `alguna::prelude::*`. Optional fields are `Option`. Request structs implement `Default`, so use `..Default::default()` for fields you do not set. Every method takes `Option<RequestOptions>` as its last argument. `RequestOptions` can set `api_version`, `idempotency_key`, `timeout_seconds`, `max_retries`, `api_key`, `additional_header` and `additional_query_param` for one call.

```rust theme={null}
use alguna::prelude::*;

async fn list_metered(client: &Alguna) -> Result<(), ApiError> {
    let filter = ProductsListQueryRequest {
        fee_type: Some(vec![ListProductsRequestFeeTypeItem::Metered]),
        ..Default::default()
    };
    for product in client.products.list(&filter, None).await?.data {
        println!("{}", product.name);
    }
    Ok(())
}
```

## Pagination

`list` returns one page of results in `data`. `list_paginated` returns a `Stream` of every item. It loads the next page when the stream needs it. Add `futures = "0.3"` to your dependencies for `StreamExt`.

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

async fn every_customer(client: &Alguna) -> Result<(), ApiError> {
    let mut customers = client
        .customers
        .list_paginated(&CustomersListQueryRequest::default(), None)
        .await?;
    while let Some(customer) = customers.next().await {
        let customer: CustomerResponse = customer?;
        println!("{}", customer.name);
    }
    Ok(())
}
```

To limit or filter a list, set the fields of the query struct, such as `limit`. The filters match the [HTTP API](/docs/api-reference/v2/overview#filtering-and-sorting).

## PDF downloads

`invoices.generate_pdf` and `subscriptions.download_pdf` return a `ByteStream`. Call `collect().await` to read the whole file into a `Vec<u8>`. Call `try_next().await` to read it in chunks.

```rust theme={null}
use alguna::prelude::*;

async fn save_pdf(client: &Alguna, invoice_id: &str) -> Result<(), Box<dyn std::error::Error>> {
    let pdf = client.invoices.generate_pdf(invoice_id, None).await?;
    std::fs::write("invoice.pdf", pdf.collect().await?)?;
    Ok(())
}
```

***

## Error handling

Every call returns `Result<_, ApiError>`. `ApiError` is `#[non_exhaustive]`, so keep a wildcard arm. The API error variants are:

| Variant | Status |
| - | - |
| `BadRequestError` | 400 |
| `UnauthorizedError` | 401 |
| `ForbiddenError` | 403 |
| `NotFoundError` | 404 |
| `ConflictError` | 409 |
| `UnprocessableEntityError` | 422 |
| `TooManyRequestsError` | 429 |
| `InternalServerError` | 500 |
| `BadGatewayError` | 502 |
| `ServiceUnavailableError` | 503 |
| `GatewayTimeoutError` | 504 |
| `Http { status, message }` | another status |

Most of these carry `message` and the optional `detail`, `status`, `title` and `type_` fields of the error body. Other variants cover a failure with no API response: `Network` (the cause is the error's `source()`), `Serialization` and `Configuration`.

```rust theme={null}
use alguna::prelude::*;

async fn find(client: &Alguna, id: &str) -> Result<Option<CustomerResponse>, ApiError> {
    match client.customers.get(id, None).await {
        Ok(customer) => Ok(Some(customer)),
        Err(ApiError::NotFoundError { .. }) => Ok(None),
        Err(error) => {
            // status(): the HTTP status, if the API answered. detail(): its reason.
            eprintln!(
                "{:?} {:?} timeout={}",
                error.status(),
                error.detail(),
                error.is_timeout()
            );
            Err(error)
        }
    }
}
```

An error prints the API's reason: the body's `detail`, else its `title`, else its `message`, else the HTTP status text. For the body format and the error codes, see [Errors](/docs/api-reference/v2/errors).

***

## Idempotency and retries

Every request other than a GET carries an `Idempotency-Key` header. The SDK makes a UUID v4 key for each call and sends the same key on every retry of that call, so the API acts once. See [Idempotency](/docs/api-reference/v2/idempotency) for how the API treats keys. To make a call safe to repeat later, for example after your process stops, set your own key:

```rust theme={null}
use alguna::prelude::*;

async fn create_once(client: &Alguna, request: &CreateCustomerRequest) -> Result<(), ApiError> {
    let options = RequestOptions::new().idempotency_key("create-customer-acme");
    client.customers.create(request, Some(options)).await?;
    Ok(())
}
```

`events.ingest`, `credits.check` and `credits.track` are different. The API identifies their work by the request body, so the SDK makes no key for them and still retries them. The API removes duplicate events by `unique_id`. A key that you set is sent on every attempt. `credits.check` with `reserve` needs your key, because the key identifies the hold.

By default the SDK retries a call 2 times (3 attempts in total) after:

* a network error: the connection is refused, reset or closed before the response, or the attempt times out;
* a 408, 429, 502, 503 or 504 response;
* a 409 response with `Retry-After`. The API sends it while a request with the same idempotency key is still running. Any other 409 is a conflict, and the SDK does not retry it.

A retry waits 1 second, then 2 seconds, each ±10%. If the response has `Retry-After` (seconds or an HTTP date), the retry waits that long, up to 60 seconds. Set `max_retries` on the builder or on `RequestOptions`. 0 turns retries off.

To stop a call, drop its future, for example with `tokio::time::timeout`. A dropped call makes no more attempts.

***

## Configuration

```rust theme={null}
use alguna::ApiClientBuilder;
use std::time::Duration;

let client = ApiClientBuilder::new("https://api.sandbox.alguna.io") // default: https://api.alguna.io
    .api_key("<key_id>.<key_secret>")  // default: the ALGUNA_API_KEY environment variable
    .api_version("2026-04-01")         // default: the version this release targets
    .timeout(Duration::from_secs(30))  // each attempt; default: 60 seconds
    .max_retries(4)                    // default: 2
    .build()?;
```

You can also pass a `ClientConfig` to `Alguna::new`. Other builder methods are `custom_header`, `custom_headers`, `user_agent` and `reqwest_client`. A custom `reqwest_client` keeps its own timeout and transport settings.

Pass `api_version` to pin the version your integration is written against. Without it, the SDK sends the version it was generated from, so upgrading the crate can move you to a newer API version. See [Versioning](/docs/api-reference/v2/overview#versioning).

***

## FAQ

**Can I use the SDK in a browser or client app?**
No. It authenticates with your secret API key. For browser purchase flows, create a checkout session on your server and mount it with [Embedded Checkout](/docs/hosted/embedded-checkout).

**Does the SDK cover every endpoint?**
Yes. The client is generated from the versioned OpenAPI spec, so every operation in the [API reference](/docs/api-reference/v2/overview) has a method.

**Are there types for webhook payloads?**
Yes. Each event `type` has a payload type in the crate, such as `WebhookPaymentCreatedPayload` for `payment.created`. The SDK does not verify signatures. Before you trust a payload, verify its `svix-id`, `svix-timestamp` and `svix-signature` headers with the [`svix`](https://docs.rs/svix) crate, as [Verifying signatures](/docs/api-reference/v2/webhooks#verifying-signatures) shows.

**How do I point it at the sandbox?**
Use `ApiClientBuilder::new("https://api.sandbox.alguna.io")` with a sandbox API key. Sandbox and production are separate environments with separate keys. See [Base URLs](/docs/api-reference/v2/overview#base-urls).

**Which version am I on, and how does it relate to the API version?**
They are different. The crate version is the release of `alguna` on crates.io. The API version is the date sent as `Alguna-Version`, and it alone decides request and response shapes. When you pass `api_version`, upgrading the crate does not change the API version. Change it on purpose, as its own change, following [Versioning](/docs/api-reference/v2/overview#versioning).

**Is there an SDK for another language?**
Yes. There are [TypeScript](/docs/api-reference/v2/sdks/typescript), [Python](/docs/api-reference/v2/sdks/python) and [Go](/docs/api-reference/v2/sdks/go) SDKs and an [`alguna` CLI](/docs/api-reference/v2/sdks/cli). See [SDKs and CLI](/docs/api-reference/v2/sdks/overview).


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