Docs · SDKs
Rust SDK
Install the Rust SDK, build a client, see every method with a Rust sample, and verify records offline.
Install
Rust
invoance on crates.io.
Rust 1.86 or later. Async on tokio and reqwest with rustls; no OpenSSL.
Cargo.toml
cargo add invoance
# or in Cargo.toml
[dependencies]
invoance = "0.2"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Client
api_key | The API key. Falls back to INVOANCE_API_KEY; build() returns a validation error when neither is set. |
|---|---|
base_url | API host. Falls back to INVOANCE_BASE_URL, then https://api.invoance.com. Trailing slashes are removed. |
api_version | Path prefix put before every request path. Default v1. |
| timeout | Per-request timeout as a Duration. Default 30 seconds; past it the error answers is_timeout(). |
idempotency_key | Default Idempotency-Key header for every mutating request; a per-call key wins. |
| header(name, value) / extra_headers | Headers merged into every request. |
from_config | InvoanceClient::from_config(ClientConfig { .. }) takes the same fields as a struct. |
| Retries | None. Each request is sent once; when is_timeout() or is_network() is true, retry it yourself with the same idempotency_key. |
Rust
use std::time::Duration;
use invoance::InvoanceClient;
#[tokio::main]
async fn main() -> Result<(), invoance::Error> {
// Reads INVOANCE_API_KEY and INVOANCE_BASE_URL from the environment.
let client = InvoanceClient::new()?;
// Or pass options.
let configured = InvoanceClient::builder()
.api_key("invoance_live_...")
.base_url("https://api.invoance.com")
.timeout(Duration::from_secs(60))
.build()?;
let _ = configured;
// GET /v1/me checks no scope, so any live key passes. Never errors.
let result = client.validate().await;
println!("{} {:?}", result.valid, result.reason);
Ok(())
}
Methods
Every endpoint with a Rust sample, by resource. Open a row for the sample; the link opens the endpoint card with its fields, response and errors.
EventsReference
POST/v1/events
Rust
use invoance::InvoanceClient;
use invoance::models::IngestEventParams;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let result = client
.events()
.ingest(IngestEventParams {
event_type: "policy.approval".into(),
event_time: Some("2026-09-22T08:14:07Z".into()),
payload: json!({
"policy_id": "pol_8472",
"approved_by": "risk_committee",
"decision": "approved",
})
.as_object()
.unwrap()
.clone(),
idempotency_key: Some("policy-approval-pol_8472".into()),
..Default::default()
})
.await?;
println!("{} {}", result.event_id, result.ingested_at);
Ok(())
}
GET/v1/events
Rust
use invoance::InvoanceClient;
use invoance::models::ListEventsParams;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let page = client
.events()
.list(ListEventsParams {
page: Some(1),
limit: Some(50),
event_type: Some("policy.approval".into()),
..Default::default()
})
.await?;
println!("{} {}", page.total, page.has_more);
for event in page.events {
println!("{} {} {}", event.event_id, event.ingested_at, event.payload_hash);
}
Ok(())
}
GET/v1/events/{event_id}
Rust
use invoance::InvoanceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let event = client
.events()
.get("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
.await?;
println!("{} {}", event.event_type, event.ingested_at);
println!("{} {} {}", event.payload_hash, event.event_hash, event.request_hash);
println!("{:?}", event.payload);
Ok(())
}
POST/v1/events/{event_id}/verify
Rust
use invoance::InvoanceClient;
use invoance::models::VerifyEventParams;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let result = client
.events()
.verify(
"7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4",
VerifyEventParams {
payload: json!({
"policy_id": "pol_8472",
"approved_by": "risk_committee",
"decision": "approved",
})
.as_object()
.cloned(),
..Default::default()
},
)
.await?;
println!("{} {:?}", result.match_result, result.matched_field);
println!("{} {} {}", result.anchored_hash, result.submitted_hash, result.anchored_at);
Ok(())
}
DocumentsReference
POST/v1/document/anchor
Rust
// cargo add invoance sha2 hex serde_json tokio --features tokio/full
use invoance::models::AnchorDocumentParams;
use invoance::InvoanceClient;
use serde_json::json;
use sha2::{Digest, Sha256};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = std::fs::read("./INV-2026-0917.pdf")?;
let document_hash = hex::encode(Sha256::digest(&file));
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let result = client
.documents()
.anchor(AnchorDocumentParams {
document_hash: document_hash.clone(),
document_ref: Some("INV-2026-0917.pdf".into()),
event_type: Some("invoice.issued".into()),
metadata: json!({
"invoice_number": "INV-2026-0917",
"amount": 5230,
"currency": "USD",
})
.as_object()
.cloned(),
idempotency_key: Some(format!("anchor-{document_hash}")),
..Default::default()
})
.await?;
println!("{} {}", result.event_id, result.status);
Ok(())
}
GET/v1/document
Rust
use invoance::models::ListDocumentsParams;
use invoance::InvoanceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let page = client
.documents()
.list(ListDocumentsParams {
limit: Some(25),
date_from: Some("2026-09-01T00:00:00Z".into()),
..Default::default()
})
.await?;
println!("{} {}", page.total, page.has_more);
for d in &page.documents {
println!("{} {} {}", d.event_id, d.document_ref, d.has_original);
}
Ok(())
}
GET/v1/document/{event_id}
Rust
use invoance::InvoanceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let doc = client
.documents()
.get("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
.await?;
println!("{} {} {}", doc.document_hash, doc.has_original, doc.created_at);
if let Some(org) = &doc.organization {
println!("{} {}", org.issuer_name, org.domain_verified);
}
Ok(())
}
GET/v1/document/{event_id}/original
Rust
use invoance::InvoanceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let data = client
.documents()
.get_original("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
.await?;
std::fs::write("./INV-2026-0917.pdf", &data)?;
println!("{}", data.len());
Ok(())
}
POST/v1/document/{event_id}/verify
Rust
// cargo add invoance sha2 hex tokio --features tokio/full
use invoance::models::VerifyDocumentParams;
use invoance::InvoanceClient;
use sha2::{Digest, Sha256};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let file = std::fs::read("./INV-2026-0917.pdf")?;
let document_hash = hex::encode(Sha256::digest(&file));
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let result = client
.documents()
.verify(
"7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4",
VerifyDocumentParams { document_hash },
)
.await?;
println!("{} {} {}", result.match_result, result.anchored_hash, result.anchored_at);
Ok(())
}
AI AttestationsReference
POST/v1/ai/attestations
Rust
use invoance::InvoanceClient;
use invoance::models::IngestAttestationParams;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let result = client
.attestations()
.ingest(IngestAttestationParams {
r#type: "output".into(),
input: "Summarize the termination clause in contract CT-8472.".into(),
output: "Either party may terminate with 30 days written notice. Early termination fees do not apply after month 12.".into(),
model_provider: "openai".into(),
model_name: "gpt-4.1".into(),
model_version: "2026-04-14".into(),
subject: json!({
"user_id": "u_4821",
"session_id": "sess_9f3a",
"department": "legal",
})
.as_object()
.cloned(),
idempotency_key: Some("ct-8472-summary-1".into()),
..Default::default()
})
.await?;
println!("{} {}", result.attestation_id, result.payload_hash);
Ok(())
}
GET/v1/ai/attestations
Rust
use invoance::InvoanceClient;
use invoance::models::ListAttestationsParams;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let page = client
.attestations()
.list(ListAttestationsParams {
limit: Some(50),
attestation_type: Some("output".into()),
model_provider: Some("openai".into()),
..Default::default()
})
.await?;
println!("{} {} {}", page.total, page.has_more, page.attestations.len());
Ok(())
}
GET/v1/ai/attestations/{attestation_id}
Rust
use invoance::InvoanceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let att = client
.attestations()
.get("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
.await?;
println!("{} {} {}", att.attestation_hash, att.signature_alg, att.public_key);
Ok(())
}
GET/v1/ai/attestations/{attestation_id}/raw
Rust
use invoance::InvoanceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let raw = client
.attestations()
.get_raw("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
.await?;
println!("{} {}", raw["type"], raw["context"]["model_name"]);
Ok(())
}
POST/v1/ai/attestations/{attestation_id}/verify
Rust
use invoance::InvoanceClient;
use invoance::models::VerifyAttestationParams;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let result = client
.attestations()
.verify(
"7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4",
VerifyAttestationParams {
content_hash: "c4efe15781214a84046ad7e0592977c634a5cf45f4c1e06160daf760a295a8df".into(),
},
)
.await?;
println!("{} {:?}", result.match_result, result.matched_field);
Ok(())
}
TracesReference
POST/v1/traces
Rust
use invoance::InvoanceClient;
use invoance::models::CreateTraceParams;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let trace = client
.traces()
.create(CreateTraceParams {
label: "Invoice batch 2026-09".into(),
metadata: Some(
json!({ "batch_id": "b_4471", "region": "eu-west" })
.as_object()
.unwrap()
.clone(),
),
})
.await?;
println!("{} {}", trace.trace_id, trace.status);
Ok(())
}
POST/v1/traces/{trace_id}/seal
Rust
use invoance::InvoanceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let sealed = client
.traces()
.seal("7c1e4b52-9a0f-4d2e-b6f3-2f8a61c0d9e4")
.await?;
println!("{} {}", sealed.status, sealed.message);
Ok(())
}
Audit LogsReference
POST/v1/audit/events
Rust
use invoance::InvoanceClient;
use invoance::models::IngestAuditEventParams;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let result = client
.audit()
.events
.ingest(IngestAuditEventParams {
organization_id: "org_8472".into(),
action: "user.signed_in".into(),
occurred_at: Some("2026-09-22T08:14:07Z".into()),
actor: json!({ "type": "user", "id": "u_4821", "name": "Ada Lovelace" })
.as_object()
.unwrap()
.clone(),
targets: Some(vec![json!({ "type": "workspace", "id": "ws_17" })]),
context: Some(
json!({ "location": "203.0.113.10", "user_agent": "Mozilla/5.0" })
.as_object()
.unwrap()
.clone(),
),
metadata: Some(json!({ "method": "sso", "mfa": true }).as_object().unwrap().clone()),
idempotency_key: Some("signin-u_4821-2026-09-22T08:14:07Z".into()),
})
.await?;
println!("{} {}", result["event_id"], result["ingested_at"]);
Ok(())
}
POST/v1/audit/orgs
Rust
use invoance::InvoanceClient;
use invoance::models::CreateAuditOrgParams;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let org = client
.audit()
.orgs
.create(CreateAuditOrgParams {
organization_id: "org_8472".into(),
name: Some("Acme Robotics".into()),
})
.await?;
println!("{} {}", org["id"], org["retention_days"]);
Ok(())
}
PlatformReference
GET/v1/me
Rust
use invoance::InvoanceClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Reads INVOANCE_API_KEY from the environment.
let client = InvoanceClient::new()?;
let me = client.me().await?;
println!("{}", me["organization"]["primary_domain"]);
println!("{}", me["limits"]["rate_limit_per_sec"]);
Ok(())
}
Verify offlineHow verification works
Two checks run without trusting the server: attestations().verify_signature fetches the record and checks its Ed25519 signature over signed_payload; invoance::verify_audit_event rebuilds the invoance.audit/1 bytes of an audit event (as serde_json::Value) and checks its signature. Pass Some(hex key) from GET /keys/{domain} to pin it instead of the key on the row.
Rust
use invoance::{verify_audit_event, InvoanceClient};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = InvoanceClient::new()?;
// AI attestation: Ed25519 over signed_payload, checked locally.
let sig = client
.attestations()
.verify_signature("a1d4f8c2-7b3e-4e9a-b5c6-0d2e8f4a7c19")
.await?;
println!("{} {:?}", sig.valid, sig.reason);
// Audit event: canonical bytes rebuilt locally, signature checked
// against a pinned key (the public_key from GET /keys/{domain}, base64url decoded to hex).
let pinned_hex_key = "d4443bd9d30ef4c2e0e5db03467e3c5c740358482c1a1e30cdb27a2e02a38176";
let event = client.audit().events.get("aevt_01J8F3KQ2R7VWX9YB4ND6MCZAH").await?;
let event_json = serde_json::to_value(&event)?;
let result = verify_audit_event(&event_json, Some(pinned_hex_key));
println!("{} {:?} {:?}", result.valid, result.reason, result.key_source); // Pinned
Ok(())
}