Unofficial Rust SDK for the Dropbox HTTP v2 API. Async-first on reqwest,
batteries-included: built-in OAuth refresh, retries on 429 / 5xx, and
streaming up- and downloads. Both async and sync are available on every
endpoint.
0.8.x — usable in real workloads (246 mocked tests + a live integration
suite); the public surface may still see breaking changes before 1.0. Not
affiliated with Dropbox. Another community SDK lives at
dropbox/dropbox-sdk-rust.
- Zero-config HTTP —
Client::new(token)and you're going. NoHttpClienttrait to implement. - OAuth refresh built in —
Client::with_refresh(...)plusclient.call(|token| ...)auto-refreshes when expired and replays once on a 401. - Automatic retries on 429 and 5xx with exponential backoff, baked into the request macro.
- Streaming helpers —
download_streamreturns afutures::Stream<Item = Bytes>;chunked_upload::upload_large_filelifts the 150 MiB single-request cap. - Sync and async on every Request — call
.call().awaitor.call_sync()from the same struct. No feature toggling. - Typed per-endpoint errors — downcast
anyhow::ErrortoTypedError<E>(e.g.TypedError<LookupError>) and match the variant. - Stone-spec naming preserved across the 11 namespaces — types map 1:1 to the Dropbox IDL.
[dependencies]
rusty_dropbox_sdk = "0.8"
tokio = { version = "1", features = ["full"] }MSRV: 1.75.
use rusty_dropbox_sdk::api;
use rusty_dropbox_sdk::api::Service;
use rusty_dropbox_sdk::Client;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let client = Client::new(std::env::var("DROPBOX_TOKEN")?);
let bearer = client.token();
let req = api::files::list_folder::ListFolderRequest {
access_token: &bearer,
payload: Some(api::files::ListFolderArgs {
path: String::new(),
recursive: Some(false),
limit: Some(50),
include_media_info: None,
include_deleted: None,
include_has_explicit_shared_members: None,
include_mounted_folders: None,
shared_link: None,
include_property_groups: None,
include_non_downloadable_files: None,
}),
};
let response = req.call().await?.expect("empty response");
for entry in response.payload.entries {
println!("{:?}", entry);
}
Ok(())
}Two ways to construct a client:
use rusty_dropbox_sdk::{Client, RefreshConfig};
// 1. Bring your own short-lived access token. No refresh — once it expires
// every request returns 401 until you build a new client.
let _client = Client::new("short-lived-access-token");
// 2. Auto-refreshing client. Pass the OAuth app's client_id, client_secret,
// and a long-lived refresh token (acquired via auth::exchange_code with
// offline=true). client.call(...) will refresh + replay on 401.
let _client = Client::with_refresh(
"current-access-token",
14_400, // expires_in (seconds), from the OAuth response
RefreshConfig {
client_id: std::env::var("DROPBOX_CLIENT_ID").unwrap(),
client_secret: std::env::var("DROPBOX_CLIENT_SECRET").unwrap(),
refresh_token: std::env::var("DROPBOX_REFRESH_TOKEN").unwrap(),
},
);Implemented and tested namespaces:
account— set profile photoauth— OAuth code exchange, refresh, token revokecheck—appanduserhealth probescontacts— manual contactsfile_properties— properties + templates (Stone-IDL naming)file_requests— create, get, list, count, deletefiles— all v2 endpoints (copy, move, delete, download, upload, upload_session/*, list_folder, search, tags, lock_file, paper, etc.)openid—userinfosharing— folders, file members, shared links, inviteesusers— account, current account, space usage, features
Out of scope today: team-admin endpoints (team* namespaces).
Runnable end-to-end programs live under examples/. Each
reads credentials from environment variables and silently skips when they
aren't set, so feel free to run any of them with no setup.
DROPBOX_TOKEN=<your-token> cargo run --example quickstart
DROPBOX_TOKEN=<your-token> cargo run --example download_stream -- /remote/big.zip ./big.zip
DROPBOX_TOKEN=<your-token> cargo run --example chunked_upload -- ./local.bin /remote/path
DROPBOX_TOKEN=<your-token> cargo run --example typed_errors
DROPBOX_ACCESS_TOKEN=... DROPBOX_CLIENT_ID=... DROPBOX_CLIENT_SECRET=... \
DROPBOX_REFRESH_TOKEN=... cargo run --example oauth_refreshA few inline recipes for the most-asked questions:
Streaming a large download to disk
use rusty_dropbox_sdk::helpers::download_stream::download_stream;
use futures::StreamExt;
use tokio::io::AsyncWriteExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (meta, mut stream) = download_stream("your_token", "/big.zip").await?;
println!("downloading {} ({} bytes)", meta.name, meta.size);
let mut out = tokio::fs::File::create("./big.zip").await?;
while let Some(chunk) = stream.next().await {
out.write_all(&chunk?).await?;
}
Ok(())
}Uploading a large file via upload_session/*
use rusty_dropbox_sdk::api::files::WriteMode;
use rusty_dropbox_sdk::helpers::chunked_upload::{upload_large_file, DEFAULT_CHUNK_SIZE};
use tokio::fs::File;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let file = File::open("./video.mp4").await?;
let metadata = upload_large_file(
"your_token",
"/videos/clip.mp4",
file,
DEFAULT_CHUNK_SIZE,
WriteMode::Add,
)
.await?;
println!("uploaded rev {} ({} bytes)", metadata.rev, metadata.size);
Ok(())
}Recovering typed errors
use rusty_dropbox_sdk::{api, TypedError};
use rusty_dropbox_sdk::api::files::LookupError;
use rusty_dropbox_sdk::api::Service;
# async fn run() -> anyhow::Result<()> {
let req = api::files::get_metadata::GetMetadataRequest {
access_token: "your_token",
payload: Some(api::files::GetMetadataArgs {
path: "/does-not-exist".to_string(),
include_media_info: None,
include_deleted: None,
include_has_explicit_shared_members: None,
include_property_groups: None,
}),
};
match req.call().await {
Ok(_) => println!("metadata fetched"),
Err(e) => {
if let Some(holder) = e.downcast_ref::<TypedError<LookupError>>() {
match holder.get() {
LookupError::NotFound => println!("that path isn't there"),
other => println!("other lookup error: {:?}", other),
}
} else {
eprintln!("non-typed error: {e}");
}
}
}
# Ok(())
# }Sync calls
Every request implements both call() and call_sync():
use rusty_dropbox_sdk::api;
use rusty_dropbox_sdk::api::Service;
fn main() -> anyhow::Result<()> {
let req = api::auth::token_revoke::TokenRevokeRequest {
access_token: "your_token",
payload: None,
};
let _ = Service::call_sync(&req)?;
Ok(())
}test-utils— pulls inmockitoand exposes the per-test ephemeral mock-server helpers used by the SDK's own test suite. Not needed for normal use; default builds skip it entirely.
cargo test --features test-utils --libIntegration tests against the live Dropbox API live in
tests/live_dropbox.rs and are env-gated — they
silently no-op without DROPBOX_TEST_TOKEN. Run them explicitly with:
DROPBOX_TEST_TOKEN=<your-token> cargo test --test live_dropbox -- --nocaptureIssues and PRs welcome. Please follow the standard GitHub flow.
GPL-3.0-only. See LICENSE.