Model or dataset
anasfik/openai avatar
anasfik/openai

dart_openai: an unofficial OpenAI SDK for Dart and Flutter

The unofficial OpenAI SDK for Dart & Flutter. Full API coverage + OpenAI-compatible providers (Azure, DeepSeek, LM Studio, Ollama): Responses, Chat, Realtime, Videos, Batch, Fine-tuning.

666 stars231 forksDartMIT

At a glance

What is it?
dart_openai wraps the OpenAI HTTP surface in typed Dart clients for Chat, Responses, Realtime, Batch and fine-tuning, and it also points at any OpenAI-compatible endpoint. The interesting part is per-client configuration; the trade-off is that you inherit every API change OpenAI ships, because the package is unofficial.
Who is it for?
Adopt dart_openai if you are shipping a Flutter app or a server-side Dart service that needs OpenAI or an OpenAI-compatible endpoint, and you want typed clients rather than hand-rolled JSON. Do not adopt it if you need Assistants v1 or ChatKit, which the README lists as not planned, or if you require a vendor-backed SDK with a support contract.
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 22 days ago.
What is it written in?
Mainly Dart, 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 dart_openai is for, and who ends up using it

Dart has no first-party OpenAI SDK. If you are writing a Flutter app that streams a chat completion, or a server-side Dart service that submits a Batch job or a fine-tuning run, the default path is to build HTTP requests by hand, parse JSON into maps, and write your own streaming loop over server-sent events. dart_openai exists to remove that work. It is an unofficial package maintained by Anas Fikhi, published on pub.dev as dart_openai, and licensed MIT.

The audience is narrow and specific. Flutter developers who want one code path across Android, iOS, macOS, Linux, Windows and web. Dart backend developers who would rather call a typed method than assemble a request body. Teams that run more than one provider: the README states the package works with any provider that implements the OpenAI wire format, and names DeepSeek, LM Studio, Ollama, Groq, Together and Azure OpenAI gateways. That last group is the one the design actually seems aimed at, because the per-client configuration model is what makes running several endpoints side by side practical.

One client object per endpoint, and why that beats a global key

The older shape of this package was a global facade: you set OpenAI.apiKey once and called OpenAI.instance.chat.create. That still works, but it is a process-wide singleton, which means one API key, one base URL, one timeout for the whole program. The current design makes each OpenAIClient own its configuration, so a production OpenAI account, a DeepSeek key and a local LM Studio server on localhost can coexist without stepping on each other.

Configuration is per client: apiKey, organization, baseUrl, version, requestsTimeOut and extraHeaders, with global equivalents on the facade. Azure is handled by passing an OpenAIAzure object with a resource, an apiVersion and a deployments map. The README shows that the model name you pass is rewritten to your deployment automatically, so you keep writing model: 'gpt-4o' in your call sites while the client substitutes gpt4o-prod underneath. That is a small thing that saves a lot of conditional code.

The API surface is broad. The README's coverage table lists Responses, Chat Completions, Conversations, Audio, Images, Embeddings, Files, Uploads, Batch, Vector Stores, Containers, Models, Moderation, Evals, Graders, Fine-tuning, Videos, Realtime, Skills, Content provenance and Administration (projects, users, invites, audit logs, costs, rate limits, API keys), plus legacy Completions and Edits. Two surfaces are explicitly excluded: Assistants v1 and Threads, described as superseded by the Responses API, and ChatKit. If your existing code is built on Assistants, this package will not migrate it for you.

Install dart_openai and send a first chat completion

Add the dependency to pubspec.yaml. The README pins the major version at 8.

yaml
dependencies:
  dart_openai: ^8.0.0

Then resolve it from the command line.

bash
dart pub get

The quickstart reads the key from the environment rather than hardcoding it, which is the pattern the README uses. Construct a client, then call chat.create with a model and a message list. The message is a typed OpenAIChatCompletionChoiceMessageModel with a role from OpenAIChatMessageRole and a content string. The response is typed, so the text you want is completion.choices.first.message.content.

dart
import 'package:dart_openai/dart_openai.dart';

Future<void> main() async {
  final client = OpenAIClient(apiKey: Platform.environment['OPENAI_API_KEY']!);

  final completion = await client.chat.create(
    model: 'gpt-4o',
    messages: [
      const OpenAIChatCompletionChoiceMessageModel(
        role: OpenAIChatMessageRole.user,
        content: 'Say hello in five words.',
      ),
    ],
  );

  print(completion.choices.first.message.content);
}

