FlowApps are plugin-style integrations that extend your agents with external service capabilities. Unlike providers (LLM, TTS, STT) that handle core AI functions, FlowApps add custom actions and data sources to the conversation flow.
What are FlowApps?
A FlowApp is a self-contained integration module that provides:
- Actions - Operations the agent can execute (e.g., book a meeting, send an email)
- Data fetchers - Dynamic data sources for UI dropdowns (e.g., list of calendars)
- Integration credentials - Secure storage of API keys and configuration
FlowApps appear as draggable nodes in the Script Builder, allowing non-technical users to add complex integrations without code.
Architecture
Core interface
public interface IFlowApp
{
string AppKey { get; } // Unique identifier (e.g., "cal_com")
string Name { get; } // Display name (e.g., "Cal.com")
string IconUrl { get; } // App icon URL
string? IntegrationType { get; } // Links to admin integration
IReadOnlyList<IFlowAction> Actions { get; } // Available actions
IReadOnlyList<IFlowDataFetcher> DataFetchers { get; } // Data sources
}
Action interface
public interface IFlowAction
{
string ActionKey { get; } // Unique action ID
string Name { get; } // Display name
string Description { get; } // Help text
Task<ActionExecutionResult> ExecuteAsync(
Dictionary<string, object> inputs, // User-provided parameters
Dictionary<string, string> credentials // Integration credentials
);
}
Data fetcher interface
public interface IFlowDataFetcher
{
string FetcherKey { get; } // Unique fetcher ID
string Name { get; } // Display name
Task<List<DynamicOption>> FetchAsync(
Dictionary<string, string> credentials // Integration credentials
);
}
Built-in FlowApps
App Key: cal_com
Implementation: CalComApp.cs
Integrate with Cal.com for meeting scheduling and calendar management.
Available actions
Book meeting
Get slots
Cancel booking
Reschedule booking
Get all bookings
Other actions
Action Key: book_meetingSchedule a new meeting on a Cal.com calendar.Input parameters:| Parameter | Type | Required | Description |
|---|
eventTypeId | number | Yes | Event type ID from Cal.com |
start | datetime | Yes | Meeting start time (ISO 8601) |
attendeeName | text | Yes | Attendee full name |
attendeeEmail | email | Yes | Attendee email address |
attendeePhone | text | No | Attendee phone number |
notes | text | No | Meeting notes |
timezone | text | No | Timezone (default: UTC) |
Output:{
"success": true,
"bookingId": "abc123",
"bookingUid": "unique-uid",
"startTime": "2024-03-15T14:00:00Z",
"endTime": "2024-03-15T15:00:00Z",
"meetingUrl": "https://meet.google.com/xxx-yyyy-zzz"
}
Example use case:
Caller: “I’d like to schedule a consultation for next Tuesday at 2 PM.”
Agent: Executes book_meeting action with detected date/time
Agent: “Perfect! I’ve scheduled your consultation for Tuesday, March 15th at 2:00 PM. You’ll receive a confirmation email with the meeting link.”
Action Key: get_slotsRetrieve available time slots for scheduling.Input parameters:| Parameter | Type | Required | Description |
|---|
eventTypeId | number | Yes | Event type ID |
startDate | date | Yes | Start of date range |
endDate | date | Yes | End of date range |
timezone | text | No | Timezone for results |
Output:{
"success": true,
"slots": [
{
"time": "2024-03-15T14:00:00Z",
"available": true
},
{
"time": "2024-03-15T15:00:00Z",
"available": true
}
]
}
Action Key: cancel_bookingCancel an existing meeting.Input parameters:| Parameter | Type | Required | Description |
|---|
bookingId | text | Yes | Booking ID to cancel |
cancellationReason | text | No | Reason for cancellation |
Output:{
"success": true,
"message": "Booking cancelled successfully"
}
Action Key: reschedule_bookingMove an existing meeting to a new time.Input parameters:| Parameter | Type | Required | Description |
|---|
bookingId | text | Yes | Booking ID to reschedule |
newStartTime | datetime | Yes | New start time (ISO 8601) |
rescheduleReason | text | No | Reason for rescheduling |
Output:{
"success": true,
"bookingId": "abc123",
"newStartTime": "2024-03-16T14:00:00Z",
"newEndTime": "2024-03-16T15:00:00Z"
}
Action Key: get_all_bookingsRetrieve list of bookings.Input parameters:| Parameter | Type | Required | Description |
|---|
status | select | No | Filter: upcoming, past, cancelled |
limit | number | No | Max results (default: 10) |
Output:{
"success": true,
"bookings": [
{
"id": "abc123",
"title": "Consultation",
"startTime": "2024-03-15T14:00:00Z",
"attendees": ["[email protected]"]
}
]
}
Additional actions:
get_booking - Retrieve single booking details
add_guests - Add attendees to existing booking
mark_absent - Mark attendee as no-show
Data fetchers
get_event_types - Fetches list of available event types
Used to populate dropdowns in the Script Builder when configuring booking actions.
Output:
[
{
"key": "123",
"value": "30 Minute Consultation",
"metadata": {
"duration": 30,
"description": "Quick consultation call"
}
},
{
"key": "456",
"value": "1 Hour Strategy Session",
"metadata": {
"duration": 60,
"description": "Deep-dive strategy planning"
}
}
]
Configuration
To use Cal.com integration:
- Create Cal.com account at https://cal.com
- Generate API key in Settings → Developer → API Keys
- Add integration in Iqra AI Admin Dashboard:
- Integration Type:
cal_com
- API Key: Your Cal.com API key
- Configure in agent - FlowApp becomes available in Script Builder
FlowApp discovery and registration
FlowApps are automatically discovered at runtime:
- Assembly scanning -
FlowAppManager uses reflection to find IFlowApp implementations
- Dependency injection - Apps can request services (e.g.,
IHttpClientFactory)
- Registration - Apps added to in-memory registry keyed by
AppKey
- Permission loading - Database permissions merged with app definitions
private void InitializeApps()
{
var appType = typeof(IFlowApp);
var types = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(p => appType.IsAssignableFrom(p) && !p.IsInterface && !p.IsAbstract);
foreach (var type in types)
{
var appInstance = (IFlowApp)ActivatorUtilities.CreateInstance(_serviceProvider, type);
_apps.Add(appInstance.AppKey, appInstance);
}
}
See FlowAppManager.cs:59-95 for implementation.
Using FlowApps in scripts
FlowApps appear as action nodes in the Script Builder:
- Drag FlowApp action from sidebar into canvas
- Configure inputs using static values or variables
- Map credentials - System auto-injects integration credentials
- Handle output - Access returned data via output ports
Example: Booking flow
[User Message] → [LLM: Extract date/time] → [Cal.com: Get Slots] → [Condition: Slots available?]
↓ Yes ↓ No
[Cal.com: Book Meeting] [LLM: "No availability, suggest alternatives"]
↓
[LLM: "Meeting confirmed"]
Variable mapping
FlowApp inputs can use conversation variables:
{
"eventTypeId": 123,
"start": "{{variables.extracted_datetime}}",
"attendeeName": "{{variables.caller_name}}",
"attendeeEmail": "{{variables.caller_email}}"
}
Variables are resolved at execution time using the Scriban template engine.
Creating custom FlowApps
Step 1: Define the app class
using IqraCore.Interfaces.Integration;
using IqraCore.Interfaces.FlowApp;
namespace IqraInfrastructure.Managers.FlowApp.Apps.MyApp
{
public class MyCustomApp : IFlowApp
{
public string AppKey => "my_custom_app";
public string Name => "My Custom App";
public string IconUrl => "https://example.com/icon.png";
public string? IntegrationType => "my_custom_app"; // Matches admin integration
private readonly IHttpClientFactory _httpClientFactory;
public IReadOnlyList<IFlowAction> Actions { get; }
public IReadOnlyList<IFlowDataFetcher> DataFetchers { get; }
public MyCustomApp(IHttpClientFactory httpClientFactory)
{
_httpClientFactory = httpClientFactory;
Actions = new List<IFlowAction>
{
new MyCustomAction(this)
};
DataFetchers = new List<IFlowDataFetcher>();
}
}
}
Step 2: Implement actions
public class MyCustomAction : IFlowAction
{
private readonly MyCustomApp _app;
public string ActionKey => "my_action";
public string Name => "My Custom Action";
public string Description => "Does something cool";
public MyCustomAction(MyCustomApp app)
{
_app = app;
}
public async Task<ActionExecutionResult> ExecuteAsync(
Dictionary<string, object> inputs,
Dictionary<string, string> credentials)
{
// Extract inputs
var apiKey = credentials["apiKey"];
var param1 = inputs["param1"].ToString();
// Call external API
var client = _app.CreateClient();
client.DefaultRequestHeaders.Add("Authorization", $"Bearer {apiKey}");
var response = await client.PostAsync("/api/endpoint", /* payload */);
var result = await response.Content.ReadAsStringAsync();
// Return result
return new ActionExecutionResult
{
Success = true,
Data = new Dictionary<string, object>
{
{ "result", result }
}
};
}
}
Step 3: Define integration in admin
Create integration definition in IqraInfrastructure/Managers/Integrations/IntegrationsManager.cs:
new IntegrationData
{
Name = "My Custom App",
Description = "Integrate with My Custom App",
Type = new List<string> { "my_custom_app" },
Fields = new List<IntegrationFieldData>
{
new IntegrationFieldData
{
Id = "apiKey",
Name = "API Key",
Type = "password",
Required = true,
IsEncrypted = true
}
},
Help = new IntegrationHelpData
{
HelpUrl = "https://docs.example.com/api-key",
Description = "Get your API key from the developer dashboard"
}
}
Step 4: Test and deploy
- Restart application - FlowApp auto-registers
- Add integration in Admin Dashboard
- Test in Script Builder - Drag action node and configure
- Verify execution - Test with real API calls
FlowApps support dependency injection. Request any registered service (e.g., ILogger, IHttpClientFactory, IMemoryCache) in the constructor.
Permission management
FlowApps support granular permission control:
App-level permissions
Disable entire app:
{
"appKey": "cal_com",
"disabledAt": "2024-03-15T00:00:00Z",
"disabledPublicReason": "Temporarily unavailable for maintenance"
}
Action-level permissions
Disable specific actions:
{
"appKey": "cal_com",
"actionPermissions": {
"cancel_booking": {
"disabledAt": "2024-03-15T00:00:00Z",
"disabledPublicReason": "Cancellation temporarily disabled"
}
}
}
Permissions are stored in MongoDB (FlowAppData collection) and merged with app definitions at runtime.
Security considerations
Credential handling
- Encryption - API keys encrypted with AES-256 before storage
- Scoped access - Credentials only accessible during action execution
- No logging - Credentials never logged or exposed in errors
FlowApps should validate all inputs:
if (!inputs.ContainsKey("requiredParam"))
{
return new ActionExecutionResult
{
Success = false,
ErrorCode = "MISSING_PARAM",
ErrorMessage = "Required parameter 'requiredParam' not provided"
};
}
Rate limiting
Implement rate limiting for external API calls:
private static readonly SemaphoreSlim _rateLimiter = new(5, 5); // 5 concurrent requests
public async Task<ActionExecutionResult> ExecuteAsync(...)
{
await _rateLimiter.WaitAsync();
try
{
// Execute API call
}
finally
{
_rateLimiter.Release();
}
}
Best practices
Action design
- Atomic operations - Each action should do one thing well
- Clear naming - Use verb + noun (e.g.,
book_meeting, not handle_calendar)
- Descriptive errors - Return actionable error messages
- Idempotency - Support retries without side effects where possible
Data fetcher design
- Caching - Cache expensive fetcher results using
IMemoryCache
- Pagination - Limit results to reasonable sizes (100-1000 items)
- Filtering - Support filtering via input parameters
Testing
- Unit tests - Test action logic in isolation
- Integration tests - Verify API integration with real credentials
- Error scenarios - Test network failures, API errors, invalid inputs
Roadmap
Planned FlowApp integrations:
- Email - SendGrid, Mailgun, AWS SES
- CRM - Salesforce, HubSpot, Pipedrive
- Ticketing - Zendesk, Freshdesk, Intercom
- Payments - Stripe, Square, PayPal
- SMS - Twilio, Vonage, Telnyx
- Databases - PostgreSQL, MySQL, Airtable
Next steps
Build conversation flows
Use FlowApps in the Script Builder
Integration management
Configure integration credentials
Custom actions
Build your own FlowApp integrations
Script variables
Map conversation data to action inputs