workos 1.0.1

Official Rust SDK for the WorkOS API
Documentation

WorkOS Rust Library

The WorkOS Rust SDK provides async access to the WorkOS API from Rust applications. It uses tokio, ships with a default reqwest HTTP transport, and includes helpers for common WorkOS flows such as AuthKit, SSO, webhooks, sessions, JWKS, PKCE, and Vault local crypto.

Documentation

Installation

Requires Rust 1.88+ (edition 2024). The repository pins this via rust-toolchain.toml, so a fresh checkout will install the matching toolchain automatically when you run any cargo or ./script/ci command under rustup.

cargo add workos

By default, the crate enables reqwest with rustls-tls. You can switch TLS backends or provide a custom HTTP transport; see HTTP Transport.

Quick Start

use workos::{Client, organizations::ListOrganizationsParams};

#[tokio::main]
async fn main() -> Result<(), workos::Error> {
    let client = Client::builder()
        .api_key(std::env::var("WORKOS_API_KEY").unwrap())
        .client_id(std::env::var("WORKOS_CLIENT_ID").unwrap())
        .build();

    let page = client
        .organizations()
        .list_organizations(ListOrganizationsParams::default())
        .await?;

    for org in page.data {
        println!("{}: {}", org.id, org.name);
    }

    Ok(())
}

For an API-key-only client with default settings:

let client = workos::Client::new(std::env::var("WORKOS_API_KEY").unwrap());

Configuration

Client::builder() supports:

Method Description
.api_key(_) WorkOS secret key (sk_...), required for authenticated API calls.
.client_id(_) WorkOS Client ID, required for AuthKit, SSO, and JWKS helpers.
.base_url(_) Override the API host. Defaults to https://api.workos.com.
.timeout(_) Per-request timeout. Defaults to 30 seconds.
.max_retries(_) Retry budget for 429 and 5xx responses. Defaults to 3.
.user_agent(_) Override the User-Agent header.
.transport(_) Plug in a custom HttpTransport.

build() panics if the supplied API key or user-agent contains bytes that aren't valid in an HTTP header. Use .try_build() to surface those failures as Err(workos::Error::Builder(_)) instead — useful when the API key comes from untrusted input.

The client is cheap to clone and can be shared across handlers and tasks.

API Access

API resources are exposed as accessors on Client, for example:

client.organizations();
client.user_management();
client.sso();
client.webhooks();
client.audit_logs();

List endpoints return Page<T> values with data and list_metadata cursors. The crate also exports auto_paginate for stream-based iteration.

Every API call returns Result<_, workos::Error>. The error type includes API errors, transport failures, decode errors, configuration errors, and helper-specific failures. It also provides predicates such as is_unauthorized(), is_not_found(), is_rate_limited(), and is_server_error().

See the crate docs for the full resource list, request and response types, pagination details, and helper APIs.

Forward-compatible enums

Generated enums are #[non_exhaustive] and include an Unknown(String) variant for wire values the SDK doesn't recognize yet — WorkOS can add new enum values server-side without breaking deserialization for older SDK builds. Match defensively:

use workos::ConnectionType;

match connection.r#type {
    ConnectionType::GoogleOAuth => { /* ... */ }
    ConnectionType::Unknown(raw) => {
        log::warn!("unknown connection type: {raw}");
    }
    _ => { /* ... */ }
}

The original wire string is preserved through round-trips: serializing an Unknown("FooBar") re-emits "FooBar" verbatim. Each enum also implements Display, FromStr (infallible), and AsRef<str> for ergonomic conversions.

Per-Request Options

Each generated method has a *_with_options companion that takes a RequestOptions. Use it to pass an idempotency key, additional headers, or a per-request retry policy:

use workos::{RequestOptions, organizations::CreateOrganizationParams};

let opts = RequestOptions::new().idempotency_key("ik_create_acme_42");

let org = client
    .organizations()
    .create_organization_with_options(
        CreateOrganizationParams::new(workos::OrganizationInput {
            name: "Acme".into(),
            ..Default::default()
        }),
        Some(&opts),
    )
    .await?;

Replaying a mutating request with the same idempotency key is safe; WorkOS recognises the key on the server side.

Request strategies

For finer-grained control, attach a RequestStrategy to override the client's default retry behavior on a single call:

use workos::{RequestOptions, RequestStrategy};

