Skip to documentation content

Rust SDK

A strongly typed asynchronous crate with structured errors.

In this guide
  1. 1Understand the package contract
  2. 2Run or resume jobs
  3. 3Handle native errors
  4. 4Verify webhooks
In this guide
  1. 1Understand the package contract
  2. 2Run or resume jobs
  3. 3Handle native errors
  4. 4Verify webhooks

Package contract

The planned crate is nordic-devhouse and its crate name is nordic_devhouse. It is asynchronous, serde-native, and ships rustls TLS by default without unsafe code.

DistributionNamespace or importRuntime
nordic-devhousenordic_devhouseCurrent stable Rust with Tokio

Create and reuse the client

The builder validates configuration once. Run consumes a typed request by value, borrows the reusable client, and returns a generic JobResult<T> decoded through serde.

Rust
use nordic_devhouse::{Client, Error, OutputFormat, ScrapeRequest};

#[tokio::main]
async fn main() -> Result<(), Error> {
    let client = Client::builder()
        .api_key(std::env::var("NORDIC_DEVHOUSE_API_KEY")?)
        .max_retries(3)
        .build()?;

    let result = client.scrape().run(
        ScrapeRequest::new("https://example.com/products")
            .format(OutputFormat::Json)
            .idempotency_key("catalog-2026-08-11"),
    ).await?;

    println!("{} {}", result.job_id, result.records);
    Ok(())
}

Control a durable job

Start and wait are async methods that accept cancellation through future dropping and optional explicit deadlines. Dropping the future never implies cancellation of the remote job.

Rust
let job = client.scrape().start(
    ScrapeRequest::new("https://example.com/products")
        .format(OutputFormat::Json),
).await?;

let result = client.jobs()
    .wait(&job.id)
    .timeout(Duration::from_secs(600))
    .await?;

Public models

Names follow the language conventions, but every SDK preserves the same fields and job-state semantics.

ConceptContract
Client optionsAPI key, base URL, request timeout, retry budget, and optional test transport
Scrape requestURL, output formats, webhook URL, metadata, and idempotency key
JobStable ID, status, timestamps, progress, and warnings
Job resultJob ID, record count, typed data, metadata, and request ID
Webhook eventVerified event ID, type, timestamp, and typed data

Handle errors idiomatically

A non-exhaustive Error enum preserves typed recovery data and sources. Callers match only variants they can recover from and use the catch-all SDK variant for forward compatibility.

Rust
match client.scrape().run(request).await {
    Ok(result) => consume(result),
    Err(Error::RateLimited { retry_after, request_id, .. }) => {
        tracing::warn!(?retry_after, %request_id, "rate limited");
    }
    Err(Error::Validation { issues, .. }) => reject(issues),
    Err(error) => {
        tracing::error!(request_id = error.request_id(), %error);
        return Err(error);
    }
}

Verify the raw webhook body

Verification uses the exact request bytes, validates HMAC-SHA256 and timestamp tolerance, and returns a typed event. Persist the verified event ID to prevent duplicate processing.

Rust
let event = client.webhooks().verify(
    raw_body,
    headers.get("Nordic-Signature"),
    &std::env::var("NORDIC_WEBHOOK_SECRET")?,
)?;

if event.kind == "scrape.completed" {
    import_result(&event.data.job_id).await?;
}

Runtime and lifecycle rules

  1. 1
    Use async Rust with Tokio and rustls defaults
  2. 2
    Mark public enums non-exhaustive for forward compatibility
  3. 3
    Avoid unsafe code in the crate and dependencies owned by the module
  4. 4
    Expose serde models and retain unknown fields when requested
  5. 5
    Use feature flags only for real adapters such as tracing and native-tls
Was this page helpful?