Strongly-typed Rust SDK for the Rootly API, generated from the OpenAPI spec using Progenitor.
- Full coverage — all 415+ API operations with builder pattern
- Strongly typed — every endpoint, parameter, and response is a Rust type
- Async/await — powered by reqwest and tokio
- JSON:API compliant — handles
application/vnd.api+jsoncontent negotiation
Add to your Cargo.toml:
[dependencies]
rootly = "0.1"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }use rootly::RootlyClient;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let client = RootlyClient::from_token(std::env::var("ROOTLY_API_TOKEN")?);
let response = client.client().list_incidents().page_size(10).send().await?;
for item in &response.data {
println!("{}: {}", item.id, item.attributes.title);
}
Ok(())
}let response = client
.client()
.list_incidents()
.filter_status("started".to_string())
.page_number(1)
.page_size(10)
.include("causes,services".to_string())
.send()
.await?;
for item in &response.data {
println!(
"[{}] {} ({})",
item.id,
item.attributes.title,
item.attributes.status.as_deref().unwrap_or("unknown"),
);
}let response = client
.client()
.get_incident()
.id("abc123")
.include("sub_statuses,causes,subscribers".to_string())
.send()
.await?;
let incident = &response.data.attributes;
println!("Title: {}", incident.title);
println!("Summary: {}", incident.summary.as_deref().unwrap_or(""));use rootly::types::{NewIncident, NewIncidentData, NewIncidentDataAttributes, NewIncidentDataType};
let body = NewIncident::builder()
.data(
NewIncidentData::builder()
.type_(NewIncidentDataType::Incidents)
.attributes(
NewIncidentDataAttributes::builder()
.title("Service degradation".to_string())
.summary("Users experiencing elevated latency".to_string())
.severity_id("sev-1".to_string()),
),
)
.build()
.unwrap();
let response = client.client().create_incident().body(body).send().await?;use rootly::types::{UpdateIncident, UpdateIncidentData, UpdateIncidentDataAttributes, UpdateIncidentDataType};
let body = UpdateIncident::builder()
.data(
UpdateIncidentData::builder()
.type_(UpdateIncidentDataType::Incidents)
.attributes(
UpdateIncidentDataAttributes::builder()
.summary("Root cause identified — deploying fix".to_string()),
),
)
.build()
.unwrap();
let response = client
.client()
.update_incident()
.id("abc123")
.body(body)
.send()
.await?;let response = client.client().list_services().page_size(50).send().await?;
for svc in &response.data {
println!("{}: {}", svc.id, svc.attributes.name);
}use rootly::types::{NewAlert, NewAlertData, NewAlertDataAttributes, NewAlertDataType};
let body = NewAlert::builder()
.data(
NewAlertData::builder()
.type_(NewAlertDataType::Alerts)
.attributes(
NewAlertDataAttributes::builder()
.summary("High error rate on payments service".to_string())
.source("datadog".to_string()),
),
)
.build()
.unwrap();
let response = client
.client()
.create_alert()
.alerts_source_id("src-123")
.body(body)
.send()
.await?;let mut page = 1;
loop {
let response = client
.client()
.list_incidents()
.page_number(page)
.page_size(25)
.send()
.await?;
let data = response.into_inner();
if data.data.is_empty() {
break;
}
for item in &data.data {
println!("{}: {}", item.id, item.attributes.title);
}
page += 1;
}use rootly::ClientError;
match client.client().get_incident().id("nonexistent").send().await {
Ok(response) => println!("Found: {}", response.data.attributes.title),
Err(ClientError::ErrorResponse(resp)) => {
eprintln!("API error {}: {}", resp.status(), resp.status());
}
Err(e) => eprintln!("Request failed: {e}"),
}For handling rate limits (HTTP 429):
use rootly::retry::{with_backoff, RateLimitConfig};
let config = RateLimitConfig {
max_retries: 5,
initial_backoff_ms: 500,
};
let response = with_backoff(config, || {
client.client().list_incidents().page_size(100).send()
}).await?;use rootly::{RootlyClient, RootlyClientConfig};
// Custom base URL
let client = RootlyClient::new(RootlyClientConfig {
token: "your-token".into(),
base_url: "https://custom.rootly.com".into(),
});
// Quick setup with defaults
let client = RootlyClient::from_token("your-token");All generated types are available under rootly::types:
use rootly::types::{
Incident,
IncidentList,
NewIncident,
Service,
Alert,
Severity,
Team,
// ... 685 total types
};Full coverage of the Rootly API v1 (415+ operations):
| Resource | Operations |
|---|---|
| Incidents | list, get, create, update, delete |
| Services | list, get, create, update, delete |
| Alerts | list, get, create, update, resolve, snooze, escalate |
| Teams | list, get, create, update, delete |
| Severities | list, get, create, update, delete |
| Environments | list, get, create, update, delete |
| Workflows | list, get, create, update, delete |
| Playbooks | list, get, create, update, delete |
| Schedules | list, get, create, update, delete |
| Escalation Policies | list, get, create, update, delete |
| Dashboards | list, get, create, update, delete |
| Status Pages | list, get, create, update, delete |
| Custom Fields | list, get, create, update, delete |
| Catalogs | list, get, create, update, delete |
| ... and more |
make regenerate # Full clean + fetch spec + generate + build
make fetch-spec # Download latest OpenAPI spec from Rootly
make generate # Run Progenitor code generation
make build # Compile
make test # Run tests
make clippy # Lint
make fmt # Format
make clean # Remove build artifacts + generated codeThe xtask crate handles code generation with four spec sanitization passes:
- Sanitize enums — strips operator enums (
=,!=,>=,<=) that can't be Rust identifiers - Strip error responses — removes 4xx/5xx responses (Progenitor limitation)
- Strip nullable defaults — removes defaults from nullable fields (typify limitation)
- Simplify path params — flattens
anyOfpath params to plain strings
Then Progenitor generates a fully typed async client with builder-pattern methods from the preprocessed OpenAPI spec.
- Rust 1.88+
- tokio runtime
MIT — see LICENSE