openrouter-batch
A Rust client for the OpenRouter Batch API, plus synchronous structured chat completions and embeddings. Every wire behavior in this crate was verified against the live API.
Features
- Batch API (
/beta/batches): submit chat-completions batches, poll to a terminal status, decode per-line results. One malformed result line never fails a completed batch — it arrives asBatchResultLine::Undecodablewith the raw JSON preserved. - Structured output: strict JSON Schema (
response_formatwithstrict: true), shared by the batch and synchronous paths so their wire shapes cannot drift. One schema per batch, as required by Google-backed routes. - Embeddings (
/v1/embeddings): array input, decoded back into input-ordered vectors with envelope/count/index/duplicate validation. - Typed transport errors with retry classification (timeouts, connect failures, 408/429, 5xx are retryable) and provider request IDs surfaced on every failure.
- Testable seam:
BatchTransportandEmbeddingTransporttraits let you fake the API in your own tests;HttpTransporttakes a configurable API root.
Wire rules encoded (live-verified)
- One batch carries one
endpoint, one batch-levelmodel, and one inlinerequestsarray;endpointandmodelmust serialize beforerequests(the API stream-parses and answers 400 otherwise). - Submit answers
202 Acceptedwithstatus: "validating"— queued, not complete. Polls can 404 in the window after submit (eventual consistency). - Terminal statuses:
completed,failed,expired,cancelled. Acompletedbatch can still contain per-request failures: exactly one ofresponseorerroris populated per result line. custom_idis capped at 64 characters; overlong keys are rejected locally before reaching the wire.- The batch-level
modelmust be a:batchvariant slug. - Server-side retention is 30 days: persist every result at poll time.
Usage
Structured batch
use ;
use json;
async
Synchronous structured completion
let transport = with_timeout?;
let response = transport.chat_completion.await?;
let content = response.content?;
Embeddings
use ;
let transport = new?;
let raw = transport.embed.await?;
let vectors = embedding_vectors?;
assert_eq!;
Live smoke test CLI
# Resume polling a batch after a `poll timeout`; the batch keeps
# processing server-side and results stay retrievable for 30 days.
The CLI resolves the API key from --api-key, then OPENROUTER_API_KEY, then a .env file in the current directory.
Notes
- The default request timeout is 30s, sized for batch calls. Synchronous chat completions can run far longer — use [
HttpTransport::with_timeout]. - The Batch API lives under
/beta/batchesand may change; the client treats the API root as configuration, so pointed tests and fakes stay easy. - Reasoning fields are opt-in: some routes make reasoning mandatory and fail every item when
"reasoning": {"enabled": false}is present.
License
MIT