linger-openai-sdk 0.1.0

Rust-native SDK for OpenAI APIs.
Documentation

linger-openai-sdk

Rust-native SDK for OpenAI APIs.

linger-openai-sdk provides typed request builders, typed responses, streaming support, multipart uploads, structured errors, conservative retry handling, redacted diagnostics, webhook verification, and a replaceable HTTP transport boundary.

This crate is designed to follow current OpenAI API behavior while keeping the implementation idiomatic for Rust: async APIs, explicit error types, feature-gated transport, and forward-compatible public models.

Installation

[dependencies]
linger-openai-sdk = "0.1"

Default features enable the reqwest transport with rustls:

[dependencies]
linger-openai-sdk = { version = "0.1", features = ["reqwest-transport", "rustls-tls"] }

To provide your own transport implementation:

[dependencies]
linger-openai-sdk = { version = "0.1", default-features = false }

Minimum supported Rust version: 1.82.

Quick Start

use linger_openai_sdk::{Client, CreateResponseRequest, LingerError};

async fn run() -> Result<(), LingerError> {
    let api_key = std::env::var("OPENAI_API_KEY")
        .map_err(|_| LingerError::invalid_config("OPENAI_API_KEY is required"))?;

    let client = Client::new(api_key)?;
    let response = client
        .responses()
        .create(
            CreateResponseRequest::builder()
                .model("gpt-4.1")
                .input("Explain Rust in one sentence.")
                .build()?,
        )
        .await?;

    println!("{}", response.output_text());
    Ok(())
}

Client Configuration

Client::new uses the default reqwest transport:

use linger_openai_sdk::Client;

let client = Client::new("sk-...")?;

Use ClientConfig when you need organization, project, base URL, or retry policy configuration. Custom configuration is passed together with a transport:

use linger_openai_sdk::{
    transport::ReqwestTransport,
    Client, ClientConfig, RetryPolicy, SharedTransport,
};

let retry_policy = RetryPolicy {
    max_retries: 2,
    ..RetryPolicy::default()
};

let config = ClientConfig::builder()
    .api_key("sk-...")
    .organization("org_...")
    .project("proj_...")
    .base_url("https://api.openai.com")
    .retry_policy(retry_policy)
    .build()?;

let client =
    Client::with_config_and_transport(config, SharedTransport::new(ReqwestTransport::default()));

Lower-level connection, request, and read timeouts can be configured on a custom reqwest::Client, wrapped with ReqwestTransport::new(client).

Implemented API Areas

The current crate covers the main user-facing OpenAI API surfaces:

  • Responses, including streaming, input token counting, and compaction
  • Chat Completions and legacy Completions
  • Embeddings and Moderations
  • Models
  • Files and multipart Uploads
  • Images
  • Audio speech, transcription, translation, voice consents, and custom voices
  • Videos, including create, edit, extend, remix, content download, and characters
  • Realtime sessions, transcription sessions, client secrets, and calls
  • Vector Stores, files, search, and file batches
  • Conversations and conversation items
  • Assistants and Threads APIs where implemented
  • Batches
  • Fine-tuning jobs, checkpoints, permissions, and grader helpers
  • Evals and eval runs
  • Containers and container files
  • Skills and skill versions
  • ChatKit sessions and threads
  • Webhook signature verification

Organization/Admin/Projects management APIs are outside the current supported scope. Live OpenAI API tests are opt-in only and are not required for normal crate builds.

Streaming

Streaming APIs return Rust streams whose items surface errors explicitly. The SDK parses Server-Sent Events incrementally instead of buffering a full stream.

use futures_util::StreamExt;
use linger_openai_sdk::{Client, CreateResponseRequest, LingerError};

async fn stream_response(client: &Client) -> Result<(), LingerError> {
    let mut stream = client
        .responses()
        .create_stream(
            CreateResponseRequest::builder()
                .model("gpt-4.1")
                .input("Stream a short answer.")
                .build()?,
        )
        .await?;

    while let Some(item) = stream.next().await {
        let item = item?;
        if let Some(delta) = item.output_text_delta() {
            print!("{delta}");
        }
    }

    Ok(())
}

Files and Uploads

