inndx/
GitHub

Rust SDK

Installation, configuration, and full reference for the Rust SDK.

The Rust SDK (inndx-sdk) is async and runtime agnostic. It handles MPP payment automatically for both per-request and session endpoints.

Installation

The crate is not published to crates.io. Add it directly from git:

[dependencies]
inndx-sdk = { git = "https://github.com/inndx-io/rust-sdk" }

Pin a specific tag or commit for reproducible builds:

inndx-sdk = { git = "https://github.com/inndx-io/rust-sdk", tag = "0.1.2" }
inndx-sdk = { git = "https://github.com/inndx-io/rust-sdk", rev = "<commit-sha>" }

Configuration

use inndx_sdk::{Config, InndxClient, PrivateKeySigner};

let signer: PrivateKeySigner = std::env::var("WALLET_PRIVATE_KEY")?.parse()?;

let client = InndxClient::new(
    Config::new().with_signer(signer),
)?;

Config::new() returns a builder. Available methods:

MethodDescription
.with_base_url(url)Base URL of the inndx API. Defaults to https://api.inndx.io.
.with_signer(signer)Wallet used to sign payments. Required for paid calls and sessions.
.with_rpc_url(url)RPC endpoint for the Tempo chain.
.with_header(key, value)Adds a header to every outgoing request.

BillingConfig can be constructed separately for more control:

MethodDescription
.with_max_deposit(amount)Default escrow cap per session in token base units.
.with_escrow_contract(addr)Escrow contract override.
.with_accept_payment_origins(origins)Origins allowed to receive payments.
use inndx_sdk::{Config, BillingConfig, ClientConfig, InndxClient, PrivateKeySigner};

let client = InndxClient::new(Config {
    client: ClientConfig::new(),
    billing: BillingConfig::new()
        .with_signer(signer)
        .with_max_deposit(10_000_000_000_000_000_000),
})?;

Per-call extras

Every resource method returns a Call builder. Before awaiting it, chain any of these to apply them to that one call only:

MethodEffect
.header(name, value)Adds a header to the outgoing request.
.timeout(duration)Sets a per-call timeout.
.with(|req| ...)Escape hatch for the underlying reqwest_middleware::RequestBuilder.
use std::time::Duration;

let markdown = client
    .scrape()
    .scrape_url_markdown(SessionOptions::default())?
    .call("https://example.com")
    .header("X-Scrape-Proxy", "isp")
    .timeout(Duration::from_secs(10))
    .await?;

Scrape

client.scrape() returns a ScrapeClient. All Scrape API endpoints are session-based.

scrape_url_markdown

Fetches a URL and returns the page as a markdown string.

use inndx_sdk::SessionOptions;

let session = client
    .scrape()
    .scrape_url_markdown(SessionOptions::default())?;

let markdown: String = session.call("https://example.com", None).await?;

session.close().await?;

Pass ScrapeUrlMarkdownParams to control proxy, timeout, or locale:

use inndx_sdk::types::scrape::{ScrapeProxy, ScrapeUrlMarkdownParams};

let markdown: String = session.call(
    "https://example.com",
    Some(&ScrapeUrlMarkdownParams {
        proxy: Some(ScrapeProxy::Isp),
        timeout_seconds: Some(30),
        locale: Some("en-US".to_string()),
    }),
).await?;

scrape_url

Full control over output formats and options. Returns a ScrapeResponse with a url field and a results vec.

use inndx_sdk::types::scrape::{ScrapeRequest, ScrapeFormat, ScrapeProxy, ScrapeResponseResult};

let session = client
    .scrape()
    .scrape_url(SessionOptions::default())?;

let response = session.call(&ScrapeRequest {
    url: "https://example.com".to_string(),
    formats: Some(vec![ScrapeFormat::default_markdown(), ScrapeFormat::html()]),
    proxy: Some(ScrapeProxy::Isp),
    timeout_seconds: Some(30),
    locale: Some("en-US".to_string()),
}).await?;

for result in response.results {
    match result {
        ScrapeResponseResult::Markdown { content } => println!("{content}"),
        ScrapeResponseResult::Html { content } => println!("{content}"),
        _ => {}
    }
}

