# assinafy — Rust SDK
[](https://crates.io/crates/assinafy)
[](https://docs.rs/assinafy)
Async, idiomatic Rust client for the [Assinafy](https://assinafy.com.br)
electronic-signature API.
The SDK is a 1:1 mapping of the public REST surface documented at
<https://api.assinafy.com.br/v1/docs>:
| Authentication | [`Client::auth_api`] |
| API keys | [`Client::api_keys`] |
| Signers | [`Client::signers`] |
| Signer (self) | [`Client::signer_self`] |
| Documents | [`Client::documents`] |
| Assignments | [`Client::assignments`] |
| Tags | [`Client::tags`] |
| Fields | [`Client::fields`] |
| Templates | [`Client::templates`] |
| Webhooks | [`Client::webhooks`] |
| Activities | [`Client::activities`] |
| Public endpoints | [`Client::public`] |
[`Client::auth_api`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.auth_api
[`Client::api_keys`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.api_keys
[`Client::signers`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.signers
[`Client::signer_self`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.signer_self
[`Client::documents`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.documents
[`Client::assignments`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.assignments
[`Client::tags`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.tags
[`Client::fields`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.fields
[`Client::templates`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.templates
[`Client::webhooks`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.webhooks
[`Client::activities`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.activities
[`Client::public`]: https://docs.rs/assinafy/latest/assinafy/struct.Client.html#method.public
## Install
Requires Rust 1.86 or newer.
```toml
[dependencies]
assinafy = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```
## Quick start
```rust
use assinafy::Client;
# async fn run() -> assinafy::Result<()> {
let client = Client::builder()
.api_key(std::env::var("ASSINAFY_API_KEY").unwrap())
.sandbox() // omit for production
.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
```rust
use assinafy::{Auth, Client};
// Server-to-server via API key (default for most users).
let c1 = Client::builder().api_key("...").build().unwrap();
// User-token flow.
let c2 = Client::builder()
.bearer("eyJhbGciOi...")
.build()
.unwrap();
// Query-parameter access-token flow, when required by an integration.
let c2_query = Client::builder()
.access_token("eyJhbGciOi...")
.build()
.unwrap();
// Signer-facing endpoints use the URL access code.
let c3 = c1.with_auth(Auth::AccessCode("signer-token".into()));
```
## Common flows
### Upload a document
```rust
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
```rust
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?;
// Clear expiration by sending the documented JSON null value.
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
```rust
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
```rust
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:
```rust
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:
```rust
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>`](https://docs.rs/assinafy/latest/assinafy/struct.Page.html)
containing `data` and `meta` (extracted from the `X-Pagination-*` response
headers):
```rust
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>`](https://docs.rs/assinafy/latest/assinafy/enum.Error.html).
API errors retain the HTTP status, server message and raw error payload:
```rust
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.
[rustls]: https://docs.rs/rustls
## Running the integration tests
```bash
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`](AUDIT.md) for the endpoint coverage and verification audit.
## License
Dual-licensed under MIT or Apache-2.0 at your option.