Sarufi - Dart & Flutter SDK

pub package pub points license: MIT Made in Tanzania

A modern, null-safe Dart SDK for the Sarufi Conversational AI platform. Build, train and chat with Swahili-first chatbots from any Dart or Flutter app: mobile, desktop, web, server or CLI.

final sarufi = Sarufi('YOUR_API_KEY');
final bot = await sarufi.getBot(42);
final reply = await bot.respond(message: 'Habari');
print(reply.messages); // ['Habari yako? Karibu Weledi']

Why this SDK

  • API-key auth - one line to get started.
  • Typed models - Bot, ChatResponse, ChatState, IntentPrediction; no more digging through raw maps.
  • Typed errors - a sealed SarufiException hierarchy you can exhaustively switch on.
  • Self-documenting - print(SarufiDocs.chat) prints an endpoint's fields and an example, right in your terminal.
  • Testable - inject your own http.Client; unit tests never touch the network.
  • Pure Dart - no Flutter dependency, so it runs everywhere Dart does.

Contents

Install

dart pub add sarufi      # or: flutter pub add sarufi
dependencies:
  sarufi: ^1.0.0

Then import it:

import 'package:sarufi/sarufi.dart';

Quick start

import 'package:sarufi/sarufi.dart';

Future<void> main() async {
  final sarufi = Sarufi('YOUR_API_KEY');

  // Create a bot with intents (example phrases) and a flow (state machine).
  final bot = await sarufi.createBot(
    name: 'Weledi Bot',
    description: 'A friendly Swahili student assistant',
    intents: {
      'salamu': ['habari', 'mambo', 'hi'],
    },
    flow: {
      'salamu': {
        'message': ['Habari! Karibu Weledi'],
        'next_state': 'end',
      },
    },
  );

  // Chat with it.
  final reply = await bot.respond(message: 'Habari');
  print(reply.messages);  // ['Habari! Karibu Weledi']
  print(reply.nextState); // 'end'

  sarufi.close(); // release the HTTP client when you're done
}

Authentication

Grab an API key from the Sarufi dashboard and pass it to the constructor. Every request is authenticated with a Bearer token automatically.

final sarufi = Sarufi(
  'YOUR_API_KEY',
  timeout: const Duration(seconds: 30), // optional (default: 120s)
);

Creating bots

Bots are defined by intents (a map of intent name to example phrases) and a flow (the conversation state machine).

final bot = await sarufi.createBot(
  name: 'iBank',
  industry: 'banking',
  intents: {
    'greeting': ['habari', 'mambo', 'hi'],
    'balance': ['angalia salio', 'check my balance'],
  },
  flow: {
    'greeting': {
      'message': ['Karibu iBank! Naweza kukusaidiaje?'],
      'next_state': 'end',
    },
  },
);

From JSON / YAML files

