openai-api-rs: an unofficial OpenAI client for Rust, with OpenRouter and Realtime support
OpenAI API client library for Rust (unofficial)
At a glance
- What is it?
- openai-api-rs wraps the OpenAI HTTP API in typed Rust structs, covers chat, embeddings, audio, batch, assistants, realtime and responses, and lets you point the same client at OpenRouter. The trade-off is that it is a single-maintainer, unofficial crate with a fast-moving major version.
- Who is it for?
- Adopt openai-api-rs if you are writing a Rust service or CLI that talks to OpenAI-compatible HTTP endpoints and you want typed request and response structs instead of hand-rolled JSON. Do not adopt it if you need an officially supported SDK with a vendor SLA, or if you are unwilling to track a crate whose major version has moved to 10.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 151 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 15, 2026, and from our analysis. They are not legal advice.
DEEP OPEN-SOURCE ANALYSIS
What openai-api-rs actually solves for a Rust codebase
Calling the OpenAI HTTP API from Rust without a client crate means writing serde structs for every request and response shape, handling multipart uploads for audio and files, and keeping a WebSocket path for realtime. openai-api-rs packages those shapes. The README describes it as an unofficial client that provides convenient access to the OpenAI API from Rust applications, and the supported list covers Completions, Chat, Edits, Images, Embeddings, Audio, Files, Fine-tuning, Moderations, Function calling, Assistants, Batch, Realtime and Responses.
The intended user is a Rust developer who already knows which endpoint they want and would rather not model the payloads. It is not a framework. There is no retry policy, no rate-limit governor, no token accounting layer in the README, and no abstraction over providers beyond an endpoint override. If you want those, you are building them on top.
The practical value is narrower than the feature list suggests: it gives you a typed surface and an examples directory you can copy from. That is the whole pitch, and for a lot of internal tooling it is enough.
Client construction, endpoints and the OpenRouter path
The architecture is a thin builder plus per-endpoint request structs. You construct an OpenAIClient through OpenAIClient::builder(), pass an API key with with_api_key, and optionally override the base URL with with_endpoint. Every call then takes a request struct and returns a typed result. The client also exposes a headers field, and the README example prints it after a call, which is a small but useful detail: you can inspect what the server returned at the HTTP level without dropping to reqwest yourself.
The OpenRouter story is the same mechanism, not a separate code path. You set with_endpoint("https://openrouter.ai/api/v1") and use OPENROUTER_API_KEY instead of OPENAI_API_KEY. The README shows the same ChatCompletionRequest shape against GPT4_O_MINI in that configuration. Because OpenRouter is OpenAI-compatible at the wire level, the crate does not need provider-specific logic, and the repository includes examples/openrouter.rs, examples/openrouter_models.rs and examples/openrouter_reasoning.rs.
That design has a consequence worth naming. Anything OpenRouter adds that is not part of the OpenAI schema has to fit through the same structs, which is why the reasoning example exists as a separate file rather than as a flag on the base request. The base URL override is a blunt instrument: it redirects everything, including endpoints the alternate provider may not implement.
Installing openai-api-rs and sending a first chat completion
The crate is published on crates.io and the README pins the dependency at version 10.0.1. Add it to Cargo.toml:
[dependencies]
openai-api-rs = "10.0.1"Then export a key. The README recommends an environment variable and shows both names:
export OPENAI_API_KEY=sk-xxxxxxxThere is also an optional base URL variable, OPENAI_API_BASE, which the README documents as https://api.openai.com/v1.
The minimal program builds the client, constructs a ChatCompletionRequest against GPT4_O, sends it, and reads the first choice. This is adapted from the README example, which is an async main using tokio:
use openai_api_rs::v1::api::OpenAIClient;
use openai_api_rs::v1::chat_completion::{self, ChatCompletionRequest};
use openai_api_rs::v1::common::GPT4_O;
use std::env;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let api_key = env::var("OPENAI_API_KEY").unwrap().to_string();
let mut client = OpenAIClient::builder().with_api_key(api_key).build()?;
let req = ChatCompletionRequest::new(
GPT4_O.to_string(),
vec![chat_completion::ChatCompletionMessage {
role: chat_completion::MessageRole::user,
content: chat_completion::Content::Text(String::from("What is bitcoin?")),
name: None,
tool_calls: None,
tool_call_id: None,
}],
);
let result = client.chat_completion(req).await?;
println!("Content: {:?}", result.choices[0].message.content);
Ok(())
}What you should see is the assistant text printed from result.choices[0].message.content. Note the message fields: name, tool_calls and tool_call_id are all Option and must be set explicitly, which is verbose but keeps function calling and plain text on one struct. For streaming, the repository ships examples/chat_completion_stream.rs; for vision, examples/vision.rs. The README points at the examples directory for more.
Where openai-api-rs stops being the right choice
The crate is unofficial, and the README says so in its own title. That matters when an endpoint changes shape: you are waiting on a maintainer rather than on a vendor SDK release process. The repository is not archived and the last push was on 2026-04-17, so the project has not been abandoned, but it is a single-author crate and the release cadence shows it: v9.0.1 on 2025-12-29, then v10.0.0 on 2026-03-29, then v10.0.1 on 2026-04-17. Two major-version bumps inside four months is a real upgrade tax if you pin loosely.
The feature list also mixes current and legacy surface. Edits appears in the supported list alongside Responses and Realtime. If you are starting today, several of those endpoints are not what you would choose, and the README does not rank them or mark any as deprecated. You have to know the OpenAI API yourself to pick correctly.
The README does not document retries, timeouts, backoff, or what happens on a non-2xx response beyond the Result return type. It does not document rollback or migration steps between major versions either. If your service needs predictable failure handling under load, that logic is yours to write, and the crate gives you no hooks for it beyond wrapping the call.
Finally, if your target is a provider that is only partly OpenAI-compatible, the single with_endpoint override is a weak fit. You will be constructing requests that the base structs do not model.
How it compares with async-openai and hand-written reqwest code
The obvious alternative in Rust is async-openai, which is the other widely used OpenAI client in the ecosystem. The difference in approach is structural rather than cosmetic: async-openai models the API as a set of service traits hanging off a Client, so you call client.chat().create(...) and the client is generic over configuration. openai-api-rs instead exposes free-standing request structs and a chat_completion method on the client itself, which is why the README example reads as construct-then-send.
That makes openai-api-rs more direct for a single-endpoint script and less composable if you want to inject a custom HTTP layer or share configuration across many services. It also means the OpenRouter path here is a base-URL swap, whereas a trait-based client tends to make alternate providers a configuration object.
The other alternative is doing it yourself with reqwest and serde. The Cargo.toml shows that openai-api-rs is essentially that: reqwest 0.12 with charset, http2, json, multipart, socks and stream, plus serde, serde_json, bytes, tokio, tokio-tungstenite, futures-util and url. If you only need one endpoint, writing the structs yourself is a few hundred lines and removes the upgrade cadence from your dependency graph. If you need audio multipart uploads, batch, assistants and a realtime WebSocket, the crate is saving you a meaningful amount of plumbing.
Licence, TLS features and the cost of tracking v10
The project is MIT licensed, and the Cargo.toml carries license = "MIT" with the LICENSE file at the repository root. MIT is permissive: you can use the crate in closed-source software provided you keep the copyright and permission notice. That is a general description of the licence, not legal advice, and if your organisation has a policy on third-party notices you should follow it.
The feature flags are worth reading before you build. The default feature is default-tls, which pulls reqwest/default-tls and tokio-tungstenite/native-tls. There is a rustls feature that switches to reqwest/rustls-tls and tokio-tungstenite/rustls-tls-webpki-roots. If you are building for a musl target or a minimal container, that choice affects your binary and your certificate story, and it is the kind of thing that is easier to decide at the start than to change later.
Upgrade cost is the real maintenance line item. Moving from v9 to v10 is a major bump, and the README does not include a changelog or migration guide. Your defence is to pin exactly (openai-api-rs = "10.0.1" as the README shows) and read the release notes before moving. The examples directory is the practical reference: if a struct changed shape, the matching example file is where you will notice it.
Editorial conclusion
Adopt openai-api-rs if you are writing a Rust service or CLI that talks to OpenAI-compatible HTTP endpoints and you want typed request and response structs instead of hand-rolled JSON. Do not adopt it if you need an officially supported SDK with a vendor SLA, or if you are unwilling to track a crate whose major version has moved to 10. Before committing, verify that the endpoints you need appear in the supported API list, check the examples directory for a file matching your use case, and confirm the TLS feature you want (default-tls or rustls) builds in your target environment.
Frequently asked questions
Is openai-api-rs an official OpenAI library?
No. The README titles it as an unofficial OpenAI API client library for Rust, and the crate is published by its author rather than by OpenAI.
How do I install openai-api-rs in a Rust project?
Add it to Cargo.toml as openai-api-rs = "10.0.1", which is the version the README pins. The crate is also documented on docs.rs.
Can openai-api-rs talk to OpenRouter instead of OpenAI?
Yes. The README shows building the client with with_endpoint("https://openrouter.ai/api/v1") and reading the key from OPENROUTER_API_KEY, and the repository includes examples/openrouter.rs.
Which OpenAI endpoints does openai-api-rs support?
The README lists Completions, Chat, Edits, Images, Embeddings, Audio, Files, Fine-tuning, Moderations, Function calling, Assistants, Batch, Realtime and Responses. It does not mark any of them as deprecated.
Does openai-api-rs support streaming responses?
The repository ships examples/chat_completion_stream.rs and examples/responses_stream.rs, and the Cargo.toml enables the reqwest stream feature. The README itself only shows the non-streaming chat example.
Which TLS backend does openai-api-rs use by default?
The default feature is default-tls, which enables reqwest/default-tls and tokio-tungstenite/native-tls. A rustls feature is available as an alternative.
Community notes