# zammad-api
Async, idiomatic Rust client for the [Zammad](https://zammad.com) REST API, written by [SiSol PRO](https://www.sisol-pro.fr), a French company that has its expertise in managing "communautés d'autoconsommation".
- Builder-style `Client` with token, bearer or basic auth
- Strongly typed models for tickets, articles, users, organizations, groups, tags, states and priorities
- Streaming pagination via `futures::Stream`
- Custom-field passthrough via `#[serde(flatten)] extra`
- Write-verification helper for setting custom fields (Zammad silently returns 200 when a field doesn't exist on the object - `set_field` turns that into a loud error)
- TLS opt-in: `rustls-tls` (default) or `native-tls`
## Quick start
```toml
[dependencies]
zammad-api = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
futures-util = "0.3"
```
```rust,no_run
use futures_util::TryStreamExt;
use zammad_api::{Auth, Client, models::NewArticle};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let zammad = Client::builder()
.url("https://help.example.com")
.auth(Auth::token(std::env::var("ZAMMAD_TOKEN")?))
.build()?;
// Stream every open ticket
let mut stream = zammad.tickets().search("state.name:open").stream();
while let Some(ticket) = stream.try_next().await? {
println!("#{} {}", ticket.id, ticket.title.as_deref().unwrap_or(""));
}
// Reply to a ticket
zammad
.articles()
.create(42)
.article(NewArticle::new("Thanks for the report.").article_type("note"))
.send()
.await?;
// Tag a ticket
zammad.tags().add(42, "important").await?;
Ok(())
}
```
## Authentication
```rust,no_run
use zammad_api::Auth;
Auth::token("YOUR_TOKEN"); // -> Authorization: Token token=...
Auth::bearer("YOUR_OAUTH_TOKEN"); // -> Authorization: Bearer ...
Auth::basic("agent@example.com", "pw"); // -> Authorization: Basic ...
```
## Custom fields
Every model exposes an `extra: serde_json::Map<String, Value>` field that captures any attribute Zammad returns that isn't part of the typed schema - perfect for the custom fields you've added in Zammad's Object Manager.
```rust,no_run
# use zammad_api::Client;
# async fn run(z: &Client) -> zammad_api::Result<()> {
let ticket = z.tickets().get(123).await?;
// Set + verify the value was persisted (Zammad silently swallows unknown fields)
z.tickets().set_field(123, "espocrm_link", "https://crm/Contact/abc").await?;
# Ok(()) }
```
## License
MIT