Keep your training data in files and load it with the IO helper (uses dart:io, so it's available everywhere except Flutter web):

import 'package:sarufi/sarufi.dart';
import 'package:sarufi/sarufi_io.dart';

final bot = await sarufi.createFromFile(
  intents: 'data/intents.yaml',
  flow: 'data/flow.yaml',
  metadata: 'data/metadata.json', // name, description, industry, ...
);

// Re-upload after editing the files:
await sarufi.updateFromFile(id: bot.id!, flow: 'data/flow.yaml');

Conversations

// Reuse a chat id to keep context across turns.
const chatId = 'user-123';
await sarufi.chat(botId: 42, chatId: chatId, message: 'Habari');
await sarufi.chat(botId: 42, chatId: chatId, message: 'Nataka kukopa');

// Where are we in the flow?
final state = await sarufi.chatStatus(botId: 42, chatId: chatId);
print('${state.currentState} -> ${state.nextState}');

// Classify a message without advancing the conversation.
final p = await sarufi.predictIntent(botId: 42, message: 'nataka salio langu');
print('${p.intent} (${p.confidence})');

// Send over WhatsApp instead of the default channel.
await sarufi.chat(botId: 42, message: 'Hi', channel: Channel.whatsapp);

The Bot object

getBot, bots, createBot and updateBot all return a Bot: a typed wrapper you can act on directly. Mutating helpers call the API and return a fresh Bot (no hidden network calls behind a plain setter).

final bot = await sarufi.getBot(42);

bot.name;        // 'Weledi Bot'
bot.intents;     // Map<String, dynamic>?
bot.flows;       // Map<String, dynamic>?

await bot.respond(message: 'Habari');            // ChatResponse
await bot.predictIntent('nataka kozi');          // IntentPrediction
final trained = await bot.addIntent({'bye': ['kwaheri']}); // updated Bot
final renamed = await bot.rename('Weledi v2');   // updated Bot
await bot.delete();

Self-documenting API

Every operation ships with inline docs, no browser needed. Print one endpoint, or all of them:

print(SarufiDocs.chat);
┌─ chat
│  POST conversation
│  Send a message to a bot and get its reply.
│
│  Set channel to whatsapp to route through the conversation/whatsapp
│  endpoint. Reuse chat_id across turns to keep conversation context.
│
│  Request body:
│    bot_id                  number   required
│    chat_id                 string   optional  Conversation session id (auto-generated if omitted).
│    message                 string   required
│    message_type            string   optional  [text, image, audio, video, file]
│
│  Response:
│    message                 array    optional  The bot's replies.
│    next_state              string   optional  The state moved to.
│
│  Example:
│    final reply = await sarufi.chat(botId: 42, chatId: 'user-1', message: 'Habari');
│    print(reply.messages);
└─
print(Sarufi.docs); // every operation, in reading order

Error handling

Every call throws a typed SarufiException on failure. The base type is sealed, so a switch is exhaustive and the analyzer keeps you honest:

try {
  await sarufi.getBot(42);
} on SarufiException catch (e) {
  final reason = switch (e) {
    SarufiAuthException()       => 'Check your API key',
    SarufiNotFoundException()   => 'That bot does not exist',
    SarufiValidationException() => 'Bad request: ${e.message}',
    SarufiNetworkException()    => 'No connection',
    SarufiServerException()     => 'Sarufi is having a bad day',
    SarufiApiException()        => e.message,
  };
  print(reason);
}

Each exception carries message, statusCode and the decoded body.

Logging

Nothing is printed by default. Turn on diagnostics during development:

Sarufi.enableConsoleLogging(); // built on the `logging` package

Testing

Inject a mock http.Client and your integration never hits the network:

import 'package:http/testing.dart';

final sarufi = Sarufi(
  'test-key',
  httpClient: MockClient((req) async => http.Response('{"id":1}', 200)),
);

API reference

Method HTTP Returns
createBot(...) POST chatbot Bot
updateBot(id, ...) PUT chatbot/{id} Bot
getBot(id) GET chatbot/{id} Bot
bots() GET chatbots List<Bot>
deleteBot(id) DELETE chatbot/{id} Map
chat(...) POST conversation ChatResponse
chatStatus(...) POST conversation/status ChatState
updateConversationState(...) POST conversation-state Map
predictIntent(...) POST predict/intent IntentPrediction

Base URL: https://developers.sarufi.io/.

Examples

Runnable examples live in example/:

Example What it shows
example.dart End-to-end tour: create, chat, list, train, delete
weledi/ Flagship - a bilingual student bot: browse courses, enroll, pay
insurance/ Build an insurance bot from JSON files
kubeti/ Swahili betting bot, created/updated from JSON
dart run example/weledi/weledi.dart

Other Sarufi SDKs

Prefer another language? Sarufi has official and community SDKs:

Credits

Built and maintained by Brightius Kalokola at TRIXA. Thanks to the Sarufi and Neurotech Africa team for the platform.

Support

Please open an issue on GitHub, or contact the maintainer at brightius@trixa.net

Licensed under the MIT License.

Libraries

sarufi
A modern, null-safe Dart & Flutter SDK for the Sarufi Conversational AI platform.
sarufi_io
File-based helpers for building bots from JSON/YAML config on disk.