If you would rather stream, chat.createStream returns an async sequence of chunks and you read chunk.choices.first.delta?.content inside an await for loop. The Responses API has its own stream, createStream, which yields raw event maps; the README's example filters on event['type'] == 'response.output_text.delta' and writes event['delta'] to stdout. Both paths sit on the same SSE engine, which the README says closes on [DONE], does not duplicate events, and raises errors as exceptions rather than swallowing them.

Streaming, retries and the idle watchdog

Two mechanisms in this package deserve attention because they are the places where hand-rolled OpenAI clients usually break.

The first is retry policy. Requests retry automatically on connection errors and on HTTP 408, 429 and 5xx. GET requests always retry; POST retries only on rate limits and server errors. Backoff is exponential with jitter, and the server's Retry-After header takes precedence over the computed delay. The default is 2 total attempts, which is conservative: for a batch submission you may want more, and the README shows OpenAIRetryPolicy(maxAttempts: 4) passed to the client constructor. Note that this is a constructor argument, so the policy is per client, not global.

The second is the streaming idle watchdog. A connection that stops delivering bytes fails with StreamTimedOutException after the request timeout rather than hanging indefinitely. That matters more than it sounds. A chat UI that waits forever on a dead socket looks like a frozen app; a typed timeout exception is something you can catch and surface. The README does not document a separate configurable idle window distinct from the request timeout, so if you need a longer silence tolerance than your overall request timeout, that is a gap you should test against your own network conditions before relying on it.

Quota is observable after any call through OpenAIResponseMeta.lastRateLimit, with fields such as remainingRequests. That is a static, so it reflects the most recent response, not a per-client counter. In a program running several clients concurrently, treat it as a hint rather than an authoritative ledger.

Files without dart:io, and what that buys on web

The file APIs take a platform-neutral type, and the README is explicit that this avoids dart:io so the same code runs on web. On native platforms you load from disk with loadOpenAIFile and pass the result to client.file.upload. Anywhere, including the browser, you upload bytes directly.

dart
await client.file.uploadBytes(
  bytes: utf8.encode(jsonl),
  fileName: 'training.jsonl',
  purpose: 'fine-tune',
);

This is the kind of design decision that only shows up when you try to reuse code across targets. A fine-tuning workflow that prepares a JSONL file and submits it is exactly the sort of thing teams prototype on a server and then want in a Flutter admin screen. Because the byte path exists, that move does not require a rewrite.

Error handling follows the same normalization idea. Failures throw typed exceptions: RequestFailedException for non-2xx responses, with message and statusCode; MissingApiKeyException when no key is configured for that client; OpenAIUnexpectedException for a malformed response that is not an API error payload. The README states that error payloads are normalized across providers, so whether a provider returns a nested error object, a bare string, or an HTML error page, you get a RequestFailedException with the body preserved. That normalization is the practical reason multi-provider support works at all, since compatible providers are not consistent about error shapes.

Where dart_openai is the wrong choice

The package is unofficial, and that is the first limitation, not a footnote. OpenAI's API changes on its own schedule. When a new parameter or endpoint ships, this package has to catch up in a release, and you have to upgrade to get it. The README shows the maintainer is responsive, with v8.0.0 adding web support, automatic retries and native Azure handling, but there is no support contract and no guarantee about how quickly a breaking upstream change is absorbed.

Coverage is deliberately partial. Assistants v1 and Threads are listed as not planned, and ChatKit is excluded. Any codebase built on Assistants has no migration path inside this package; you would move to the Responses API or stay elsewhere.

The README is also thin in places that matter for production. It documents retry counts and the Retry-After precedence, but not total backoff ceilings or how retries interact with streaming reconnects. It shows OpenAIResponseMeta.lastRateLimit but does not describe what happens to that value when a request fails before a response arrives. There is no documented rollback procedure for a version upgrade, and the repository's CHANGELOG.md is the place to look for that rather than the README.

Finally, if your application is Python or Node, this package is irrelevant to you. It is a Dart library, and the installation instructions are pub commands, not pip or npm.

How it compares to generating a client from the OpenAPI spec