session.close().await?;

ScrapeFormat constructors:

ConstructorDescription
ScrapeFormat::default_markdown()Page as markdown.
ScrapeFormat::markdown(skip_tags)Markdown with specified HTML tags removed.
ScrapeFormat::html()Raw HTML.

Sessions

Opening and closing

let session = client
    .scrape()
    .scrape_url_markdown(SessionOptions::default())?;

let page1 = session.call("https://example.com").await?;
let page2 = session.call("https://example.com/about").await?;

println!("spent: {}", session.cumulative());

let receipt = session.close().await?;

Scoped sessions

Use scope to guarantee the session closes even if your code returns an error:

let result = client
    .scrape()
    .scrape_url_markdown(SessionOptions::default())?
    .scope(|session| async move {
        session.call("https://example.com").await
    })
    .await?;

Session options

FieldDescription
max_depositEscrow cap for this session in token base units.
escrow_contractEscrow contract override for this session.

Session members

MemberDescription
.call(...)Makes a request through the session.
.close()Settles the channel on-chain. Returns Ok(Some(receipt)) or Ok(None) if nothing was opened.
.scope(f)Runs f(session) then settles regardless of outcome.
.cumulative()Amount spent so far across all calls.
.channel_id()The on-chain channel id, or None if not yet opened.
.opened()Whether the channel has been opened on-chain.
.escrow_contract()The escrow contract this session targets.

Reclaiming a stranded channel

If a process exits before close is called, the escrowed deposit stays on-chain. Persist the channel id as soon as the channel opens:

let page = session.call("https://example.com").await?;

if let Some(channel_id) = session.channel_id() {
    persist_to_disk(channel_id);
}

Forced close is a two-step sequence with a roughly 15-minute grace period between steps:

use inndx_sdk::{B256, ReclaimOptions};

let channel_id: B256 = "0x...".parse()?;
let reclaim = client.reclaim_session(channel_id, ReclaimOptions::default())?;

// Step 1: start the forced-close timer on-chain.
reclaim.request_close().await?;

// Step 2: after the grace period, withdraw.
let state = reclaim.get_state().await?;
if state.ready {
    reclaim.withdraw().await?;
}

The two steps do not need to run in the same process. Both request_close and withdraw are idempotent.

ReclaimOptions fields:

FieldDescription
chain_idChain to target. Defaults to Tempo mainnet.
escrow_contractEscrow contract override.

get_state() returns a ReclaimChannelState:

FieldTypeDescription
finalizedboolWhether the channel has been withdrawn on-chain.
close_requestedboolWhether request_close has been called.
readyboolWhether withdraw is expected to succeed right now.
ready_atOption<u64>Unix timestamp when withdraw becomes callable.
depositu128Total deposited amount.
settledu128Amount already settled to the payee.
refundableu128Amount returned to your wallet once finalized.

Error handling

All methods return Result<T, inndx_sdk::errors::Error>.

use inndx_sdk::errors::{Error, ErrorBody};

match session.call("https://example.com").await {
    Ok(markdown) => println!("{markdown}"),
    Err(Error::Api { status, body: ErrorBody::Validation { fields, .. } }) => {
        for field in fields {
            eprintln!("{}: {:?}", field.field, field.errors);
        }
    }
    Err(Error::Api { status, body: ErrorBody::Generic { message, .. } }) => {
        eprintln!("API error {status}: {message}");
    }
    Err(Error::ChannelNotReady { ready_at, .. }) => {
        eprintln!("grace period not elapsed; withdraw after unix timestamp {ready_at}");
    }
    Err(e) => eprintln!("{e}"),
}

Error variants:

VariantWhen
Error::Api { status, body }The server returned a non-2xx response.
Error::Payment(MppError)A payment or signing operation failed.
Error::ChannelNotReady { channel_id, ready_at }withdraw called before the grace period elapsed.
Error::RequestA transport-level error.
Error::DeserializeResponse body could not be deserialized.
Error::SessionClosedThe session was closed before a response was received.
Error::UnexpectedResponseThe server returned an unexpected response type.

Search docs

Search the Cloud documentation