hotdata
Official Rust client for the Hotdata HTTP API: workspaces, connections, databases, SQL queries, results, uploads, indexes, jobs, embedding providers, and workspace context.
The crate pairs a fully generated, typed API surface (hotdata::apis, hotdata::models) with a hand-written ergonomic layer: a flat Client that wires up authentication (a static API token, or a pluggable per-request bearer) and workspace scoping, plus an optional Apache Arrow result decoder.
Requirements
Rust 1.74+ and a Tokio runtime (the client is async).
Install
Add the crate to your Cargo.toml:
[]
= "0.18"
= { = "1", = ["macros", "rt-multi-thread"] }
For an unreleased revision:
[]
= { = "https://github.com/hotdata-dev/sdk-rust.git" }
By default the crate builds against native-tls. To use rustls instead:
[]
= { = "0.18", = false, = ["rustls"] }
Authentication
The API authenticates with an API token sent as Authorization: Bearer <token>, plus an X-Workspace-Id header on requests scoped to a workspace.
Your API token (prefixed hd_) is the only credential you need. It is sent verbatim as the bearer token on every request — there is nothing to exchange, refresh, or cache.
Deprecated and removed. Earlier versions exchanged the API token for a short-lived JWT against
/v1/auth/jwtand refreshed it in the background. That exchange,TokenManager,ClientBuilder::client_id, and theHOTDATA_DISABLE_JWT_EXCHANGEescape hatch are all gone and are not coming back — see the CHANGELOG. If you passed an API token toClientBuilder::api_token, nothing changes for you.
use *;
let client = builder
.api_token
.workspace_id
.build?;
base_url defaults to https://api.hotdata.dev. Override it if you target another environment.
Supplying a bearer per request
A static API token is fixed for the life of the Configuration, which is all most callers need. A host that owns its own credential lifecycle — an interactive login whose access token expires in minutes, say — can install a BearerTokenProvider on Configuration::token_provider instead. The SDK then asks it for a bearer once per request, so a long call (a multi-gigabyte upload_file, a slow query, a large parallel batch) can refresh mid-flight rather than 401 on a token that expired after it started:
use ;
use *;
let mut client = builder.api_token.build?;
client.configuration_mut.token_provider = Some;
bearer_value is called on every request, including the create-session and finalize legs of upload_file, so a credential that rotates between them is picked up. It is a hook and nothing more: the SDK never exchanges one credential for another. With no provider installed, bearer_access_token behaves exactly as before.
If a provider returns an error, the SDK logs a warning on the log facade and sends the request unauthenticated, which surfaces as a 401 from the server. That warning is only visible if your binary installed a log implementation with warn enabled for the hotdata target — without one, a provider failure looks like any other 401, so wire up a logger before debugging one.
Quickstart
use *;
async
Resource handles
The OpenAPI generator emits free functions; the Client groups them into
ergonomic, workspace-scoped handles so you never pass a Configuration around:
// Grouped handles: client.<resource>().<operation>(..)
let connections = client.connections.list.await?;
let connection = client.connections.get.await?;
// Query runs are database-scoped: pass the database id first.
let runs = client.query_runs.list.await?;
Handles exist for every resource — connections, databases,
database_context, embedding_providers, indexes, information_schema,
jobs, queries, query_runs, results, saved_queries, uploads,
workspaces. The hottest
operations also have flat shortcuts directly on Client (query — with
query_in to scope to a database, query_preview to skip auto-follow, and
query_with for a per-call QueryConfig — plus get_result, list_results,
list_query_runs, list_workspaces). The result and query-run operations take
a database_id argument — they are scoped to a database via the required
X-Database-Id header.
For anything not yet wrapped, the full generated surface is one call away via
client.configuration():
use workspaces_api;
let workspaces = list_workspaces.await?;
Typed status
Result and query-run status fields are plain strings on the wire. Interpret
them with the typed [ResultStatus] / [QueryRunStatus] enums via the
result_status() / run_status() accessors:
use *;
let result = client.await_result.await?;
if result.result_status.is_ready
let run = client.query_runs.get.await?;
if run.run_status.is_terminal
Both enums carry an Other(String) variant, so a status the server adds later
round-trips instead of breaking deserialization.
Updating nullable fields
Several update requests model a field that is both optional (omit to leave
unchanged) and nullable (send null to clear) as Option<Option<T>>. The
field helpers name the three
intents so call sites read clearly:
use field;
let mut req = new;
req.name = set; // set
req.description = clear; // send null (clear)
// req.sql left as None -> omitted -> unchanged
client.saved_queries.update.await?;
Every resource lives under hotdata::apis::<resource>_api, and request/response
types under hotdata::models. The flat prelude re-exports Client,
ClientBuilder, PollConfig, Configuration, the resource handles, and all
models for convenience.
Errors from generated operations are returned as hotdata::Error<T>; builder
and configuration failures are hotdata::ClientError. The enhanced query
family returns hotdata::QueryError — Overloaded (429 retries exhausted),
Submit (the underlying request failed), AsyncRequested (use submit_query
for async queries), Async (the server fell back to asynchronous execution
with a 202; the acknowledgement is passed through), Poll (an API error while
polling during auto-follow), and Result(ResultError) for truncation
auto-follow failures (TooLarge / Timeout / Incomplete / …). Result-polling and
one-call helpers return hotdata::AwaitResultError / hotdata::QueryToArrowError.
The SDK's own error enums are #[non_exhaustive], so match them with a wildcard
arm.
Arrow results
Query results can be fetched as an Apache Arrow IPC stream instead of JSON, which is faster and far more memory-efficient for large result sets. The decoder is behind an optional arrow feature (off by default):
[]
= { = "0.18", = ["arrow"] }
ArrowResult hands back arrow types, so a crate that names them must depend
on arrow 59 — the same major this SDK uses. Two arrow majors in one build
are distinct types and will not compile together.
use *;
async
A third, open_result_arrow, decodes straight off the socket: it pulls body chunks as batches are asked for, so peak memory is one record batch rather than the whole result. Use it for a result larger than memory — the other two collect the entire body before returning. Its schema is available before the first batch, and the pooled connection stays checked out until the stream is drained or dropped.
let mut stream = client
.open_result_arrow
.await?;
println!;
while let Some = stream.next_batch.await?
All three accept offset and limit for pagination, and all carry the same authentication and workspace headers as the generated operations. They return ArrowError::NotReady if the result is still pending or processing — poll client.get_result(result_id, database_id) until its status is ready first. ArrowResult also surfaces the X-Total-Row-Count header (total_row_count) and the rel="next" pagination Link (next_link).
To run a query and get its result as Arrow in a single call — submit, await
ready, and decode — use query_to_arrow:
let arrow = client
.query_to_arrow
.await?;
Debug logging
Every HTTP call the SDK makes — generated operations and the hand-written submit_query, upload_file, and Arrow fetch — emits log::debug! records on the hotdata::http target: the request (>>> METHOD url, headers, body) and the response (<<< status, body). Authorization bearer tokens and sensitive body fields (api_token, secret, password, …) are masked before logging.
The SDK installs no logger and prints nothing on its own. To see the records, wire any log backend and enable the hotdata::http target at debug level. For example with env_logger:
// RUST_LOG=hotdata::http=debug cargo run
init;
[]
= "0.11"
>>> POST https://api.hotdata.dev/v1/query
authorization: Bearer hd_a...cdef
content-type: application/json
{"sql":"SELECT 1"}
<<< 200 OK
{"result_id":"…","columns":[…]}
API reference
Generated documentation builds on docs.rs (with all-features enabled, so the arrow surface is included).
Generated Markdown for every operation and model also lives in docs/:
- Resource APIs:
docs/*Api.md(for exampleQueryApi.md) - Request and response models:
docs/<ModelName>.md
Support
Questions and issues: github.com/hotdata-dev/sdk-rust.