The obvious alternative is not another Dart package; it is code generation. OpenAI publishes an OpenAPI specification, and tools such as openapi-generator can produce a Dart client from it. The difference in approach is fundamental. A generated client mirrors the spec mechanically: every endpoint, every field, every schema, regenerated whenever you rerun the generator. dart_openai is hand-written, and its value is in the parts a generator does not give you.

Those parts are the SSE engine shared across streaming endpoints, the retry policy with jitter and Retry-After handling, the idle watchdog that turns a dead stream into StreamTimedOutException, the error normalization that flattens three different provider error shapes into one exception type, and the Azure deployment rewriting. A generator will produce method signatures for the same endpoints, but you will write the streaming loop, the retry logic and the error mapping yourself, and you will own them.

The trade-off runs the other way too. A generated client tracks the spec automatically, so a new endpoint is available as soon as you regenerate. dart_openai tracks it on the maintainer's schedule. If you need guaranteed spec parity and you are willing to build the resilience layer yourself, generation is defensible. If you want streaming and retries to work on day one across Flutter targets, the hand-written package is the shorter path.

Maintenance, licensing and the cost of upgrading

The repository is not archived, and the last push was on 2026-08-25, the same day v8.0.0 was released. The release history shows a major-version cadence rather than a constant trickle: v6.0.0 in November 2025, then v7.0.0 and v8.0.0 both on 2026-08-25. That pattern suggests periodic batches of breaking work rather than continuous small changes, which is easier to plan around but means an upgrade can be substantial when it lands.

The pubspec constraint in the README is ^8.0.0, so a future v9 would require an explicit version bump in your pubspec.yaml. Because the package is unofficial, the upgrade cost is yours: you read CHANGELOG.md, bump the constraint, run your own tests, and check whether any client constructor arguments you rely on, such as retryPolicy or the azure configuration, changed shape. Nothing in the README describes a deprecation window or a compatibility shim between majors.

Licensing is straightforward. The repository carries an MIT license, which permits commercial use and modification with attribution and without warranty. That is a statement about the license file, not legal advice; if your organisation has rules about open-source dependencies, route it through whoever normally handles that.

Editorial conclusion

Adopt dart_openai if you are shipping a Flutter app or a server-side Dart service that needs OpenAI or an OpenAI-compatible endpoint, and you want typed clients rather than hand-rolled JSON. Do not adopt it if you need Assistants v1 or ChatKit, which the README lists as not planned, or if you require a vendor-backed SDK with a support contract. Before you commit, verify three things against your own account: that your model works through the client.chat path with the reasoningEffort and maxTokens parameters your workload needs, that your deployment mapping behaves as expected if you go through Azure, and that RequestFailedException carries the status codes your retry logic depends on.

Frequently asked questions

How do I install dart_openai in a Dart or Flutter project?

Add dart_openai: ^8.0.0 to the dependencies block of your pubspec.yaml, then run dart pub get. The package is published on pub.dev as dart_openai.

How do I use an OpenAI API key with dart_openai?

Pass it to the client constructor, as in OpenAIClient(apiKey: Platform.environment['OPENAI_API_KEY']!). The older global facade still works by setting OpenAI.apiKey. If no key is configured for a client, calls throw MissingApiKeyException.

Can dart_openai talk to providers other than OpenAI?

Yes. The README states it works with any provider implementing the OpenAI wire format, and names DeepSeek, LM Studio, Ollama, Groq, Together and Azure OpenAI gateways. You point a client at a different endpoint with the baseUrl option.

How do I stream responses with dart_openai?

Use chat.createStream for Chat Completions and read chunk.choices.first.delta?.content inside an await for loop. The Responses API has its own createStream that yields event maps, where you filter on event['type'] == 'response.output_text.delta'.

Does dart_openai support the Assistants API?

No. The README lists Assistants v1 and Threads as not planned, describing them as superseded by the Responses API. ChatKit is also excluded.

Does dart_openai retry failed requests automatically?

Yes, on connection errors and HTTP 408, 429 and 5xx. GET requests always retry, while POST retries only on rate limits and server errors, with exponential backoff and jitter. The default is 2 total attempts, configurable through OpenAIRetryPolicy(maxAttempts: 4) on the client.

Official sources

  1. anasfik/openai on GitHub
  2. License: MIT
  3. Project website
  4. README
  5. Releases
Community notes

Community notes