Dart & Flutter SDK
The official nRouter Dart & Flutter SDK with cross-platform support for web, mobile, and desktop, Server-Sent Events streaming, and typed error handling.
Last updated
The official nRouter Dart SDK (nrouter) provides cross-platform client bindings for Flutter mobile (iOS and Android), desktop (macOS, Windows, and Linux), web, and standalone Dart VM applications. With a lightweight footprint built on http, it connects directly to https://api.nrouter.ai/v1, delivering automated response metadata parsing, Server-Sent Events (SSE) token streaming, and sealed error types.
Routing Flutter and Dart applications through nRouter centralizes model orchestration: server-side guardrails block prompt injections before requests hit downstream models, virtual keys isolate mobile device spend, and multi-provider failover protects client apps against cloud provider outages.
Prerequisites & Installation
The package requires Dart SDK 3.0+ or Flutter 3.10+.
Add nrouter to your project dependencies:
dart pub add nrouterOr for Flutter applications:
flutter pub add nrouterAlternatively, add nrouter directly to your pubspec.yaml:
dependencies:
flutter:
sdk: flutter
nrouter: ^3.1.2Setup & Configuration
Configure authentication based on whether your code runs on a server or client device.
Server & Command-Line Dart VM
For command-line tools and Dart backend servers, export your virtual key in the shell:
export NROUTER_API_KEY="sk-nrouter-your-virtual-key"Initialize the client by reading from Platform.environment:
import 'dart:io';
import 'package:nrouter/nrouter.dart';
final client = NRouter(
apiKey: Platform.environment['NROUTER_API_KEY']!,
baseUrl: 'https://api.nrouter.ai/v1',
);Key Safety in Flutter Client Applications
On Flutter mobile and web apps, Platform.environment is unavailable. Pass your virtual key directly:
import 'package:nrouter/nrouter.dart';
final client = NRouter(
apiKey: 'sk-nrouter-your-virtual-key',
baseUrl: 'https://api.nrouter.ai/v1',
);Client Security Invariant: Never bundle permanent administrative API keys inside production Flutter client binaries or web bundles. Mobile binaries can be decompiled and network requests intercepted. Always mint short-lived virtual keys with strict USD budget caps from your backend service and deliver them to authenticated client apps.
Configuration Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
apiKey | String | Required | Your nRouter virtual key (sk-nrouter-...). |
baseUrl | String | https://api.nrouter.ai/v1 | Unified gateway base URL. Must include /v1. |
timeout | Duration | Duration(seconds: 60) | HTTP connection and socket read timeout. |
defaultHeaders | Map<String, String> | {} | Custom HTTP headers sent on every request (e.g. x-nr-routing). |
import 'package:nrouter/nrouter.dart';
final client = NRouter(
apiKey: 'sk-nrouter-your-virtual-key',
baseUrl: 'https://api.nrouter.ai/v1',
timeout: const Duration(seconds: 45),
defaultHeaders: {
'x-nr-routing': 'latency',
},
);Implementation Patterns
1. Synchronous Chat Completion
Send structured messages and parse the completion:
import 'package:nrouter/nrouter.dart';
Future<void> main() async {
final client = NRouter(apiKey: 'sk-nrouter-your-virtual-key');
final result = await client.chatCompletions({
'model': 'gpt-5.4-mini',
'messages': [
{'role': 'system', 'content': 'You are a senior Flutter engineer.'},
{'role': 'user', 'content': 'Explain Flutter widget tree reconciliation.'},
],
});
print(result.body['choices'][0]['message']['content']);
// Inspect real-time gateway spend
if (result.meta.isPriced) {
print('Cost: \$${result.meta.cost}');
print('Request ID: ${result.meta.requestId}');
print('Model: ${result.meta.model}');
}
client.close();
}2. Server-Sent Events (SSE) Streaming in Flutter
Stream tokens directly into your Flutter UI state:
import 'package:flutter/material.dart';
import 'package:nrouter/nrouter.dart';
class StreamingChatWidget extends StatefulWidget {
final NRouter client;
const StreamingChatWidget({super.key, required this.client});
@override
State<StreamingChatWidget> createState() => _StreamingChatWidgetState();
}
class _StreamingChatWidgetState extends State<StreamingChatWidget> {
String _streamedText = '';
bool _isLoading = false;
Future<void> _startStreaming(String prompt) async {
setState(() {
_isLoading = true;
_streamedText = '';
});
try {
final stream = widget.client.messagesStream({
'model': 'claude-haiku-4-5-20251001',
'max_tokens': 256,
'messages': [
{'role': 'user', 'content': prompt}
],
});
await for (final chunk in stream) {
if (!mounted) break;
setState(() {
_streamedText += chunk.delta;
});
}
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Stream error: $e')),
);
}
} finally {
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
Expanded(child: SingleChildScrollView(child: Text(_streamedText))),
if (_isLoading) const CircularProgressIndicator(),
ElevatedButton(
onPressed: () => _startStreaming('Write a haiku about mobile apps.'),
child: const Text('Stream Haiku'),
),
],
);
}
}Stream Lifecycle: Cancelling a stream subscription closes the underlying HTTP connection immediately. nRouter terminates downstream generation, preventing token waste on dismissed screens.
3. Per-Request Gateway Overrides
Attach prompt template parameters or bypass cache in the payload:
final result = await client.chatCompletions({
'model': 'gpt-5.5',
'messages': [
{'role': 'user', 'content': 'Summarize user feedback.'}
],
'nrouter_prompt_template_id': 'tmpl_feedback_summary_v1',
'nrouter_prompt_variables': {'locale': 'en'},
'nrouter_cache': true,
});Production Best Practices
Deterministic Routing & Fallbacks
Ensure high reliability across mobile networks with fallback chains:
final result = await client.chatCompletions({
// Primary model with automatic fallback
'model': 'gpt-5.4-mini,claude-haiku-4-5-20251001',
'messages': [
{'role': 'user', 'content': 'Verify transaction receipt.'}
],
});x-nr-routing: latency: Directs traffic to the lowest-latency active provider deployment.x-nr-routing: cost: Prioritizes the most cost-effective deployment matching the model specification.- Model Fallback Chain: If the primary provider reports capacity limits or elevated 5xx errors, nRouter fails over instantly to the fallback model.
Response Telemetry & FinOps Tracking
Every call through nRouter produces metadata headers accessible via result.meta:
requestId: Edge UUID for correlating client logs with nRouter spend ledgers.model: Canonical identifier of the physical model that served the response.cost: Exact USD cost charged for the call.isPriced: Boolean indicating if exact pricing was calculated.
Troubleshooting & Error Handling
All gateway refusals map to subclasses of the sealed NRouterError hierarchy:
try {
final res = await client.chatCompletions(payload);
} on NRouterGuardrailBlockedError catch (e) {
print('Blocked by nRouter safety guardrail: ${e.message}');
} on NRouterAuthenticationError catch (e) {
print('Authentication failed: ${e.message}');
} on NRouterCreditError catch (e) {
print('Budget ceiling reached: ${e.message}');
} on NRouterRateLimitError catch (e) {
if (e.isRetryable) {
print('Rate limit exceeded; retryable after backoff: ${e.message}');
}
} on NRouterServiceError catch (e) {
print('Gateway service error: ${e.message}');
} on NRouterTransportError catch (e) {
print('Network transport error: ${e.message}');
}Common Error Codes
| Type | HTTP Status | Description | Recommended Action |
|---|---|---|---|
NRouterRequestError | 400 | Invalid payload or missing parameters | Verify payload structure and model parameter bounds. |
NRouterGuardrailBlockedError | 400 | Request blocked by active guardrail checks | Inspect input prompt; review guardrail rules in dashboard. |
NRouterAuthenticationError | 401 | Invalid or revoked virtual key | Mint a fresh virtual key from your authentication service. |
NRouterCreditError | 402 | Insufficient balance or budget limit reached | Refill organization credits or increase key spending ceiling. |
NRouterNotFoundError | 404 | Model not found | Consult model catalog for supported model IDs. |
NRouterRateLimitError | 429 | Rate limit or TPM quota exceeded | Implement backoff logic; check retry headers. |
NRouterServiceError | 503 | Gateway or upstream service error | Specify fallback models in the request. |
NRouterTransportError | — | Network connection failure | Verify mobile device internet connection. |
isRetryable is true only for NRouterRateLimitError, NRouterServiceError, and NRouterTransportError.
Source & Repository
The source code, unit tests, and runnable demonstrations are maintained in the official SDK repository:
Next Steps
- Swift / iOS SDK Guide — Native Swift client for Apple platforms
- Node.js SDK Guide — TypeScript client for web and backend services
- Chat Completions API — Direct HTTP wire specifications