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.

pub package Dart SDK License: MIT Tests

OpenAI-compatible REST API — same endpoints and payloads as documented at rodiumai.io/docs.

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

  1. Create a free account on rodiumai.io/dashboard
  2. Top up RODI credits (Mobile Money: MTN, Orange, Wave, Moov, Airtel…)
  3. Generate an API key (prefix rd_sk_… or rdk_…)

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.