Dart & Flutter SDK
The official nRouter Dart & Flutter SDK with cross-platform support for web, mobile, and desktop, SSE streaming, and typed errors.
Last updated
The official nRouter Dart SDK (nrouter) provides cross-platform support for Flutter mobile, desktop, web, and standalone Dart VM applications. With a single lightweight dependency on http, it connects to https://api.nrouter.ai/v1 and delivers automated metadata extraction, Server-Sent Events (SSE) streaming, and sealed error types.
Installation
Add nrouter to your project dependencies:
dart pub add nrouter
# or for Flutter projects:
flutter pub add nrouterOr add it directly to your pubspec.yaml:
dependencies:
nrouter: ^3.1.2Setup & Authentication
Pass your virtual API key directly to the NRouter client:
import 'package:nrouter/nrouter.dart';
final client = NRouter(apiKey: 'sk-nrouter-your-api-key-here');For command-line Dart VM applications, configure the environment variable:
export NROUTER_API_KEY="sk-nrouter-your-api-key-here"Then read it in your CLI app:
import 'dart:io';
import 'package:nrouter/nrouter.dart';
final client = NRouter(apiKey: Platform.environment['NROUTER_API_KEY']!);The client connects to https://api.nrouter.ai/v1 by default. Custom endpoints can be passed via baseUrl:
final client = NRouter(
apiKey: 'sk-nrouter-your-api-key-here',
baseUrl: 'https://api.nrouter.ai/v1',
);Key Safety in Flutter Applications
In Flutter mobile and web apps, Platform.environment does not supply shell environment variables (dart:io is unavailable on web). Furthermore, any key compiled into a mobile binary or served on the web is visible to anyone inspecting network requests or decompiling the client bundle. Always mint short-lived virtual keys from your backend service rather than bundling root credentials.
Quickstart
Send a chat completion request and read the response:
import 'package:nrouter/nrouter.dart';
Future<void> main() async {
final client = NRouter(apiKey: 'sk-nrouter-your-api-key-here');
final result = await client.chatCompletions({
'model': 'gpt-5.4-mini',
'messages': [
{'role': 'user', 'content': 'Hello!'}
],
});
print(result.body['choices']);
if (result.meta.isPriced) {
print('Cost: \$${result.meta.cost}');
}
client.close();
}Streaming
Stream responses in real-time using asynchronous streams:
await for (final chunk in client.messagesStream({
'model': 'claude-haiku-4-5-20251001',
'max_tokens': 64,
'messages': [
{'role': 'user', 'content': 'Hello!'}
],
})) {
print(chunk.delta);
}Cancelling stream subscription stops consumption of the underlying HTTP stream immediately.
Error Handling
All gateway refusals map to subclasses of the sealed NRouterError hierarchy:
try {
await client.chatCompletions(body);
} on NRouterGuardrailBlockedError catch (e) {
print('Blocked by guardrail: ${e.message}');
} on NRouterCreditError catch (e) {
print('Out of credits: ${e.message}');
} on NRouterRateLimitError catch (e) {
if (e.isRetryable) {
print('Rate limit hit; retryable after backoff');
}
}| Type | HTTP Status | Description |
|---|---|---|
NRouterRequestError | 400 | Invalid payload or missing parameters |
NRouterGuardrailBlockedError | 400 | Request blocked by active guardrail checks |
NRouterAuthenticationError | 401 | Invalid or revoked virtual key |
NRouterCreditError | 402 | Insufficient balance or budget limit reached |
NRouterNotFoundError | 404 | Model not found |
NRouterRateLimitError | 429 | Rate limit or TPM quota exceeded |
NRouterServiceError | 503 | Gateway or upstream service error |
NRouterTransportError | — | Network connection failure |
isRetryable is true only for NRouterRateLimitError, NRouterServiceError, and NRouterTransportError.
Response Metadata & Cost Tracking
result.meta extracts all x-nr-* gateway response headers into strongly-typed properties:
final meta = result.meta;
print('Request ID: ${meta.requestId}');
print('Model: ${meta.model}');
print(meta.isPriced ? 'Cost: \$${meta.cost}' : 'Cost: unpriced');Source & Repository
The source code and runnable demonstrations are available on GitHub: