Invoist for Rust
The official Rust client for the Invoist API. Send an invoice, receipt or payment confirmation and get clean, structured data back; read a household's bills; check that a webhook really came from Invoist.
- Async on reqwest and Tokio, with an optional blocking client.
- Types for every request and response, generated from the API's OpenAPI
document. Money is always an
i64in cents, never a float. - One error type, safe retries, and webhook verification built in.
Requires Rust 1.88 or newer.
Install
TLS uses rustls by default, so there is no OpenSSL to install. See Features for the alternatives.
A business workspace
Upload a document and wait for what Invoist reads from it:
use ;
async
Reading happens in the background. extract returns at once with the
extraction pending; wait_for_extraction polls every 2 seconds for up to 2
minutes, and returns the extraction whether it is done or failed (a failed
reading is an answer, with error_message saying why). Uploading the same
bytes twice returns the first document with duplicate: true and isn't billed
again.
The other business calls: extract_batch, list_documents, get_document,
get_document_file, list_review_queue, resolve_review_item,
list_categories, get_usage, get_stats, list_webhook_endpoints,
create_webhook_endpoint and delete_webhook_endpoint.
A household
A household's key (Invoist Personal) reads its bills:
use ;
async
Also on client.bills(): get, get_file, summary, stats, mark_paid
and mark_unpaid; and client.held_emails().list() for emails waiting for a
person to approve their sender. Months are invoist::Month
(Month::new(2026, 10)? or "2026-10".parse()?).
Configuration
use Duration;
let client = builder
.api_key
.base_url // the default
.timeout // per attempt; the default
.max_retries // the default
.build?;
# Ok::
Bring your own reqwest::Client (for a proxy, custom certificates, or tests)
with .http_client(...); invoist::reqwest re-exports the matching version.
A Client is cheap to clone and safe to share: build one and reuse it.
Errors
Every failure is an invoist::Error:
# async
status(): the HTTP status, or0when no response arrived.code(): a stable machine code such asinvalid_api_key,insufficient_scope,not_found,rate_limited,network_error,timeoutorunexpected_response. Branch on this, never on the message.message(),request_id()(quote it to support) andbody()(the raw response).is_auth_error()(401),is_permission_error()(403),is_not_found()(404),is_rate_limited()(429),is_timeout(),is_retryable().
Retries
A request that got no response, a 429, or a 5xx is retried up to twice. The
client waits what Retry-After asks for, otherwise 500ms, then 1s (each ±20%,
never more than 8s). A Retry-After above 60 seconds isn't waited for: the
call fails at once with the 429 so your program can decide. Any other 4xx is
never retried. Uploads retry safely: the API recognises the same bytes, and
ExtractOptions::idempotency_key covers a re-encoded file.
To cancel a call, drop its future, for instance with
tokio::time::timeout. A cancelled call is never retried.
Webhooks
Verify every delivery before trusting it. Pass the raw request body:
parsing the JSON and serialising it again changes the bytes, and the
signature no longer matches. With axum, take
the body as Bytes:
use ;
use ;
async
async
verify allows 5 minutes of clock difference either way;
webhooks::verify_with takes VerifyOptions to change that or to fix the
clock in tests, and webhooks::sign builds a valid header for your own
handler's tests. A failed check is an Error with status 400 and code
invalid_signature, signature_too_old or invalid_payload. The
X-Invoist-Delivery header (webhooks::DELIVERY_HEADER) stays the same when
a delivery is retried, so use it to skip repeats. The event types are
constants in webhooks::events.
Blocking
With the blocking feature, invoist::blocking::Client has the same methods
without async, for programs with no runtime:
let client = new?;
let summary = client.bills.summary?;
Don't use it from inside an async runtime; use the async client there.
Features
| Feature | Default | What it does |
|---|---|---|
rustls-tls |
yes | TLS with rustls and the platform's certificate verifier. No OpenSSL. |
native-tls |
no | TLS with the platform's library (OpenSSL on Linux) instead. |
http2 |
no | HTTP/2 when the server offers it. |
blocking |
no | invoist::blocking::Client. |
Links
- API documentation: https://invoi.st/docs
- Crate documentation: https://docs.rs/invoist
- Changes: CHANGELOG.md
- Security reports: SECURITY.md
License
MIT. See LICENSE.