API Studio
A powerful in-app API debugging and inspection tool for Flutter — like Charles Proxy, Chucker, and Alice — built specifically for Dio.
Zero configuration. Drop it in, add one interceptor, and every HTTP request your app makes is captured, stored, and browsable — without leaving the app. Go further with Slack failure alerts, live internet connectivity monitoring, and real-time failed API counters — all opt-in with a single flag.
✨ Features at a Glance
| Feature | Description | |
|---|---|---|
| 🔍 | Auto-capture | Intercepts all Dio requests — URL, method, headers, body, query params, form data, multipart |
| 💾 | Persistent logs | Powered by Hive — logs survive hot restart, app restart, and device reboots |
| 📊 | Inspector dashboard | Search, filter by method/status, sort — handles 10,000+ logs smoothly |
| 🗂 | Detail view | Overview / Request / Response / Error tabs with copy buttons on every section |
| ✏️ | Edit & Run | Modify any captured request and re-execute it — original log is never mutated |
| 🧾 | CURL generator | One tap to copy or share any request as a curl command |
| 📤 | Export | Export all logs as JSON or TXT and share via the system share sheet |
| 🎨 | Themeable | Full light/dark support + ApiInspectorThemeData for custom colors and radius |
| 🔔 | Slack Alerts | Push rich failure alerts to a Slack channel via Incoming Webhooks — zero extra packages |
| 🌐 | Internet stream | Opt-in broadcast stream that emits true/false on every connectivity change |
| 📉 | Failed API counter | Opt-in live count + stream of all failed requests — drive a badge on your debug menu |
| 🔐 | Sensitive data masking | Auth headers, tokens, passwords, and API keys are automatically redacted before any alert is sent |
| ⚙️ | Configurable init | Control maxStoredLogs, requestTimeout, connectivity stream, and failed-count stream from init() |
Screenshots
Installation
Add to your pubspec.yaml:
dependencies:
api_studio: ^0.0.1
Then run:
flutter pub get
Quick Start
Step 1 — Initialise before runApp
import 'package:api_studio/api_studio.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await ApiStudio.init();
runApp(const MyApp());
}
Step 2 — Add the interceptor to your Dio instance
final dio = Dio();
dio.interceptors.add(ApiStudio.interceptor);
Step 3 — Open the inspector
Trigger it from any button, FAB, or shake gesture:
ApiStudio.show(context);
That's it. Every request made through that Dio instance is now captured and browsable.
Full Example
import 'package:api_studio/api_studio.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await ApiStudio.init();
final dio = Dio();
dio.interceptors.add(ApiStudio.interceptor);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('My App'),
actions: [
IconButton(
icon: const Icon(Icons.bug_report_rounded),
onPressed: () => ApiStudio.show(context),
),
],
),
),
);
}
}
See the complete working example in the /example folder.
Configuration
All parameters are optional — sensible defaults apply if not provided.
await ApiStudio.init(
// Maximum number of logs retained on disk (oldest pruned automatically)
maxStoredLogs: 500, // default: 10,000
// Timeout applied to re-run requests from Edit & Run
requestTimeout: Duration(seconds: 15), // default: 30s
// Enable the internet connectivity broadcast stream
enableConnectivityStream: true, // default: false
// Enable the failed API count stream
enableFailedApiStream: true, // default: false
// Slack + callback alert configuration
notificationConfig: NotificationConfig(
slackWebhook: 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL',
onApiFailed: (log) => debugPrint('API failed: ${log.url}'),
),
// Custom theme
theme: ApiInspectorThemeData(
primaryColor: Colors.teal,
borderRadius: 16,
),
);
🔔 Slack Failure Alerts
API Studio can push a rich, structured alert to any Slack channel the moment an API call fails — no third-party packages required.
Setup
- Create a Slack Incoming Webhook for your workspace.
- Pass the webhook URL through
NotificationConfigininit():
await ApiStudio.init(
notificationConfig: NotificationConfig(
slackWebhook: 'https://hooks.slack.com/services/YOUR/WEBHOOK/URL',
),
);
What each alert includes
| Field | Description |
|---|---|
| Method | HTTP verb (GET, POST, PUT, DELETE, …) |
| Endpoint | Path extracted from the full URL |
| Status code | HTTP response code, or N/A for network errors |
| Duration | Request round-trip time in ms or seconds |
| Error message | Truncated to 300 characters to keep alerts readable |
| Internet | Whether the device was online at failure time |
| Timestamp | Exact date and time of the failure |
| App / platform / device | App version, OS, and device model |
Sensitive data protection
All alerts are scrubbed automatically before dispatch:
- Request headers containing
Authorization,Cookie,token,api-key,secret,jwt, and 30+ other keys are replaced with*** - Bearer tokens, Basic auth strings, JWTs, and long API keys are detected by pattern and masked
- Request/response body fields such as
password,otp,token,secret, andrefresh_tokenare redacted from JSON payloads
Your secrets never leave the device in plain text.
Custom callback
Register an onApiFailed callback alongside (or instead of) Slack to handle failures in your own way:
notificationConfig: NotificationConfig(
onApiFailed: (ApiLogNotificationModel log) {
// Send to Firebase Crashlytics, Sentry, your own backend, etc.
FirebaseCrashlytics.instance.log('API failed: ${log.url} — ${log.statusCode}');
},
),
Teams support is coming — Microsoft Teams webhook integration will be added in the next release.
🌐 Internet Connectivity Stream
Enable a lightweight polling-based connectivity monitor that broadcasts true/false whenever the device's internet state changes. No external plugin needed — works on all platforms.
await ApiStudio.init(
enableConnectivityStream: true,
);
One-time check
final bool isOnline = await ApiStudio.isInternetConnected();
Live stream
ApiStudio.internetConnectivityStream.listen((bool isConnected) {
if (!isConnected) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('No internet connection')),
);
}
});
The stream emits only on state changes (online → offline or offline → online), so listeners are not flooded with redundant events.
📉 Failed API Count
Track the running total of failed requests in real time. Useful for displaying a badge on your debug menu or triggering alerts after a threshold is crossed.
await ApiStudio.init(
enableFailedApiStream: true,
);
Synchronous snapshot
final int count = ApiStudio.failedApiCount;
Reactive stream
ApiStudio.failedApiCountStream.listen((int failedCount) {
setState(() => _badgeCount = failedCount);
});
Badge example
StreamBuilder<int>(
stream: ApiStudio.failedApiCountStream,
builder: (context, snapshot) {
final count = snapshot.data ?? 0;
return Badge(
isLabelVisible: count > 0,
label: Text('$count'),
child: IconButton(
icon: const Icon(Icons.bug_report_rounded),
onPressed: () => ApiStudio.show(context),
),
);
},
)
Theming
// Light (default)
const ApiInspectorThemeData()
// Dark
ApiInspectorThemeData.dark()
// Match the app's brightness automatically
ApiInspectorThemeData.fromBrightness(Theme.of(context).brightness)
// Fully custom
ApiInspectorThemeData(
primaryColor: Color(0xFF6C63FF),
backgroundColor: Colors.white,
surfaceColor: Colors.white,
cardColor: Colors.white,
borderRadius: 12.0,
isDark: false,
)
Pass a theme per show() call to override the init-time theme for that session only:
ApiStudio.show(
context,
theme: ApiInspectorThemeData.dark(),
);
Edit & Run
Open any captured request → tap the pencil icon → Edit & Run:
- All fields are pre-filled with the original request data
- Modify URL, HTTP method, headers, query params, or body
- Tap RUN — a new log is created and tagged with an EDITED badge
- The original log is never modified
Architecture
Built with Clean Architecture + SOLID principles:
lib/src/
├── core/ ← Constants, errors, extensions, use-case base, utils
├── domain/ ← Entities, repository interfaces, use cases (pure Dart)
├── data/ ← Hive models, TypeAdapter, datasource, repo impl, interceptor
├── presentation/ ← BLoC × 4, screens × 3, widgets × 7
├── theme/ ← ApiInspectorTheme, AppColors, AppTextStyles, Dimensions
├── notification/ ← NotificationConfig, SlackProvider, NotificationService, SensitiveDataMasker
└── services/ ← DiService (DI wiring), ConnectivityService, FailedApiCountService, ExportService
| Concern | Solution |
|---|---|
| State management | flutter_bloc — feature-scoped BLoCs, Equatable states |
| Storage | Hive + generated TypeAdapter, 10k+ logs with pagination |
| Performance | ListView.builder, RepaintBoundary per card, buildWhen guards |
| Interception | Dio Interceptor — captures request / response / error phases |
| CURL export | Pure Dart utility, zero extra dependencies |
| Theming | InheritedWidget-based ApiInspectorTheme |
| Slack alerts | dart:io HttpClient — no third-party HTTP package |
| Connectivity | Polling-based, platform-conditional implementation (native vs. web) |
| Sensitive data | Pattern + key-list based masker applied before every notification dispatch |
Requirements
| Minimum | |
|---|---|
| Flutter | 3.19+ |
| Dart | 3.3+ |
| Dio | 5.x |
| Platforms | Android, iOS, macOS, Linux, Windows, Web |
Running the Example App
cd example
flutter pub get
flutter run
Tap the bug icon (🐛) in the app bar to open the inspector.
Running Tests
flutter test
After changing the Hive model, regenerate the adapter:
dart run build_runner build --delete-conflicting-outputs
Roadmap
xDio interception & persistent Hive storagexInspector dashboard — search, filter, sortxEdit & Run with EDITED badgexCURL generator & JSON/TXT exportxSlack failure alerts with sensitive data maskingxInternet connectivity streamxFailed API count streamMicrosoft Teams webhook alerts (coming next)Shake-to-open gesture support
Contributing
- Follow the existing Clean Architecture layering — keep the domain layer free of Flutter/Hive imports
- Keep the notification layer provider-agnostic — new providers implement
NotificationProviderand are registered inNotificationService - File bugs via the issue tracker
- PRs welcome
License
MIT