Unofficial Dart/Flutter SDK for the OpenAI API. Typed clients for every major API surface: Responses, Chat Completions, Realtime, Videos, Batch, Fine-tuning, Vector Stores, Evals, Administration and more. Compiles and runs on every platform: Android, iOS, macOS, Linux, Windows, web, and server-side Dart.
Every OpenAIClient owns its configuration. Run several accounts, Azure resources, or OpenAI-compatible providers side by side without global state:
final production = OpenAIClient(apiKey: 'sk-prod');
final deepseek = OpenAIClient(
apiKey: 'ds-...',
baseUrl: 'https://api.deepseek.com',
);
final localLlama = OpenAIClient(
apiKey: 'not-needed',
baseUrl: 'http://localhost:1234/v1',
);
Works with any provider that implements the OpenAI wire format: DeepSeek, LM Studio, Ollama, Groq, Together, Azure OpenAI gateways, and others.
Streaming
One SSE engine backs every streaming endpoint. It closes on [DONE], never duplicates events, and surfaces errors as exceptions instead of swallowing them.
Responses API events:
final events = client.responses.createStream(
model: 'gpt-4o',
input: 'Write a haiku about databases.',
);
awaitfor (final event in events) {
if (event['type'] == 'response.output_text.delta') {
stdout.write(event['delta']);
}
}
Chat Completions deltas:
final chunks = client.chat.createStream(model: 'gpt-4o', messages: messages);
awaitfor (final chunk in chunks) {
stdout.write(chunk.choices.first.delta?.content);
}
Images emit partial results as they render (createStream), and stored completions support retrieval and listing.
Not planned: Assistants v1 and Threads (superseded by the Responses API), ChatKit.
Resilience
Requests retry automatically on transient failures (connection errors, HTTP 408/429/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.
Streaming has an idle watchdog: a connection that stops delivering bytes fails with StreamTimedOutException after the request timeout instead of hanging forever. After any call, quota is observable:
final azure = OpenAIClient(
apiKey: '<azure-key>',
azure: constOpenAIAzure(
resource: 'my-resource',
apiVersion: '2024-10-21',
deployments: {'gpt-4o': 'gpt4o-prod'},
),
);
// model is rewritten to your deployment automatically:await azure.chat.create(model: 'gpt-4o', messages: messages);
Files on every platform
Upload APIs take a platform-neutral file type — no dart:io, so the same code runs on web:
// From disk (native):final file = awaitloadOpenAIFile('training.jsonl');
await client.file.upload(file: file, purpose: 'fine-tune');
// From memory (anywhere, including web):await client.file.uploadBytes(
bytes: utf8.encode(jsonl),
fileName: 'training.jsonl',
purpose: 'fine-tune',
);
Error handling
All failures throw typed exceptions you can catch precisely:
try {
await client.chat.create(model: 'gpt-4o', messages: messages);
} onRequestFailedExceptioncatch (e) {
// Non-2xx from the API: e.message, e.statusCode
} onMissingApiKeyExceptioncatch (e) {
// No key configured for this client
} onOpenAIUnexpectedExceptioncatch (e) {
// Malformed response that is not an API error payload
}
Error payloads are normalized across providers. Whether a provider returns {"error": {"message": ...}}, a bare string error, or an HTML error page, you get a RequestFailedException with the body preserved.
Global facade equivalents: OpenAI.apiKey, OpenAI.organization, OpenAI.baseUrl, OpenAI.requestsTimeOut, plus OpenAI.showLogs and OpenAI.showResponsesLogs for request debugging.
Reasoning models (o-series, GPT-5 family): pass reasoningEffort: 'low' and use maxTokens, which maps to max_completion_tokens. Incompatible sampling parameters are rejected by the API; use extraParams to pass anything not yet modeled.
Migrating from 6.x / 7.x
Nothing breaks if you used the global facade. To adopt per-client instances, replace global configuration with construction:
8.0.0 replaces dart:io File parameters with the platform-neutral OpenAIFile type (await loadOpenAIFile(path) on native, OpenAIFile(bytes:, fileName:) anywhere), and audio.createSpeech now returns bytes.
The suite runs entirely against mocked HTTP — request shapes, SSE decoding, malformed-payload fuzzing, retry behavior. No API keys required:
dart test
Live integration tests run only when OPEN_AI_API_KEY is present in the environment.
Contributing
Bug reports and feature requests go through GitHub Issues. Pull requests welcome: keep diffs focused, add or update tests for changed behavior, and follow the existing module layout (lib/src/instance/<module>/ with models under lib/src/core/models/<module>/). Commit messages use Conventional Commits.