Alexandr Chibilyaev on the architecture behind AACFlow's 213+ integration blocks — how we designed a system where every API becomes a drag-and-drop building block for AI agents.
An AI agent without tools is just a chatbot. An AI agent with tools is infrastructure. The difference? Integrations. And at AACFlow, we've built 213+ of them. But the number isn't the story. The architecture behind them is.
Connecting an AI agent to Gmail is fundamentally different from connecting it to a PostgreSQL database. Gmail has OAuth, rate limits, paginated responses, and a REST API with 50+ endpoints. PostgreSQL has connection pooling, SQL parsing, type coercion, and transaction management.
Every integration platform faces the same question: how do you make 200+ completely different APIs feel like one unified system?
category: string // 'communication', 'database', 'ai', etc.
7
8
inputs: FieldConfig[]// Typed input fields
9
outputs: FieldConfig[]// Typed output fields
10
11
auth: AuthConfig // How to authenticate
12
handler: Handler // The execution function
13
errorMap: ErrorMap // Error transformation rules
14
15
// UI metadata
16
color: string
17
subBlocks?: BlockConfig[]// Nested sub-blocks for complex integrations
18
}
A new developer adding an integration fills out this config and writes the handler. Everything else — validation, error handling, pagination, UI rendering — is handled by the framework.
Some services are too complex for a single block. Gmail, for example, has "send email," "search emails," "manage labels," "manage drafts," "manage filters." Each of these is a distinct operation with different inputs and outputs.
Our answer: sub-blocks. A top-level "Gmail" block contains sub-blocks for each operation. The user picks "Gmail" from the block palette, then selects "Send Email" from a dropdown. The sub-block inherits the parent's authentication but has its own input schema, output schema, and handler.
This nesting keeps the block palette manageable — 30 top-level blocks, not 200 — while exposing the full surface area of each API.
Not all blocks are tools, and not all tools are blocks. Confused? Here's the distinction:
Blocks are visual canvas elements — the things users drag onto the workflow editor. They have UI config, icons, colors, categories.
Tools are the execution-layer representation — they're what the AI agent "sees" when deciding what to do. A tool has a name, description, and parameter schema that gets injected into the LLM context.
When an agent reasons about what to do, it sees the tool list. When it decides "I need to send an email," it calls gmail_send. The block's handler executes the actual API call. The result flows back into the agent's context.
For the most complex integrations, a block isn't enough. Some services require:
Webhook registration and management
Real-time event streaming
Bidirectional sync (AACFlow ↔ external service)
Complex state management across multiple API calls
These become connectors — a superset of blocks that includes event handling, webhook management, and synchronization logic. Currently, we have connectors for services like Fivepost (social media management) and Poster (restaurant POS system), where the integration needs go beyond simple API calls.
A connector is still a block — it appears on the canvas and can be wired into workflows — but it also has background processes, event listeners, and sync loops.
OAuth 2.0 — with token refresh, scope management, and revocation
API Key — simple, stored encrypted
Basic Auth — username/password with encryption
Custom headers — for services that use non-standard auth
mTLS — for enterprise private cloud deployments
Credentials are stored encrypted at rest. The executor injects them at runtime — the block handler receives a ready-to-use authenticated client, never raw credentials.
External APIs throw errors in wildly different formats. Gmail returns JSON with an error.code field. PostgreSQL throws database error codes. A REST API might return XML with a custom error envelope.
Every block includes an errorMap that transforms provider-specific errors into standardized AACFlow errors:
Many APIs paginate their responses. If your agent asks for "all customers" and the API returns only the first 100, your automation is silently broken.
Every block can declare a pagination strategy:
Offset-based — ?page=1&limit=100
Cursor-based — ?after=cursor_token
Link-header — parse Link: headers for next page URLs
Time-based — ?since=2024-01-01&until=2024-01-31
The framework handles the pagination loop. The block handler just returns results for one page. The framework automatically requests subsequent pages and merges the results.
Every API has a quirk. Rate limits that aren't documented. Pagination that breaks on page 1,000. Auth tokens that expire 5 minutes early. You can't predict these. You discover them by building and testing.
Error handling is 40% of the work. The "happy path" for an integration — the successful API call — is maybe 20 lines of code. The error handling, retry logic, edge cases, and pagination? 200 lines. Every time.
OAuth is still painful. Even with libraries, every provider has subtle differences in token refresh behavior, scope naming, and redirect URI validation. We spent more time on auth than on any other integration concern.
Type safety saves you. Every block's input and output is typed with Zod. The executor validates data at every boundary. This catches integration bugs before they reach production — and before they cost users money.