// Send exactly once, regardless of `max_retries`:
let opts = RequestOptions::new().strategy(RequestStrategy::Once);

// Make a mutation idempotent and retry-eligible (the key is also sent
// as the `Idempotency-Key` header):
let opts = RequestOptions::new()
    .strategy(RequestStrategy::Idempotent("ik_42".into()));

// Custom retry budget with jitter:
let opts = RequestOptions::new().strategy(RequestStrategy::ExponentialBackoff {
    max_attempts: 5,
    jitter: true,
});

Variants: Once, Idempotent(key), Retry { max_attempts }, ExponentialBackoff { max_attempts, jitter }.

Errors

API errors carry structured metadata. Always log request_id() when reporting bugs to WorkOS:

match client.organizations().get_organization("org_missing").await {
    Ok(org) => println!("{org:?}"),
    Err(err) if err.is_not_found() => println!("not found"),
    Err(err) => {
        eprintln!(
            "API error {} (code={:?}, request_id={:?}): {}",
            err.status().unwrap_or(0),
            err.code(),
            err.request_id(),
            err.api().map(|a| a.message.as_str()).unwrap_or(""),
        );
        if let Some(after) = err.retry_after() {
            eprintln!("retry after {after:?}");
        }
    }
}

err.api() returns the full ApiError with the raw response headers and body for advanced debugging.

Sensitive Fields

Fields that hold credentials or tokens — password, client_secret, access_token, refresh_token, token, secret, etc. — are typed as workos::SecretString. Their Debug representation prints "<redacted>", so secrets don't leak through logs, panic messages, or error reports. Read the underlying value with .expose() when you genuinely need it:

let token: &str = session.access_token.expose();

SecretString serializes transparently as a JSON string, so the wire format is unchanged. Constructors that accept a sensitive parameter take impl Into<SecretString> — passing a String or &str works without an explicit conversion.

Retries

The client retries 429 and 5xx responses (plus retryable transport errors) up to max_retries times — default 3 — with exponential backoff and equal-jitter. The Retry-After header is honored when present and supersedes the computed backoff.

To preserve at-most-once semantics for state-changing calls, only safe HTTP methods (GET/HEAD/OPTIONS) and requests carrying an Idempotency-Key are auto-retried. POST/PUT/PATCH/DELETE without an idempotency key are sent exactly once.

// Disable retries entirely for this client:
let client = workos::Client::builder()
    .api_key(std::env::var("WORKOS_API_KEY").unwrap())
    .max_retries(0)
    .build();

Pair mutations with an idempotency key (or RequestStrategy::Idempotent) so a redelivered request is processed exactly once on the server.

Auto-Paging

Every list endpoint generates a *_auto_paging companion that returns a futures_util::Stream, advancing the after cursor under the hood:

use futures_util::TryStreamExt;
use workos::organizations::ListOrganizationsParams;

let all: Vec<workos::Organization> = client
    .organizations()
    .list_organizations_auto_paging(ListOrganizationsParams::default())
    .try_collect()
    .await?;

For custom paginated flows the crate also re-exports the lower-level auto_paginate(fetch) helper, which drives any (after) -> Result<Page<T>, _> closure to exhaustion.

Helpers

The SDK includes hand-maintained helpers for:

  • AuthKit and SSO URL builders, PKCE flows, token exchange, logout, and device authorization.
  • Webhook signature verification.
  • Sealed session cookies.
  • JWKS fetching and URL construction.
  • Vault key-value operations and optional local AES-GCM encryption.
  • Public PKCE-only clients for browser or mobile flows that must not hold an API key.

HTTP Transport

The default HTTP transport is reqwest, gated behind the default reqwest feature. To use another client, share an existing request pipeline, or support environments such as WASM, disable default features and provide a workos::transport::HttpTransport implementation:

# Cargo.toml
workos = { version = "1", default-features = false }
let transport: workos::transport::SharedTransport = std::sync::Arc::new(MyTransport);

let client = workos::Client::builder()
    .api_key("sk_...")
    .transport(transport)
    .build();

Supported crate features:

Feature Default Description
reqwest yes Enables the bundled reqwest transport.
rustls-tls yes Uses rustls for TLS through reqwest.
native-tls no Uses the platform native TLS stack through reqwest.

More Information

License

MIT. See LICENSE.txt.