# vynco
[](https://crates.io/crates/vynco)
[](https://docs.rs/vynco)
[](https://github.com/VynCorp/vc-rust/actions/workflows/ci.yml)
[](LICENSE)
Rust SDK for the [VynCo](https://vynco.ch) Swiss Corporate Intelligence API. Access 780,000+ Swiss companies from the commercial register with historical timelines, change tracking, sanctions screening, AI-powered risk analysis, UBO resolution, network graphs, watchlists, webhooks, market-flow analytics, and bulk data exports.
## Installation
```toml
[dependencies]
vynco = "2.3"
```
For the synchronous (blocking) client:
```toml
[dependencies]
vynco = { version = "2.3", features = ["blocking"] }
```
## Quick Start
```rust
use vynco::{Client, CompanyListParams};
#[tokio::main]
async fn main() -> Result<(), vynco::VyncoError> {
let client = Client::builder("vc_live_your_api_key").build()?;
// List companies with filtering
let params = CompanyListParams {
search: Some("Novartis".into()),
canton: Some("BS".into()),
legal_form: Some("AG".into()),
sort_by: Some("shareCapital".into()),
sort_desc: Some(true),
..Default::default()
};
let resp = client.companies().list(¶ms).await?;
println!("Found {} companies", resp.data.total);
// Get a single company (full details with address, geo, FINMA status, etc.)
let company = client.companies().get("CHE-105.805.080").await?;
println!("{}: {:?} ({})", company.data.name, company.data.legal_form,
company.data.address_city.as_deref().unwrap_or("unknown"));
// Get full company view (persons, changes, relationships in one call)
let full = client.companies().get_full("CHE-105.805.080").await?;
println!("{} board members, {} recent changes",
full.data.persons.len(), full.data.recent_changes.len());
// Sanctions screening
let screening = client.screening().screen(&vynco::ScreeningRequest {
name: "Suspicious Corp".into(), uid: None, sources: None,
}).await?;
println!("Risk: {} ({} hits)", screening.data.risk_level, screening.data.hit_count);
// AI risk score
let risk = client.ai().risk_score(&vynco::RiskScoreRequest {
uid: "CHE-105.805.080".into(),
}).await?;
println!("Risk score: {}/100 ({})", risk.data.overall_score, risk.data.risk_level);
// Credit balance
let credits = client.credits().balance().await?;
println!("Credits remaining: {}", credits.data.balance);
Ok(())
}
```
### Blocking Client
```rust
use vynco::blocking::Client;
fn main() -> Result<(), vynco::VyncoError> {
let client = Client::builder("vc_live_your_api_key")
.build()?;
let count = client.companies().count()?;
println!("Companies: {}", count.data.count);
Ok(())
}
```
## API Coverage
20 resource modules covering 100+ endpoints:
| `health()` | `check` |
| `companies()` | `list`, `get`, `get_full`, `count`, `events`, `statistics`, `compare`, `structure`, `acquisitions`, `news`, `reports`, `relationships`, `hierarchy`, `classification`, `fingerprint`, `nearby`, `timeline`, `timeline_summary`, `similar`, `ubo`, `media`, `media_analyze`, `notes`, `create_note`, `update_note`, `delete_note`, `tags`, `create_tag`, `delete_tag`, `all_tags`, `export_csv` |
| `auditors()` | `history`, `tenures` |
| `dashboard()` | `get` |
| `screening()` | `screen`, `batch` |
| `watchlists()` | `list`, `create`, `delete`, `companies`, `add_companies`, `remove_company`, `events` |
| `webhooks()` | `list`, `create`, `update`, `delete`, `test`, `deliveries` |
| `exports()` | `create`, `get`, `download` |
| `ai()` | `dossier`, `search`, `risk_score`, `risk_score_batch` |
| `api_keys()` | `list`, `create`, `revoke` |
| `credits()` | `balance`, `usage`, `history` |
| `billing()` | `create_checkout`, `create_portal` |
| `teams()` | `me`, `create`, `members`, `invite_member`, `update_member_role`, `remove_member`, `billing_summary`, `join` |
| `changes()` | `list`, `by_company`, `statistics` |
| `persons()` | `board_members`, `board_members_paged`, `search`, `get`, `network` |
| `analytics()` | `cantons`, `auditors`, `cluster`, `anomalies`, `rfm_segments`, `cohorts`, `candidates`, `flows`, `migrations`, `benchmark` |
| `dossiers()` | `create`, `list`, `get`, `delete`, `generate` |
| `graph()` | `get`, `export`, `analyze` |
| `alerts()` | `list`, `create`, `delete` |
| `ownership()` | `trace` |
### New in v2.3
- **Historical timeline** — `companies().timeline(uid, ¶ms)` and AI narrative via `timeline_summary(...)`
- **Similar companies** — `companies().similar(uid, limit)` scored on industry, canton, capital, legal form, auditor tier
- **UBO resolution** — `companies().ubo(uid)` walks the ownership chain and identifies natural persons
- **Ownership trace** — `ownership().trace(uid, &req)` exposes the full chain with circular-ownership detection
- **Media with sentiment** — `companies().media(uid, ¶ms)` filtered by positive/neutral/negative
- **Batch operations** — `screening().batch(&req)` (up to 100 UIDs) and `ai().risk_score_batch(&req)` (up to 50 UIDs)
- **Market analytics** — `analytics().flows(¶ms)`, `analytics().migrations(since)`, `analytics().benchmark(¶ms)`
- **Person network** — `persons().network(id)` for person-centric investigations with co-directors
- **Saved alerts** — persistent saved queries with optional webhook delivery
- **Pagination on board_members** — new `board_members_paged(uid, ¶ms)` (max 500)
- **Typed hierarchy** — `HierarchyResponse` now uses `HierarchyEntity` (was `serde_json::Value`)
- **Enriched watchlists** — `WatchlistCompaniesResponse.companies` includes name/status/canton
- **`export_csv`** — new canonical name (`export_excel` kept as deprecated alias)
## Response Metadata
Every response includes header metadata for credit tracking and rate limiting:
```rust
let resp = client.companies().get("CHE-105.805.080").await?;
println!("Request ID: {:?}", resp.meta.request_id); // X-Request-Id
println!("Credits used: {:?}", resp.meta.credits_used); // X-Credits-Used
println!("Credits remaining: {:?}", resp.meta.credits_remaining); // X-Credits-Remaining
println!("Rate limit: {:?}", resp.meta.rate_limit_limit); // X-Rate-Limit-Limit
println!("Rate limit remaining: {:?}", resp.meta.rate_limit_remaining); // X-RateLimit-Remaining
println!("Rate limit reset: {:?}", resp.meta.rate_limit_reset); // X-RateLimit-Reset
println!("Data source: {:?}", resp.meta.data_source); // X-Data-Source
```
## Example CLI
A full CLI example is included to demonstrate real-world SDK usage:
```bash
export VYNCO_API_KEY="vc_live_your_api_key"
cargo run --example vynco_cli -- health # API health check
cargo run --example vynco_cli -- companies --canton ZH --search "Novartis" # List with filters
cargo run --example vynco_cli -- company CHE-105.805.649 # Lookup by UID
cargo run --example vynco_cli -- count # Count companies
cargo run --example vynco_cli -- events CHE-105.805.649 --limit 10 # Company events
cargo run --example vynco_cli -- screen "Test Corp" # Sanctions screening
cargo run --example vynco_cli -- dashboard # Admin dashboard
cargo run --example vynco_cli -- auditors --min-years 10 --canton ZH # Long-tenure auditors
cargo run --example vynco_cli -- risk CHE-105.805.649 # AI risk score
cargo run --example vynco_cli -- fingerprint CHE-105.805.649 # Company data fingerprint
cargo run --example vynco_cli -- graph CHE-105.805.649 # Network graph
cargo run --example vynco_cli -- dossier CHE-105.805.649 # Generate AI dossier
cargo run --example vynco_cli -- compare CHE-105.805.649 CHE-109.340.740 # Compare companies
cargo run --example vynco_cli -- credits # Credit balance
cargo run --example vynco_cli -- team # Team info
cargo run --example vynco_cli -- changes --page 1 --page-size 10 # Recent SOGC changes
cargo run --example vynco_cli -- board-members CHE-105.805.649 # Board members
```
See [`examples/vynco_cli.rs`](examples/vynco_cli.rs) for the full source.
## Configuration
```rust
use std::time::Duration;
let client = Client::builder("vc_live_your_api_key")
.base_url("https://vynco.ch/api") // default
.timeout(Duration::from_secs(60)) // default: 30s
.max_retries(3) // default: 2
.build()?;
```
The client automatically retries on HTTP 429 (rate limited) and 5xx (server error) with
exponential backoff (500ms x 2^attempt). It respects the `Retry-After` header when present.
## Error Handling
All API errors are mapped to typed variants:
```rust
use vynco::VyncoError;
match client.companies().get("CHE-000.000.000").await {
Ok(resp) => println!("{}", resp.data.name),
Err(VyncoError::Authentication(_)) => println!("Invalid API key"),
Err(VyncoError::InsufficientCredits(_)) => println!("Top up credits"),
Err(VyncoError::Forbidden(_)) => println!("Insufficient permissions"),
Err(VyncoError::NotFound(body)) => println!("Not found: {:?}", body.detail),
Err(VyncoError::Validation(body)) => println!("Bad request: {:?}", body.detail),
Err(VyncoError::Conflict(_)) => println!("Resource conflict"),
Err(VyncoError::RateLimit(_)) => println!("Rate limited, retry later"),
Err(VyncoError::Server(_)) => println!("Server error"),
Err(e) => eprintln!("Error: {e}"),
}
```
Error bodies follow [RFC 7807 Problem Details](https://tools.ietf.org/html/rfc7807) with
`error_type`, `title`, `status`, `detail`, and `instance` fields.
## Feature Flags
| `rustls-tls` | Yes | Use rustls for TLS (no OpenSSL dependency) |
| `native-tls` | No | Use the platform's native TLS stack |
| `blocking` | No | Enable the synchronous blocking client |
## Minimum Supported Rust Version
Rust 1.83 or later.
## License
Apache-2.0