rust-ynab 0.4.0

A Rust client for the YNAB API
Documentation

rust-ynab

A Rust client for the YNAB API. Supports full access to all published YNAB API endpoints. Requires a YNAB account and a Personal Access Token.

Installation

[dependencies]
rust-ynab = "0.4.0"

Usage

Authentication

All API access requires a Personal Access Token. Pass it to Client::new:

let client = Client::new(&std::env::var("YNAB_TOKEN")?)?;

Quick Start

use rust_ynab::{Client, PlanId};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new(&std::env::var("YNAB_TOKEN")?)?;

    let plans = client.get_plans().include_accounts().send().await?;
    for plan in plans {
        println!("{}", plan.name);
        for acct in &plan.accounts {
            println!("   {}", acct.name);
        }
    }

    Ok(())
}

Builder Pattern

Methods that support optional parameters use a builder. Call the factory method on the client, chain any options, then call .send():

// fetch only changes since the last sync
let (transactions, server_knowledge) = client
    .get_transactions(PlanId::LastUsed)
    .with_server_knowledge(last_known)
    .since_date(NaiveDate::from_ymd_opt(2024, 1, 1).unwrap())
    .send()
    .await?;

// include sub-resources inline
let plans = client.get_plans().include_accounts().send().await?;

Rate Limiting

The YNAB API allows 200 requests per hour. with_rate_limiter enables a token bucket limiter that automatically spaces requests to stay within that limit:

let client = Client::new(&std::env::var("YNAB_TOKEN")?)?.with_rate_limiter(200, Some(10))?;

The first argument is the request budget per hour; the second is the optional burst size — the number of requests that can be made immediately before throttling begins. To keep total consumption within YNAB's limit, the sustained rate is reduced by the burst size: with_rate_limiter(200, Some(10)) allows 10 immediate requests, then throttles to 190 per hour. Calls block until a token is available rather than returning an error, so no retry logic is needed on the caller's side.

Rate limiting is opt-in. Omit with_rate_limiter for scripts or one-off tools where request volume is not a concern.

Timeout

The default request timeout is determined by reqwest. Use with_timeout to override it:

use std::time::Duration;

let client = Client::new(&std::env::var("YNAB_TOKEN")?)?.with_timeout(Duration::from_secs(30))?;

Both with_timeout and with_rate_limiter return the client, so they can be chained:

let client = Client::new(&std::env::var("YNAB_TOKEN")?)?
    .with_rate_limiter(200, Some(10))?
    .with_timeout(Duration::from_secs(30))?;

Error Handling

Errors from the API are returned as variants of Error and can be matched directly:

match client.get_plan(PlanId::LastUsed).send().await {
    Err(Error::NotFound(e)) => eprintln!("plan not found: {}", e),
    Err(Error::Unauthorized(e)) => eprintln!("check your token: {}", e),
    Err(e) => return Err(Box::new(e)),
    Ok((plan, _)) => { /* ... */ }
}

Available error variants: BadRequest, Unauthorized, Forbidden, NotFound, Conflict, RateLimited, InternalServerError, ServiceUnavailable.

Examples

API Coverage

Plans

Method Endpoint
get_plans GET /plans
get_plan GET /plans/{plan_id}
get_plan with PlanId::LastUsed GET /plans/last-used
get_plan_settings GET /plans/{plan_id}/settings

Accounts

Method Endpoint
get_accounts GET /plans/{plan_id}/accounts
get_account GET /plans/{plan_id}/accounts/{account_id}
create_account POST /plans/{plan_id}/accounts

Categories

Method Endpoint
get_categories GET /plans/{plan_id}/categories
get_category GET /plans/{plan_id}/categories/{category_id}
get_category_for_month GET /plans/{plan_id}/months/{month}/categories/{category_id}
create_category POST /plans/{plan_id}/categories
create_category_group POST /plans/{plan_id}/category_groups
update_category PATCH /plans/{plan_id}/categories/{category_id}
update_category_for_month PATCH /plans/{plan_id}/months/{month}/categories/{category_id}
update_category_group PATCH /plans/{plan_id}/category_groups/{category_group_id}

Months

Method Endpoint
get_months GET /plans/{plan_id}/months
get_month GET /plans/{plan_id}/months/{month}

Payees

Method Endpoint
get_payees GET /plans/{plan_id}/payees
get_payee GET /plans/{plan_id}/payees/{payee_id}
get_payee_locations GET /plans/{plan_id}/payee_locations
get_payee_location GET /plans/{plan_id}/payee_locations/{payee_location_id}
get_payee_locations_by_payee GET /plans/{plan_id}/payees/{payee_id}/payee_locations
create_payee POST /plans/{plan_id}/payees
update_payee PATCH /plans/{plan_id}/payees/{payee_id}

Transactions

Method Endpoint
get_transactions GET /plans/{plan_id}/transactions
get_transaction GET /plans/{plan_id}/transactions/{transaction_id}
get_transactions_by_account GET /plans/{plan_id}/accounts/{account_id}/transactions
get_transactions_by_category GET /plans/{plan_id}/categories/{category_id}/transactions
get_transactions_by_payee GET /plans/{plan_id}/payees/{payee_id}/transactions
get_transactions_by_month GET /plans/{plan_id}/months/{month}/transactions
create_transaction POST /plans/{plan_id}/transactions
create_transactions POST /plans/{plan_id}/transactions
update_transaction PUT /plans/{plan_id}/transactions/{transaction_id}
update_transactions PATCH /plans/{plan_id}/transactions
delete_transaction DELETE /plans/{plan_id}/transactions/{transaction_id}
import_transactions POST /plans/{plan_id}/transactions/import

Scheduled Transactions

Method Endpoint
get_scheduled_transactions GET /plans/{plan_id}/scheduled_transactions
get_scheduled_transaction GET /plans/{plan_id}/scheduled_transactions/{scheduled_transaction_id}
create_scheduled_transaction POST /plans/{plan_id}/scheduled_transactions
update_scheduled_transaction PUT /plans/{plan_id}/scheduled_transactions/{scheduled_transaction_id}
delete_scheduled_transaction DELETE /plans/{plan_id}/scheduled_transactions/{scheduled_transaction_id}

Money Movements

Method Endpoint
get_money_movements GET /plans/{plan_id}/money_movements
get_money_movements_by_month GET /plans/{plan_id}/months/{month}/money_movements
get_money_movement_groups GET /plans/{plan_id}/money_movement_groups
get_money_movement_groups_by_month GET /plans/{plan_id}/months/{month}/money_movement_groups

User

Method Endpoint
get_user GET /user

† Returns server knowledge as a second return value for use with delta requests.

Test Coverage

Unit tests use wiremock to cover all endpoints (GET, POST, PATCH, PUT, DELETE), client configuration, error type dispatch, and auth header injection. Write operation tests verify the HTTP method and request body serialization.

cargo test

Integration tests exercise the live API against a real plan and require YNAB_TOKEN and YNAB_TEST_PLAN_ID environment variables. They are opt-in via a feature flag:

YNAB_TOKEN=... YNAB_TEST_PLAN_ID=... cargo test --features integration

License

MIT


Not affiliated with YNAB. YNAB API Terms of Service.