Browse documentation

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 nrouter

Or for Flutter applications:

flutter pub add nrouter

Alternatively, add nrouter directly to your pubspec.yaml:

dependencies:
  flutter:
    sdk: flutter
  nrouter: ^3.1.2

Setup & 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

ParameterTypeDefaultDescription
apiKeyStringRequiredYour nRouter virtual key (sk-nrouter-...).
baseUrlStringhttps://api.nrouter.ai/v1Unified gateway base URL. Must include /v1.
timeoutDurationDuration(seconds: 60)HTTP connection and socket read timeout.
defaultHeadersMap<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

TypeHTTP StatusDescriptionRecommended Action
NRouterRequestError400Invalid payload or missing parametersVerify payload structure and model parameter bounds.
NRouterGuardrailBlockedError400Request blocked by active guardrail checksInspect input prompt; review guardrail rules in dashboard.
NRouterAuthenticationError401Invalid or revoked virtual keyMint a fresh virtual key from your authentication service.
NRouterCreditError402Insufficient balance or budget limit reachedRefill organization credits or increase key spending ceiling.
NRouterNotFoundError404Model not foundConsult model catalog for supported model IDs.
NRouterRateLimitError429Rate limit or TPM quota exceededImplement backoff logic; check retry headers.
NRouterServiceError503Gateway or upstream service errorSpecify fallback models in the request.
NRouterTransportError—Network connection failureVerify 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

Was this page helpful?