File-like inputs use explicit upload wrappers so content type, file name, and bytes are visible at the call site.

use bytes::Bytes;
use linger_openai_sdk::{CreateFileRequest, FileUpload, LingerError};

async fn upload_file(client: &linger_openai_sdk::Client) -> Result<(), LingerError> {
    let file = FileUpload::from_bytes("training.jsonl", Bytes::from_static(b"{\"prompt\":\"...\"}\n"))?
        .content_type("application/jsonl")?;

    let uploaded = client
        .files()
        .create(
            CreateFileRequest::builder()
                .file(file)
                .purpose("fine-tune")
                .build()?,
        )
        .await?;

    println!("{}", uploaded.id);
    Ok(())
}

Large uploads can be created through client.uploads(), uploaded in parts, and completed with explicit part IDs.

Errors

All SDK methods return Result<T, LingerError>. Errors are structured and keep important diagnostics without flattening everything into strings.

use linger_openai_sdk::{ErrorKind, LingerError};

fn inspect_error(error: LingerError) {
    match error.kind() {
        ErrorKind::Api(api) => {
            eprintln!("status: {}", api.status);
            eprintln!("request id: {:?}", api.request_id);
            eprintln!("message: {}", api.error.message);
            eprintln!("type: {:?}", api.error.r#type);
            eprintln!("code: {:?}", api.error.code);
            eprintln!("param: {:?}", api.error.param);
        }
        ErrorKind::Timeout { .. } => eprintln!("request timed out"),
        ErrorKind::Transport { .. } => eprintln!("transport failed"),
        ErrorKind::Serialization { .. } => eprintln!("serialization failed"),
        ErrorKind::Streaming { .. } => eprintln!("streaming failed"),
        ErrorKind::RetryExhausted(details) => {
            eprintln!("attempts: {}", details.attempts);
            eprintln!("last error: {:?}", details.last_error);
        }
        ErrorKind::InvalidConfig { .. } => eprintln!("invalid SDK configuration"),
        _ => eprintln!("{error}"),
    }
}

OpenAI API errors preserve HTTP status, request id, selected response headers, and OpenAI error fields such as type, code, param, and message where available.

Webhooks

WebhookVerifier verifies OpenAI webhook signatures and can parse the payload after successful verification.

use linger_openai_sdk::{HeaderMap, LingerError, WebhookVerifier};
use serde_json::Value;

fn verify_webhook(headers: &HeaderMap, body: &[u8]) -> Result<Value, LingerError> {
    let verifier = WebhookVerifier::new("whsec_...")?;
    verifier.verify(headers, body)?;
    verifier.parse(headers, body)
}

Webhook secrets and authorization headers are redacted from diagnostics.

Features

Feature Enabled by default Description
reqwest-transport yes Default HTTP transport backed by reqwest.
rustls-tls yes Enables the rustls TLS backend for reqwest.
native-tls no Enables the native TLS backend for reqwest.

Disable default features if you need a runtime-neutral build with a custom transport:

[dependencies]
linger-openai-sdk = { version = "0.1", default-features = false }

Transport Boundary

Custom integrations can implement Transport:

use linger_openai_sdk::{HttpRequest, HttpResponse, LingerError, Transport};
use std::future::Future;
use std::pin::Pin;

struct MyTransport;

impl Transport for MyTransport {
    fn send(
        &self,
        request: HttpRequest,
    ) -> Pin<Box<dyn Future<Output = Result<HttpResponse, LingerError>> + Send + '_>> {
        Box::pin(async move {
            let _ = request;
            Err(LingerError::transport("custom transport is not wired yet"))
        })
    }
}

Safety and Diagnostics

The SDK avoids logging secrets or sensitive payloads by default. Diagnostics prefer request ids, HTTP status, endpoint metadata, retry information, and OpenAI error fields over raw payload dumps.

Redacted data includes API keys, authorization headers, webhook secrets, and sensitive organization/project headers.

Documentation

The crate documentation is available on docs.rs after publication:

https://docs.rs/linger-openai-sdk

The repository also includes detailed English and Chinese usage guides: README_EN.md and README_CN.md.

License

Licensed under either of:

  • Apache License, Version 2.0
  • MIT license

at your option.