rodiumai
Official Dart/Flutter SDK for the Rodium AI API — access GPT, Claude, Gemini, DeepSeek and more with RODI credits and Mobile Money top-ups.
OpenAI-compatible REST API — same endpoints and payloads as documented at rodiumai.io/docs.
Links
| Resource | URL |
|---|---|
| pub.dev | pub.dev/packages/rodiumai |
| Source code | github.com/lecodeur228/rodiumai |
| API documentation | rodiumai.io/docs |
| Dashboard & API keys | rodiumai.io/dashboard |
| Model catalogue | rodiumai.io/models |
Installation (Flutter)
Add the package to your Flutter or Dart project:
flutter pub add rodiumai
# ou pour un projet Dart pur :
dart pub add rodiumai
Requirements: Dart SDK >=3.0.0. No Flutter dependency — the package works in CLI, server, and Flutter apps alike.
Configuration
1. Get an API key
- Create a free account on rodiumai.io/dashboard
- Top up RODI credits (Mobile Money: MTN, Orange, Wave, Moov, Airtel…)
- Generate an API key (prefix
rd_sk_…orrdk_…)
2. Provide the key to your app
Option A — environment variable (recommended for dev/CI):
export RODIUMAI_API_KEY="rd_sk_votre_cle"
dart run example/main.dart
Option B — --dart-define (Flutter run/build):
flutter run --dart-define=RODIUMAI_API_KEY=rd_sk_votre_cle
const apiKey = String.fromEnvironment('RODIUMAI_API_KEY');
if (apiKey.isEmpty) throw StateError('RODIUMAI_API_KEY manquante');
Option C — flutter_dotenv (.env file, never commit secrets):
# pubspec.yaml
dependencies:
rodiumai: ^0.1.0
flutter_dotenv: ^5.2.0
flutter:
assets:
- .env
import 'package:flutter_dotenv/flutter_dotenv.dart';
await dotenv.load();
final client = RodiumAIClient(apiKey: dotenv.env['RODIUMAI_API_KEY']!);
3. Create the client
import 'package:rodiumai/rodiumai.dart';
final client = RodiumAIClient(
apiKey: 'rd_sk_votre_cle',
// logRequests: true, // debug HTTP (dev only)
);
Multilingual (optional)
Language is never required. Without configuration, the SDK uses English error hints and the AI follows the user's message language.
Optional — localized SDK error hints
final client = RodiumAIClient(
apiKey: apiKey,
locale: 'fr', // optional — only affects exception hint text
);
Optional — force AI responses in app language
// Only when you want the model to always reply in a specific language
final res = await client
.language('fr') // optional fluent method
.model('openai/gpt-4o')
.chat('Bonjour !');
Flutter example (optional):
final locale = Localizations.localeOf(context).languageCode;
await client.language(locale).model(modelId).chat('Hello');
Supported locales: en (default), fr, es.
Unlike a static enum, models are fetched live from the API so your app always reflects the current catalogue and RODI pricing metadata.
final catalogue = await client.models();
print('${catalogue.length} modèles'); // ex: 42
print(catalogue.providerPrefixes); // [anthropic, deepseek, google, …]
print(catalogue.chatModels.map((m) => m.id)); // modèles compatibles chat
// Filtres
final openai = catalogue.byProvider('openai');
final anthropic = catalogue.byProvider('anthropic');
final longCtx = catalogue.withContextAtLeast(500000);
final gpt4o = catalogue.findById('openai/gpt-4o');
// Propriétés d'un modèle
for (final m in catalogue.chatModels) {
print('${m.displayName} — ${m.contextWindow} tokens — chat: ${m.supportsChatCompletion}');
}
Reference: GET /v1/models
Quick start
Chat with a string
final res = await client.chat('Bonjour !');
print(res.content);
print('Tokens: ${res.totalTokens}');
Chat with fluent builder + dynamic model
final catalogue = await client.models();
final modelId = catalogue.findById('openai/gpt-4o')?.id
?? catalogue.chatModels.first.id;
final res = await client
.model(modelId)
.temperature(0.7)
.maxTokens(256)
.systemPrompt('Réponds toujours en français.')
.chat('Présente RodiumAI en 2 phrases.');
print(res.content);
Multi-turn conversation
final messages = [
const ChatMessage.user('Quel est le capital du Togo ?'),
const ChatMessage.assistant('Lomé est la capitale du Togo.'),
const ChatMessage.user('Et sa population ?'),
];
final res = await client
.model('anthropic/claude-haiku-4-5-20251001')
.chat(messages);
Streaming SSE
Per Streaming guide: lines data: {json}, end with data: [DONE].
await for (final delta in client.model('openai/gpt-4o-mini').stream('Raconte une histoire courte.')) {
stdout.write(delta.content);
}
Flutter integration
StatefulWidget — chat screen
import 'package:flutter/material.dart';
import 'package:rodiumai/rodiumai.dart';
class ChatScreen extends StatefulWidget {
const ChatScreen({super.key, required this.client});
final RodiumAIClient client;
@override
State<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends State<ChatScreen> {
final _controller = TextEditingController();
String _response = '';
bool _loading = false;
List<ModelInfo> _models = [];
String? _selectedModelId;
@override
void initState() {
super.initState();
_loadModels();
}
Future<void> _loadModels() async {
try {
final catalogue = await widget.client.models();
setState(() {
_models = catalogue.chatModels;
_selectedModelId = _models.isNotEmpty ? _models.first.id : null;
});
} on RodiumAIException catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Erreur modèles: ${e.message}')),
);
}
}
}
Future<void> _send() async {
final prompt = _controller.text.trim();
if (prompt.isEmpty || _selectedModelId == null) return;
setState(() {
_loading = true;
_response = '';
});
try {
final res = await widget.client
.model(_selectedModelId!)
.temperature(0.7)
.maxTokens(512)
.chat(prompt);
setState(() => _response = res.content);
} on InsufficientCreditsException {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Crédits RODI insuffisants — rechargez sur rodiumai.io')),
);
}
} on RodiumAIException catch (e) {
setState(() => _response = 'Erreur (${e.statusCode}): ${e.message}');
} finally {
setState(() => _loading = false);
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('RodiumAI Chat')),
body: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
if (_models.isNotEmpty)
DropdownButtonFormField<String>(
value: _selectedModelId,
decoration: const InputDecoration(labelText: 'Modèle'),
items: _models
.map((m) => DropdownMenuItem(
value: m.id,
child: Text('${m.displayName} (${m.providerPrefix})'),
))
.toList(),
onChanged: (v) => setState(() => _selectedModelId = v),
),
const SizedBox(height: 12),
TextField(
controller: _controller,
decoration: const InputDecoration(
labelText: 'Message',
border: OutlineInputBorder(),
),
maxLines: 3,
),
const SizedBox(height: 12),
FilledButton(
onPressed: _loading ? null : _send,
child: _loading
? const SizedBox(
width: 20,
height: 20,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('Envoyer'),
),
const SizedBox(height: 16),
Expanded(
child: SingleChildScrollView(
child: Text(_response.isEmpty ? '…' : _response),
),
),
],
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
Streaming in Flutter (typing effect)
class StreamingChat extends StatefulWidget {
const StreamingChat({super.key, required this.client, required this.modelId});
final RodiumAIClient client;
final String modelId;
@override
State<StreamingChat> createState() => _StreamingChatState();
}
class _StreamingChatState extends State<StreamingChat> {
final _buffer = StringBuffer();
Future<void> _stream(String prompt) async {
_buffer.clear();
setState(() {});
await for (final delta in widget.client.model(widget.modelId).stream(prompt)) {
setState(() => _buffer.write(delta.content));
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
FilledButton(
onPressed: () => _stream('Compte de 1 à 5.'),
child: const Text('Stream'),
),
Expanded(child: SingleChildScrollView(child: Text(_buffer.toString()))),
],
);
}
}
Provider filter tabs
Widget buildProviderTabs(ModelCollection catalogue, void Function(String) onSelect) {
final providers = catalogue.providerPrefixes;
return Wrap(
spacing: 8,
children: providers.map((prefix) {
final count = catalogue.byProvider(prefix).chatModels.length;
return ActionChip(
label: Text('$prefix ($count)'),
onPressed: () {
final chat = catalogue.byProvider(prefix).chatModels;
if (chat.isNotEmpty) onSelect(chat.first.id);
},
);
}).toList(),
);
}
Error handling
Mapped per API errors:
| HTTP | Exception | Action in Flutter |
|---|---|---|
| 401 | UnauthorizedException |
Check API key / re-login dashboard |
| 402 | InsufficientCreditsException |
Show top-up link to dashboard |
| 429 | RateLimitException |
Retry with exponential backoff |
| 422 | ValidationException |
Fix model or messages payload |
| 500+ | RodiumAIException |
Retry once, then support |
try {
await client.chat('Hello');
} on UnauthorizedException catch (e) {
// Clé invalide
} on InsufficientCreditsException catch (e) {
// Solde RODI insuffisant
} on RateLimitException catch (e) {
// Trop de requêtes
} on ValidationException catch (e) {
// Payload invalide
} on RodiumAIException catch (e) {
print('${e.statusCode}: ${e.message}');
print(e.responseBody); // JSON brut si présent
}
API reference
RodiumAIClient
| Method | Returns | Description |
|---|---|---|
chat(messages) |
Future<ChatResponse> |
Non-streaming completion |
stream(messages) |
Stream<ChatStreamDelta> |
SSE streaming |
models() |
Future<ModelCollection> |
Live catalogue from API |
model(id) |
RodiumAIClient |
Set model ID (fluent) |
temperature(n) |
RodiumAIClient |
0.0–2.0 |
topP(n) |
RodiumAIClient |
0.0–1.0 |
maxTokens(n) |
RodiumAIClient |
Max output tokens |
systemPrompt(s) |
RodiumAIClient |
Prepended system message |
stop(list) |
RodiumAIClient |
Stop sequences |
messages accepts: String, List<ChatMessage>, or List<Map<String, dynamic>>.
All fluent methods return a new immutable instance — safe to reuse the base client.
ModelCollection
| Member | Description |
|---|---|
models |
Full list from API |
chatModels |
Text/chat-capable models only |
ids |
All model ID strings |
findById(id) |
Lookup by exact ID |
byProvider(prefix) |
Filter e.g. "openai" (dynamic from API) |
withContextAtLeast(n) |
Long-context models |
reasoningModels |
o1, R1, M1… |
providerPrefixes |
Distinct providers in response |
ModelInfo
| Property | Description |
|---|---|
id |
API model slug (openai/gpt-4o) |
contextWindow |
Context size in tokens |
outputModalities |
text, image, embedding… |
displayName |
Human-readable label |
supportsChatCompletion |
Usable with chat() / stream() |
isLongContext |
≥ 200K tokens |
isReasoningModel |
Chain-of-thought models |
provider / providerPrefix |
Provider metadata |
ChatMessage constructors
ChatMessage.user('Hello');
ChatMessage.assistant('Previous reply');
ChatMessage.system('You are a helpful assistant.');
Endpoints implemented
| API | SDK | Docs |
|---|---|---|
POST /v1/chat/completions |
chat(), stream() |
Chat completions |
GET /v1/models |
models() |
List models |
Base URL: https://api.rodiumai.io/v1 (fixed — not configurable)
Auth: Authorization: Bearer {RODIUMAI_API_KEY}
Example app
cd rodiumai
export RODIUMAI_API_KEY="rd_sk_votre_cle"
dart run example/main.dart
Testing & development
See CONTRIBUTING.md for project structure and PR guidelines.
dart pub get
dart run build_runner build
dart analyze --fatal-infos
dart test
dart pub publish --dry-run
License
MIT — see LICENSE.
Libraries
- rodiumai
- SDK officiel Dart/Flutter pour l'API RodiumAI.