assinafy — Rust SDK

Async, idiomatic Rust client for the Assinafy
electronic-signature API.
The SDK is a 1:1 mapping of the public REST surface documented at
https://api.assinafy.com.br/v1/docs:
Install
Requires Rust 1.86 or newer.
[dependencies]
assinafy = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
Quick start
use assinafy::Client;
# async fn run() -> assinafy::Result<()> {
let client = Client::builder()
.api_key(std::env::var("ASSINAFY_API_KEY").unwrap())
.sandbox() .build()?;
let signers = client
.signers("102d25a489f34a275d31a16045fd")
.list()
.per_page(50)
.send()
.await?;
for s in &signers.data {
println!("{} <{:?}>", s.full_name, s.email);
}
# Ok(()) }
Authentication
use assinafy::{Auth, Client};
let c1 = Client::builder().api_key("...").build().unwrap();
let c2 = Client::builder()
.bearer("eyJhbGciOi...")
.build()
.unwrap();
let c2_query = Client::builder()
.access_token("eyJhbGciOi...")
.build()
.unwrap();
let c3 = c1.with_auth(Auth::AccessCode("signer-token".into()));
Common flows
Upload a document
use assinafy::Client;
use assinafy::resources::UploadDocumentRequest;
# async fn run() -> assinafy::Result<()> {
let client = Client::from_api_key("k")?;
let upload = UploadDocumentRequest::from_path("./contract.pdf").await?;
let doc = client.documents().upload("acc_123", upload).await?;
println!("uploaded {} ({})", doc.name, doc.id);
# Ok(()) }
Request signatures
use assinafy::Client;
use assinafy::models::AssignmentMethod;
use assinafy::resources::CreateAssignmentBody;
# async fn run() -> assinafy::Result<()> {
let client = Client::from_api_key("k")?;
let body = CreateAssignmentBody::new(
AssignmentMethod::Virtual,
["sig_1", "sig_2"],
)
.message("Please sign by Friday.");
let assignment = client.assignments().create("doc_abc", &body).await?;
client
.assignments()
.reset_expiration("doc_abc", &assignment.id, None)
.await?;
for url in &assignment.signing_urls {
println!("signer {} -> {}", url.signer_id, url.url);
}
# Ok(()) }
Templates
use assinafy::Client;
use assinafy::models::VerificationMethod;
use assinafy::resources::{CreateDocumentFromTemplateBody, TemplateDocumentSigner};
# async fn run() -> assinafy::Result<()> {
let client = Client::from_api_key("k")?;
let estimate_body = CreateDocumentFromTemplateBody::default()
.signers(vec![
TemplateDocumentSigner::role("role_123")
.verification_method(VerificationMethod::Whatsapp),
]);
let _cost = client
.templates("acc_123")
.estimate_cost("tmpl_abc", &estimate_body)
.await?;
# Ok(()) }
Tags
use assinafy::Client;
use assinafy::resources::CreateTagBody;
# async fn run() -> assinafy::Result<()> {
let client = Client::from_api_key("k")?;
let tag = client
.tags("acc_123")
.create(&CreateTagBody::new("Contracts").color("3399ff"))
.await?;
println!("tag id: {}", tag.id);
# Ok(()) }
Document tag operations use tag names, matching the API reference:
use assinafy::Client;
# async fn run() -> assinafy::Result<()> {
let client = Client::from_api_key("k")?;
let tags = client.tags("acc_123");
tags.add_to_document("doc_abc", ["Contracts", "Urgent"]).await?;
tags.set_on_document("doc_abc", ["Signed"]).await?;
# Ok(()) }
Signer-facing flows
Signer-facing endpoints use Auth::AccessCode, which automatically adds the
signer-access-code query parameter to every request:
use assinafy::{Auth, Client};
use assinafy::resources::{ConfirmSignerDataBody, VerifyCodeBody};
# async fn run() -> assinafy::Result<()> {
let client = Client::from_api_key("k")?
.with_auth(Auth::AccessCode("signer-access-code".into()));
let signer = client.signer_self().me().await?;
client.signer_self().verify(&VerifyCodeBody::new("123456")).await?;
client
.signer_self()
.confirm_data(
"doc_abc",
&ConfirmSignerDataBody::new()
.email("signer@example.com")
.accepted_terms(true),
)
.await?;
println!("ready signer {}", signer.id);
# Ok(()) }
Pagination
Every paged endpoint returns a Page<T>
containing data and meta (extracted from the X-Pagination-* response
headers):
use assinafy::Client;
# async fn run() -> assinafy::Result<()> {
let client = Client::from_api_key("k")?;
let mut next = Some(1);
while let Some(page) = next {
let res = client.signers("acc_123").list().page(page).per_page(100).send().await?;
println!("page {page}: {} items", res.data.len());
next = res.next_page();
}
# Ok(()) }
Errors
All operations return Result<T, Error>.
API errors retain the HTTP status, server message and raw error payload:
use assinafy::{Client, Error};
# async fn run() -> assinafy::Result<()> {
let client = Client::from_api_key("k")?;
match client.signers("acc_123").get("missing").await {
Ok(_) => {}
Err(Error::Api(e)) if e.status == 404 => eprintln!("not found"),
Err(other) => return Err(other),
}
# Ok(()) }
Sandbox
Use [ClientBuilder::sandbox] to target the public sandbox at
https://sandbox.assinafy.com.br/v1.
Cargo features
rustls-tls (default) — TLS via rustls.
native-tls — TLS via the operating system's native stack.
Running the integration tests
export ASSINAFY_API_KEY=<sandbox-key>
export ASSINAFY_ACCOUNT_ID=<sandbox-account>
cargo test --test sandbox -- --ignored
The --ignored flag is required because the tests hit the live sandbox; CI
runs them on a schedule.
See AUDIT.md for the endpoint coverage and verification audit.
License
Dual-licensed under MIT or Apache-2.0 at your option.