Tango Rust SDK
Official, async-first Rust SDK for the Tango API — federal contracts, IDVs, entities, opportunities, grants, vehicles, and more, with dynamic response shaping so you fetch only the fields you need.
Pre-1.0 — v0.3.1. The resource surface now tracks the sibling SDKs endpoint for endpoint, but the public API may still shift before v1.0.0. Pin to a specific version if you depend on this in production.
The sibling SDKs are tango-python, tango-node and tango-go. This Rust port ships the full transport, error model, retry / rate-limit handling, webhook signing (in the separate tango-webhooks crate), and the resource-method surface. Same 0.x → 1.0 graduation logic as Go: the transport and types are stable; the surface stabilizes once it has been verified against the live API alongside the siblings.
Features
- Async-first — built on
tokio+reqwest. One runtime, cleanfutures::Stream-based pagination. - Dynamic response shaping — request exactly the fields you need via 34 built-in
SHAPE_*presets or a custom comma-separated field list. - Typed errors — single
Errorenum with rich payload variants (Auth,NotFound,Validation,RateLimit,Timeout,Api,Transport,Decode,Build). Programmatic dispatch viaerr.status()anderr.is_retryable(). - Smart retries — automatic backoff on 5xx / 408 / 429 / transport errors, honoring the server's
Retry-Afterheader. - Compile-time-checked client builder — via
bon. Missingapi_key? Won't compile. - Async pagination —
PageStream<T>implementsfutures::Stream. Yields one item at a time, fetches successive pages automatically. - Forward-compatible models — every typed model carries
#[serde(flatten)] extra: HashMap<String, Value>so a server-side schema addition surfaces inrecord.extra["new_field"]rather than being silently dropped. - Webhook signing — in the separate
makegov-tango-webhookscrate: zero transport deps, HMAC-SHA256 verification, constant-time viasubtle.
Installation
[]
= "0.3"
# Optional: webhook signing for receivers
= "0.3"
Crates publish under the makegov- prefix on crates.io; Rust imports stay short — use tango::Client; and use tango_webhooks::verify; — thanks to a [lib] name shim in each crate (same pattern as the aws-sdk-* family).
Requires Rust 1.80 or later.
Quick start
use Client;
use TryStreamExt;
async
Get a typed agency
get_agency returns the typed AgencyRecord; forward-compatible fields land in agency.extra.
# use Client;
# async
Get a fully-shaped entity
# use ;
# async
Authentication
Pass an API key via Client::builder().api_key(...), or set TANGO_API_KEY in the environment and call Client::from_env(). You can also override the base URL via TANGO_BASE_URL for staging environments, or via the builder:
# use Client;
# async
Core concepts
Dynamic response shaping
Every list and get endpoint accepts a shape parameter selecting which fields the API returns. The SDK ships 34 presets matching the Node, Python, and Go SDKs:
# use ;
# async
See docs/SHAPES.md for the full grammar and the available presets.
Pagination
List endpoints support both page-based and cursor-based pagination. Page<T> exposes the next URL plus the extracted cursor for keyset endpoints:
# use ;
# async
page.count is the total across every page. Past 1,000 matches the contract, IDV, opportunity and notice lists may report a query-planner estimate there instead of an exact count, and page.count_type says which you got: Some("exact"), Some("approximate"), or None when the endpoint does not say. It is read from that page's own response, so concurrent calls on one client never see each other's value.
page.meta is the response's meta object as the API sends it. On a list filtered by agency it carries the filter diagnostics: each entry of meta["resolved_filters"][<filter name>] that resolved says how its token matched in matched_by (Tango API 5.9.0+): key, code, name, alias or fuzzy. A fuzzy match is worth checking against the resolved name.
# use ;
# async
Or just iterate via PageStream<T>:
# use ;
# use TryStreamExt;
# async
Or drain the whole thing:
# use ;
# async
Rate limits
After each request, client.rate_limit_info() returns a snapshot of the rate-limit headers:
# use Client;
# async
The client also automatically retries 429s, honoring Retry-After. See docs/CLIENT.md for the full retry semantics.
Webhook verification
Webhook signing lives in the separate makegov-tango-webhooks crate so a receiver service doesn't have to pull in the full SDK:
use ;
The CRUD methods for managing webhook endpoints and alerts live on tango::Client:
list_webhook_endpoints, create_webhook_endpoint, update_webhook_endpoint, delete_webhook_endpoint, test_webhook_endpoint, list_webhook_alerts, create_webhook_alert, etc.
See docs/WEBHOOKS.md for the full surface, signature format, and middleware patterns.
Error handling
Every fallible call returns Result<T, tango::Error>. The Error enum carries rich payloads — match the variant to dispatch on the failure mode:
use ;
# async
err.is_retryable() reports the SDK's retry decision (it already retries internally; this is for callers building their own retry policies on top). err.status() returns the HTTP status when one is associated with the error.
API methods
The SDK exposes a method on Client for every public endpoint the sibling SDKs cover. The main resources are listed here; the full method-by-method reference lives in docs/API_REFERENCE.md.
| Resource | List | Get | Iterate |
|---|---|---|---|
| Agencies | list_agencies |
get_agency (typed: AgencyRecord) |
— |
| Contracts | list_contracts |
get_contract |
iterate_contracts |
| Entities | list_entities |
get_entity |
iterate_entities |
| IDVs | list_idvs |
get_idv |
iterate_idvs |
| Vehicles | list_vehicles |
get_vehicle |
iterate_vehicles |
| OTAs | list_otas |
get_ota |
iterate_otas |
| OTIDVs | list_otidvs |
get_otidv |
iterate_otidvs |
| Subawards | list_subawards |
get_subaward |
iterate_subawards |
| Opportunities | list_opportunities |
get_opportunity |
iterate_opportunities |
| Notices | list_notices |
get_notice |
iterate_notices |
| Forecasts | list_forecasts |
get_forecast |
iterate_forecasts |
| Grants | list_grants |
get_grant |
iterate_grants |
| Protests | list_protests |
get_protest (typed: ProtestRecord) |
iterate_protests |
| Contract appeals | list_contract_appeals |
get_contract_appeal (typed: ContractAppealRecord) |
iterate_contract_appeals |
| DIBBS (DLA) | list_dibbs_rfqs / list_dibbs_rfps / list_dibbs_awards |
get_dibbs_rfq / get_dibbs_rfp / get_dibbs_award |
iterate_dibbs_* |
| GSA eBuy | list_ebuy_requests |
get_ebuy_request |
iterate_ebuy_requests |
| Exclusions | list_exclusions |
get_exclusion |
iterate_exclusions |
| Federal Register | list_federal_register_documents |
get_federal_register_document |
iterate_federal_register_documents |
| SBIR / STTR | list_sbir_topics / list_sbir_solicitations |
get_sbir_topic / get_sbir_solicitation |
iterate_sbir_* |
| State & local (SLED) | list_sled_opportunities / list_sled_forecasts |
get_sled_opportunity / get_sled_forecast |
iterate_sled_* |
| Budget accounts | list_budget_accounts |
get_budget_account |
iterate_budget_accounts |
| IT Dashboard | list_itdashboard |
get_itdashboard |
iterate_itdashboard |
| NAICS / PSC | list_naics / list_psc |
get_naics / get_psc |
— |
| Webhooks (CRUD) | list_webhook_endpoints / list_webhook_alerts |
get_ / create_ / update_ / delete_ / test_ |
— |
Sub-resources and lookups: list_contract_subawards / _transactions, list_entity_contracts / _idvs / _otas / _otidvs / _subawards / _lcats / get_entity_metrics / get_entity_budget_flows, get_budget_account_quarters / _recipients, list_sled_opportunity_revisions / get_sled_coverage, get_ebuy_attachment_url / get_ebuy_access, list_idv_awards / _child_idvs / _transactions / _lcats, list_agency_awarding_contracts / _funding_contracts, list_vehicle_awardees / _orders, list_otidv_awards, list_gsa_elibrary_contracts, list_business_types, list_offices, list_departments, list_mas_sins, list_assistance_listings, list_lcats (dispatcher). Meta: resolve, validate, get_version, list_api_keys. list_opportunities with search also matches attachment text. Metrics dispatcher: list_metrics.
See docs/API_REFERENCE.md for full signatures, filter fields, and quirks.
Workspace layout
This is a Cargo workspace with two published crates:
.
├── crates/
│ ├── tango/ # main SDK
│ └── tango-webhooks/ # HMAC-SHA256 signing/verification (no transport deps)
├── docs/ # in-repo guides
├── justfile # task runner (test / fmt / lint / cover / release-check)
├── Cargo.toml # workspace manifest
└── README.md
Documentation
In-repo guides:
docs/ARCHITECTURE.md— crate layout, request lifecycle, design rationaledocs/CLIENT.md— builder options, env vars, retry semantics, error model, rate-limit observabilitydocs/API_REFERENCE.md— method-by-method reference for every public methoddocs/SHAPES.md— shape grammar, the 34 presets,flat/flat_lists, trade-offsdocs/WEBHOOKS.md— receiving deliveries, CRUD methods, troubleshooting
External:
- API docs (Rust): https://docs.rs/makegov-tango · https://docs.rs/makegov-tango-webhooks
- API reference: https://docs.makegov.com
- Sibling SDKs:
@makegov/tango-node·tango-python·tango-go
Development
Requirements
- Rust 1.80 or later.
License
MIT — see LICENSE.
Support
Open an issue at https://github.com/makegov/tango-rust/issues or email support@makegov.com.
Contributing
PRs welcome. See CONTRIBUTING.md. Run just ci before opening — fmt, clippy, and the full test suite must pass.