openrouter_sdk

pub version license

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

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 has message (an AssistantMessage), finishReason, index, and logProbs.
  • 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.

Libraries

openrouter_sdk