openrouter_sdk 0.0.2
openrouter_sdk: ^0.0.2 copied to clipboard
type-safe toolkit for building AI-powered features in dart or flutter projects, giving you access to 400+ models across providers through a single unified API.
openrouter_sdk #
A type-safe Dart client for the OpenRouter.ai REST API.
Artisanally made (not vibe coded)(see - AI policy), mimicks the official openrouter sdk as closely as possile, except for where it made sense to differ.
It provides a clean, strongly-typed wrapper for both Dart and Flutter applications, with full support for streaming responses and multi-modal content.
Table of Contents #
- Implementation Progress
- Installation
- Usage
- Resources
- Error Handling
- API Reference
- OpenRouter Documentation
- AI involvement details
- License
Implementation Progress #
The table below tracks coverage of OpenRouter's API endpoint groups (as listed in the API Reference) against the resources implemented in this SDK. "Completion" reflects how many of a group's endpoints are usable (not stubs).
| Endpoint Group | Implemented as Resource | Completion | Methods |
|---|---|---|---|
| Chat | ✅ | 100% | send, streamResponse |
| Models | ✅ | 100% | get, list, count, listForUser |
| Providers | ✅ | 100% | list |
| Endpoints | ✅ | 100% | list, listZdrEndpoints |
| API Keys | ✅ | 100% | getCurrentKeyMetadata, list, get, create, update, delete |
| Credits | ✅ | 100% | getCredits |
| Analytics | ✅ | 100% | getUserActivity |
| Generations | 🟡 | ~50% | getGeneration (done); listGenerationContent is a TODO stub |
| Embeddings | ❌ | 0% | — |
| Rerank | ❌ | 0% | — |
| Responses (Beta) | ❌ | 0% | — |
| Text-to-Speech (TTS) | ❌ | 0% | — |
| Speech-to-Text (STT) | ❌ | 0% | — |
| Video Generation | ❌ | 0% | — |
| Images | ❌ | 0% | — |
| Files | ❌ | 0% | — |
| OAuth | ❌ | 0% | — |
| Organization | ❌ | 0% | — |
| Workspaces | ❌ | 0% | — |
| Guardrails | ❌ | 0% | — |
| BYOK | ❌ | 0% | — |
| Benchmarks | ❌ | 0% | — |
| Classifications | ❌ | 0% | — |
| Datasets | ❌ | 0% | — |
| Observability | ❌ | 0% | — |
| Presets | ❌ | 0% | — |
| Beta Analytics | ❌ | 0% | — |
Installation #
Add openrouter_sdk as a dependency in your pubspec.yaml.
From the command line #
-
For Dart projects:
dart pub add openrouter_sdk -
For Flutter projects:
flutter pub add openrouter_sdk
Or manually #
dependencies:
openrouter_sdk: ^0.0.1
Then run dart pub get or flutter pub get.
Usage #
1. Initialization #
All interactions go through the OpenRouter client. Get your API key from the OpenRouter Keys page.
import 'package:openrouter_sdk/openrouter_sdk.dart';
// Standard client (global routing).
final client = OpenRouter(
apiKey: 'YOUR_OPENROUTER_API_KEY',
);
You can optionally pass httpReferer (your app's URL, sent as the HTTP-Referer header) and appTitle (your app's display name, sent as the X-OpenRouter-Title header). These are used for OpenRouter rankings and dashboard display. appCategories (e.g. {'cli-agent', 'cloud-agent'}) is used for marketplace rankings.
final client = OpenRouter(
apiKey: 'YOUR_OPENROUTER_API_KEY',
httpReferer: 'https://your-app-url.com',
appTitle: 'Your App Title',
appCategories: {'cli-agent'},
);
EU in-region routing
To route requests through OpenRouter's EU region, use the OpenRouter.eu constructor:
final euClient = OpenRouter.eu(
apiKey: 'YOUR_OPENROUTER_API_KEY',
httpReferer: 'https://your-app-url.com',
appTitle: 'Your App Title',
);
Custom base URL
To target a self-hosted or compatible gateway, use OpenRouter.custom:
final customClient = OpenRouter.custom(
baseUrl: 'https://my-gateway.example.com/api/v1',
apiKey: 'YOUR_OPENROUTER_API_KEY',
);
2. Chat Completions #
Chat is accessed through the chat resource. The send method returns a single Future<ChatResponse>.
final client = OpenRouter(apiKey: 'YOUR_OPENROUTER_API_KEY');
final messages = [
UserMessage.text(content: 'What is the capital of France?'),
];
try {
final ChatResponse response = await client.chat.send(
model: 'openai/gpt-4o',
messages: messages,
);
print(response.choices.first.message.content);
} on OpenRouterException catch (e) {
print('API Error: ${e.message} (Code: ${e.code})');
}
The send method accepts many optional parameters that map directly to the OpenRouter API, including temperature, topP, maxCompletionTokens, reasoningEffort, responseFormat, tools, toolChoiceConfig, providerSettings, seed, stop, and more.
3. Streaming Completions #
For real-time output, use streamResponse, which returns a Stream<ChatResponse>. Each emitted ChatResponse contains a partial delta in choices.first.message; the final chunk typically carries aggregated usage.
final client = OpenRouter(apiKey: 'YOUR_OPENROUTER_API_KEY');
final messages = [
UserMessage.text(content: 'Write a short story about a brave knight.'),
];
try {
final Stream<ChatResponse> stream = client.chat.streamResponse(
model: 'google/gemini-2.5-flash',
messages: messages,
);
await for (final ChatResponse response in stream) {
final content = response.choices.first.message.content;
if (content != null) print(content);
}
} on OpenRouterException catch (e) {
print('API Error during stream: ${e.message}');
}
4. Constructing Messages #
Messages are represented by ChatMessage subclasses. Every role supports either a simple text constructor (*.text) or a multi-part constructor that takes a list of MessageContent parts.
| Role | Text constructor | Multi-part constructor |
|---|---|---|
system |
SystemMessage.fromText |
SystemMessage(contents: …) |
developer |
DeveloperMessage.fromText |
DeveloperMessage(contents: …) |
user |
UserMessage.text |
UserMessage(contents: …) |
assistant |
AssistantMessage.text |
AssistantMessage(contents: …) |
tool |
ToolMessage.text |
ToolMessage(contents: …) |
Text and multi-modal user messages
The user role (and others) can carry rich, multi-modal content via MessageContent parts. Available content types:
TextContent(content, [cacheControlTTL])ImageContent.fromUrl(url: …, detail: …)ImageContent.fromFile(path: …, detail: …)ImageContent.fromData(data: …, format: …, detail: …)AudioContent,VideoContent,FileContent
// Simple text
final userMsg = UserMessage.text(content: 'What is it known for?');
// Multi-modal (text + image)
final visionMsg = UserMessage(
contents: [
TextContent(content: 'What is in this picture?'),
ImageContent.fromUrl(url: 'https://i.imgur.com/kQ1c2d6.png'),
],
);
Full conversation example
final messages = [
SystemMessage.fromText(content: 'You are a helpful assistant.'),
UserMessage.text(content: 'What is the capital of France?'),
AssistantMessage.text(content: 'The capital of France is Paris.'),
UserMessage.text(content: 'What is it known for?'),
];
final response = await client.chat.send(
model: 'openai/gpt-4o',
messages: messages,
);
print(response.choices.first.message.content);
Tool messages
When using function calling, respond to a tool call with a ToolMessage referencing the assistant's toolCallId:
final toolMsg = ToolMessage.text(
content: '{"temperature": 21}',
toolCallId: call.id,
);
5. Understanding the Response #
ChatResponse contains the API's reply:
id(String): Unique identifier for the request.model(String): The model slug used for the completion.createdAt(DateTime): When the response was created.choices(List<ResopnseChoice>): The completions. Each choice hasmessage(anAssistantMessage),finishReason,index, andlogProbs.usage(ChatResponseUsage?): Token usage statistics (often only present on the final streamed chunk).metadata(OpenRouterMetadata?): OpenRouter-specific metadata when requested.serviceTier(ServiceTier): The upstream service tier used.
Resources #
The OpenRouter client exposes typed resources as getters. Each resource maps to a group of OpenRouter API endpoints.
Models #
// List models with optional filters.
final models = await client.models.list(
query: 'gpt',
sort: ModelSort.pricing,
);
// Get a single model by author/slug.
final model = await client.models.get(author: 'openai', slug: 'gpt-4o');
// Total model count.
final count = await client.models.count(null);
// Models tailored to the authenticated user's settings.
final userModels = await client.models.listForUser();
Providers #
final providers = await client.providers.list();
print('Found ${providers.length} providers.');
Endpoints #
// Endpoints for a specific model.
final endpointData = await client.endpoints.list(author: 'qwen', slug: 'qwen3-next-80b-a3b-instruct');
print('Found ${endpointData.endpoints.length} endpoints.');
// ZDR (zero data retention) endpoints.
final zdrEndpoints = await client.endpoints.listZdrEndpoints();
API Keys #
Key management requires a management (provisioning) key.
// Metadata for the current key.
final meta = await client.apiKeys.getCurrentKeyMetadata();
// List keys (management key required).
final keys = await client.apiKeys.list();
// Create a new key with an optional spending limit.
final (:key, :keyString) = await client.apiKeys.create(
name: 'New Test Key',
limit: 10.0, // $10 spending limit
);
// Update and delete.
await client.apiKeys.update(keyHash: key.hash, disabled: true);
final deleted = await client.apiKeys.delete(key.hash);
Credits #
// Management key required.
final (:totalCredits, :totalUsage) = await client.credits.getCredits();
print('Used $totalUsage of $totalCredits credits.');
Generations #
// Fetch request & usage metadata for a generation by ID.
final generation = await client.generations.getGeneration('generation-id');
print('Cost: \$${generation.totalCost}');
Analytics #
// User activity grouped by endpoint (management key required).
final activity = await client.analytics.getUserActivity(date: DateTime.now());
print('Requests today: ${activity.first.requests}');
Error Handling #
API errors are surfaced as OpenRouterException, which carries code, message, and metadata.
try {
await client.chat.send(
model: 'invalid/model',
messages: [UserMessage.text(content: 'test')],
);
} on OpenRouterException catch (e) {
print('Caught an API Error!');
print('Code: ${e.code}');
print('Message: ${e.message}');
print('Metadata: ${e.metadata}');
} catch (e) {
print('Caught a general error: $e');
}
API Reference #
For a complete list of all available methods, classes, and parameters, see the full API Reference.
OpenRouter Documentation #
For more details on the OpenRouter API, see the official documentation.
AI Policy #
this package was coded manually, the only instances where an LLM was used is in the writing of this readme and potentially other documentation in the future. some api documenation was also produced with an auto-complete assistante. the api interfaces and requests and response handling and all code is and will remain old-school manually written.
contributions are most welcome but not if they were generated by an llm.
License #
This package is licensed under the MIT License.