SDK: FlapjackClient
Complete method reference for FlapjackClient — agents, threads, streaming, knowledge, MCP servers, integrations, plans, runners, skills, analytics, and marketplace.
FlapjackClient is the main class for interacting with the Flapjack API programmatically.
import { FlapjackClient } from '@flapjack/sdk';
const client = new FlapjackClient({
apiKey: process.env.FLAPJACK_API_KEY!,
});
Agents
listAgents(orgId?)
List all agents in your organization.
const agents: Agent[] = await client.listAgents();
Returns: Promise<Agent[]>
type Agent = {
id: string;
org_id: string;
name: string;
description: string | null;
/** Populated by getAgent() but not by listAgents(). */
stable_preamble?: string;
default_model: string;
/** Populated by getAgent() but not by listAgents(). */
temperature?: number;
created_at: string;
published_chats?: { slug: string; is_active: boolean }[];
computer?: ComputerConfig | { enabled: false };
tool_profiles?: ToolProfiles | null;
default_profile?: string | null;
};
getAgent(agentId)
Get a specific agent by ID.
const agent: Agent = await client.getAgent('agent-id');
| Name | Type | Description |
|---|---|---|
agentId | string | The agent's UUID |
Returns: Promise<Agent> — throws if not found.
createAgent(options)
Create a new agent.
const agent = await client.createAgent({
name: 'Support Bot',
stablePreamble: 'You are a helpful support agent.',
defaultModel: 'gpt-5.4',
});
| Name | Type | Description |
|---|---|---|
options.name | string | Display name (required) |
options.description | string? | What this agent does |
options.stablePreamble | string? | System prompt |
options.defaultModel | string? | LLM model ID (defaults to gpt-5.4) |
options.orgId | string? | Target org (multi-org only) |
Returns: Promise<Agent>
updateAgent(agentId, options)
Update an existing agent. Only changed fields are required.
await client.updateAgent('agent-id', { name: 'Updated Bot' });
Returns: Promise<Agent>
deleteAgent(agentId)
Delete an agent. Threads are not automatically deleted.
await client.deleteAgent('agent-id');
Returns: Promise<void>
Agent Feature Configs
Get or update per-agent feature settings. All config methods follow the same pattern:
// Read config
const config = await client.getWebConfig('agent-id');
// Update config
await client.updateWebConfig('agent-id', { enabled: true, searchEnabled: true });
| Method | Config Type | Key Fields |
|---|---|---|
getWebConfig(agentId) / updateWebConfig(agentId, config) | WebConfig | enabled, searchEnabled, researchEnabled, readEnabled, crawlEnabled |
getMemoryConfig(agentId) / updateMemoryConfig(agentId, config) | MemoryConfig | enabled, autoCollect, autoInject, injectCount, maxMemories, agentScope, threadScope, resourceScope |
getPlanConfig(agentId) / updatePlanConfig(agentId, config) | PlanConfig | enabled, remindOnTurnEnd, dependenciesEnabled |
getCompactionConfig(agentId) / updateCompactionConfig(agentId, config) | CompactionConfig / UpdateCompactionConfig | enabled, anthropicTriggerTokens (min 50000), anthropicInstructions?, anthropicPauseAfter?, openaiCompactThreshold (min 1000). See Compaction. |
getComputerConfig(agentId) / updateComputerConfig(agentId, config) | ComputerConfig | enabled, mode (ephemeral/persistent/custom), scope, sizeClass |
getDelegatedConfig(agentId) / updateDelegatedConfig(agentId, config) | DelegatedConfig / UpdateDelegatedConfig | Getter: url, timeout_ms. Updater: url, secret (required), timeoutMs? |
getCredentialConfig(agentId) / updateCredentialConfig(agentId, config) | CredentialConfig / UpdateCredentialConfig | Getter: enabled, resolver_url, timeout_ms. Updater: enabled, resolverUrl?, timeoutMs? |
All getters return a Promise of the config type. All updaters use PUT and return the saved config.
Agent MCP Bindings
Attach or detach MCP servers from an agent.
listAgentMcps(agentId)
const mcps: AgentMcp[] = await client.listAgentMcps('agent-id');
Returns: Promise<AgentMcp[]>
attachMcp(agentId, mcpServerId, allowedTools?)
await client.attachMcp('agent-id', 'mcp-server-id', ['tool_a', 'tool_b']);
| Name | Type | Description |
|---|---|---|
agentId | string | The agent's UUID |
mcpServerId | string | MCP server UUID |
allowedTools | string[]? | Optional tool filter |
Returns: Promise<AgentMcp>
detachMcp(agentId, mcpServerId)
await client.detachMcp('agent-id', 'mcp-server-id');
Returns: Promise<void>
Agent Integration Bindings
Attach or detach database integrations from an agent.
listAgentIntegrations(agentId)
const integrations: AgentIntegration[] = await client.listAgentIntegrations('agent-id');
Returns: Promise<AgentIntegration[]>
attachIntegration(agentId, integrationId, allowedTables?)
await client.attachIntegration('agent-id', 'integration-id', ['users', 'orders']);
Returns: Promise<AgentIntegration>
detachIntegration(agentId, integrationId)
await client.detachIntegration('agent-id', 'integration-id');
Returns: Promise<void>
Agent Memories
listMemories(agentId, options?)
List stored memories for an agent, optionally filtered by scope.
const result: MemoryListResult = await client.listMemories('agent-id', {
scope: 'agent',
limit: 20,
});
// result.memories, result.total
| Name | Type | Description |
|---|---|---|
options.scope | string? | agent, thread, or resource |
options.resourceType | string? | Filter by resource type |
options.resourceId | string? | Filter by resource ID |
options.limit | number? | Max results |
options.offset | number? | Pagination offset |
Returns: Promise<MemoryListResult> — { memories: Memory[], total: number }
deleteMemories(agentId, filter)
Delete memories matching a filter.
await client.deleteMemories('agent-id', { scope: 'thread', scopeId: 'thread-id' });
Returns: Promise<void>
Threads
createThread(agentIdOrOptions)
Create a new conversation thread. Accepts a simple agent ID string or an options object.
// Simple
const thread = await client.createThread('agent-id');
// With options
const thread = await client.createThread({
agentId: 'agent-id',
title: 'Support Chat',
kind: 'multiplayer',
resourceBindings: [{ type: 'customer', id: 'cust-123' }],
});
| Name | Type | Description |
|---|---|---|
agentId | string | The agent's UUID (required) |
title | string? | Thread title |
kind | 'single' | 'multiplayer'? | Thread mode |
resourceBindings | ResourceBinding[]? | Attach external resource context |
activeProfile | string? | Set the initial tool profile |
systemPromptOverlay | string | string[]? | Text appended to the agent's system prompt for this thread |
orgId | string? | Target org (multi-org only) |
Returns: Promise<Thread>
type Thread = {
id: string;
org_id: string;
agent_id: string;
title: string | null;
kind: 'single' | 'multiplayer';
status?: string;
active_profile?: string | null;
created_at: string;
updated_at?: string;
};
updateThread(threadId, updates)
Update a thread's configuration (active profile, title, etc.).
await client.updateThread('thread-id', { activeProfile: 'triage' });
| Name | Type | Description |
|---|---|---|
threadId | string | The thread's UUID |
updates.activeProfile | string | null? | Set or clear the active tool profile |
updates.title | string? | Update the thread title |
updates.systemPromptOverlay | string | string[] | null? | Update or clear the thread-level system prompt overlay |
Returns: Promise<Thread>
stopThread(threadId)
Stop a streaming response in progress.
const result = await client.stopThread('thread-id');
// { ok: true, stopped: true }
Returns: Promise<{ ok: boolean; stopped: boolean }>
listThreads(agentId, options?)
List conversation threads for an agent. For API key auth, returns all org threads. For JWT auth (internal dashboard), returns only the current user's threads.
const result = await client.listThreads('agent-id');
console.log(result.threads); // Thread[]
console.log(result.nextCursor); // string | null
// With pagination
const page2 = await client.listThreads('agent-id', {
cursor: result.nextCursor!,
limit: 20,
});
| Name | Type | Description |
|---|---|---|
agentId | string | The agent's UUID |
options.cursor | string? | ISO 8601 timestamp for cursor pagination |
options.limit | number? | Max threads per page (default 50, max 100) |
options.orgId | string? | Target org (multi-org only) |
Returns: Promise<ListThreadsResult>
type ListThreadsResult = {
threads: Thread[];
nextCursor: string | null;
};
getMessages(threadId, options?)
Retrieve message history for a thread. Returns only user and assistant messages in chronological order.
const result = await client.getMessages('thread-id');
console.log(result.messages); // Array of messages
console.log(result.total); // Total message count
// With pagination
const page = await client.getMessages('thread-id', { limit: 50, offset: 50 });
| Name | Type | Description |
|---|---|---|
threadId | string | The thread's UUID |
options.limit | number? | Max messages (default 100, max 200) |
options.offset | number? | Skip N messages (default 0) |
Returns: Promise<GetMessagesResult>
type GetMessagesResult = {
messages: Array<{
id: string;
role: 'user' | 'assistant';
content: string;
sender_name: string | null;
created_at: string;
}>;
total: number;
};
Messages (Streaming)
sendMessage(threadId, content, options?)
Send a message to a thread and stream the agent's response via SSE.
for await (const event of client.sendMessage('thread-id', 'Hello!')) {
// handle events
}
Parameters:
| Name | Type | Description |
|---|---|---|
threadId | string | The thread's UUID |
content | string | The user's message text |
options.tools | ToolDef[]? | Custom tool definitions for client-side execution |
options.onToolCall | Function? | Handler for client-side tool calls (auto-submits results) |
options.modelOverride | string? | Override the agent's default model for this message |
options.webOverrides | object? | Override web tool settings |
options.memoryOverrides | object? | Override memory settings |
options.planOverrides | object? | Override plan settings |
options.computerOverrides | object? | Override computer settings |
options.anthropicOverrides | AnthropicOverrides? | Override Anthropic features (thinking, fallback) — Claude models only |
options.openaiOverrides | OpenAIOverrides? | Override OpenAI prompt cache routing — GPT models only. Fields: disabled? (skip caching), promptCacheKey? (stable shard key, defaults to thread ID), safetyIdentifier? (per-user identifier, defaults to org ID) |
options.displayContent | string? | Content shown in the chat UI instead of the raw content sent to the API. Useful when the API content includes system instructions that should not be shown to the end user |
options.resourceBindings | ResourceBinding[]? | Attach resource context |
options.toolProfile | string? | Use a specific tool profile for this message |
options.systemPromptOverlay | string | string[]? | Text appended to the system prompt for this message only |
options.senderName | string? | Display name (multiplayer) |
options.senderId | string? | Sender user ID (multiplayer) |
Returns: AsyncGenerator<ChatEvent>
type ChatEvent =
| { type: 'meta'; startedAt: string }
| { type: 'token'; delta: string }
| { type: 'tool_call'; tool: { id: string; name: string; arguments: string } }
| { type: 'tool_executing'; tool_name: string }
| { type: 'tool_result'; tool_name: string; tool_call_id: string; result: unknown }
| { type: 'custom'; kind: string; payload: unknown }
| { type: 'auth_challenge'; toolName: string; challenge: { provider: string; redirectUrl: string; description?: string } }
| { type: 'requires_action'; toolCalls: ToolCall[] }
| { type: 'client_event'; kind: string; payload: unknown }
| { type: 'profile_switch_proposal'; target: string; reason: string }
| { type: 'done'; ok: boolean; messageId?: string; content: string; skipped?: boolean; reason?: string }
| { type: 'error'; code: string; detail?: string };
Event sequence: meta → token* → [tool_call → tool_executing → tool_result → custom*]* → done
When custom tools are provided and the agent calls one, a requires_action event is emitted. If onToolCall is set, results are submitted automatically and streaming continues. Otherwise, use submitToolResults() manually.
Note: This method does not throw on HTTP errors. Instead, it yields an error event.
submitToolResults(threadId, toolResults, options?)
Submit results after receiving a requires_action event. Only needed if you don't use onToolCall.
for await (const event of client.submitToolResults('thread-id', [
{ toolCallId: 'call-id', output: '{"result": 42}' },
])) {
// handle continued stream
}
Returns: AsyncGenerator<ChatEvent>
Participants (Multiplayer)
Manage participants in multiplayer threads.
listParticipants(threadId)
const participants: ThreadParticipant[] = await client.listParticipants('thread-id');
Returns: Promise<ThreadParticipant[]>
addParticipant(threadId, userId, options?)
await client.addParticipant('thread-id', 'user-id', {
displayName: 'Alice',
role: 'member',
});
Returns: Promise<ThreadParticipant>
removeParticipant(threadId, userId)
await client.removeParticipant('thread-id', 'user-id');
Returns: Promise<void>
Knowledge
uploadDocument(agentId, file, title)
Upload a document to an agent's knowledge base for RAG.
const doc: KnowledgeDoc = await client.uploadDocument(
'agent-id',
new Blob(['content...'], { type: 'text/plain' }),
'My Document'
);
| Name | Type | Description |
|---|---|---|
agentId | string | The agent's UUID |
file | File | Blob | The document file |
title | string | Display title |
Returns: Promise<KnowledgeDoc>
type KnowledgeDoc = {
id: string;
title: string;
source_ref: string;
chunk_count: number;
created_at: string;
};
listDocuments(agentId)
List all knowledge documents for an agent.
const docs: KnowledgeDoc[] = await client.listDocuments('agent-id');
Returns: Promise<KnowledgeDoc[]>
deleteDocument(docId)
Delete a knowledge document and all its chunks.
await client.deleteDocument('doc-id');
Returns: Promise<void>
MCP Servers
Full CRUD for MCP server connections at the organization level.
listMcpServers(orgId?)
const servers: McpServer[] = await client.listMcpServers();
Returns: Promise<McpServer[]>
createMcpServer(options)
const server = await client.createMcpServer({
name: 'GitHub',
transport: 'streamable_http',
url: 'https://mcp.github.com/sse',
});
| Name | Type | Description |
|---|---|---|
options.name | string | Display name (required) |
options.transport | 'streamable_http' | 'sse' | 'stdio' | Transport type (required) |
options.url | string? | Server URL (required for HTTP/SSE) |
options.command | string? | Command (required for stdio) |
options.args | string[]? | Arguments (stdio only) |
options.credentials | object? | { headers?, env? } — encrypted at rest |
options.allowedTools | string[]? | Org-level tool filter |
Returns: Promise<McpServer>
getMcpServer(mcpId) / updateMcpServer(mcpId, options) / deleteMcpServer(mcpId)
const server = await client.getMcpServer('mcp-id');
await client.updateMcpServer('mcp-id', { name: 'New Name' });
await client.deleteMcpServer('mcp-id');
testMcpServer(mcpId)
Test an MCP server connection and discover available tools.
const result = await client.testMcpServer('mcp-id');
// { ok: true, tools: [{ name: 'search', description: '...', inputSchema: {...} }] }
Returns: Promise<{ ok: boolean; tools?: McpToolDef[] }>
Integrations
CRUD for database integrations (Postgres/Supabase).
listIntegrations(orgId?)
const integrations: Integration[] = await client.listIntegrations();
Returns: Promise<Integration[]>
createIntegration(options)
const integration = await client.createIntegration({
name: 'Production DB',
kind: 'postgres',
connectionString: 'postgresql://user:pass@host:5432/dbname',
});
Returns: Promise<Integration>
getIntegration(integrationId) / deleteIntegration(integrationId)
const integration = await client.getIntegration('integration-id');
await client.deleteIntegration('integration-id');
Org Settings
getOrgSettings() / updateOrgSettings(settings)
Manage organization-level settings such as allowed embed domains for public chats.
const settings: OrgSettings = await client.getOrgSettings();
// { allowed_domains: ['example.com', 'app.example.com'] }
await client.updateOrgSettings({ allowed_domains: ['example.com', 'new-domain.com'] });
type OrgSettings = {
allowed_domains: string[];
};
Custom Tools
Full CRUD for custom tool definitions at the organization level. Tools can be webhook (server-side HTTP), custom (client-side execution), or delegated (server-side without a fixed URL).
listTools(orgId?)
const tools: Tool[] = await client.listTools();
Returns: Promise<Tool[]>
createTool(options)
const tool = await client.createTool({
name: 'lookup_order',
description: 'Look up an order by ID',
kind: 'webhook',
webhookUrl: 'https://api.example.com/tools/lookup-order',
inputSchema: {
type: 'object',
properties: { orderId: { type: 'string' } },
required: ['orderId'],
},
secret: 'whsec_...',
});
| Name | Type | Description |
|---|---|---|
options.name | string | Tool name (required) |
options.description | string? | What this tool does |
options.kind | 'webhook' | 'custom' | 'delegated'? | Tool type (default: custom) |
options.webhookUrl | string? | HTTP endpoint (required for webhook) |
options.inputSchema | object? | JSON Schema for tool arguments |
options.secret | string? | HMAC secret for webhook signature verification |
options.orgId | string? | Target org (multi-org only) |
Returns: Promise<Tool>
type Tool = {
id: string;
org_id: string;
name: string;
description: string | null;
kind: 'webhook' | 'custom' | 'delegated';
webhook_url: string | null;
input_schema: Record<string, unknown> | null;
created_at: string;
};
getTool(toolId, orgId?) / updateTool(toolId, options) / deleteTool(toolId, orgId?)
const tool = await client.getTool('tool-id');
await client.updateTool('tool-id', { description: 'Updated description' });
await client.deleteTool('tool-id');
All three accept an optional orgId parameter for multi-org accounts (getTool and deleteTool as a second argument, updateTool inside its options object).
Plans & Todos
Manage agent plans (structured task lists) within threads.
listPlans(threadId, status?)
const plans: Plan[] = await client.listPlans('thread-id', 'active');
Returns: Promise<Plan[]>
createPlan(options)
const plan = await client.createPlan({
threadId: 'thread-id',
title: 'Migration Plan',
todos: [{ content: 'Back up database' }, { content: 'Run migrations' }],
});
| Name | Type | Description |
|---|---|---|
options.threadId | string | Thread UUID (required) |
options.title | string | Plan title (required) |
options.todos | Array<{ content: string; parent_todo_index?: number }>? | Initial todos (use index for nesting) |
Returns: Promise<Plan>
type Plan = {
id: string;
org_id: string;
agent_id: string;
thread_id: string;
title: string;
status: string; // 'active' | 'paused' | 'completed'
created_at: string;
updated_at: string;
todos?: PlanTodo[];
deps?: TodoDep[]; // dependency edges (when dependenciesEnabled)
};
getPlan(planId) / updatePlan(planId, updates) / deletePlan(planId)
const plan = await client.getPlan('plan-id');
await client.updatePlan('plan-id', { status: 'completed' });
await client.deletePlan('plan-id');
addTodos(planId, todos)
Add todos to a plan. Supports nesting via parent_id.
const todos: PlanTodo[] = await client.addTodos('plan-id', [
{ content: 'Sub-task A', parent_id: 'parent-todo-id' },
]);
Returns: Promise<PlanTodo[]>
updateTodo(planId, todoId, updates) / deleteTodo(planId, todoId)
await client.updateTodo('plan-id', 'todo-id', { status: 'done' });
await client.deleteTodo('plan-id', 'todo-id');
Todo status values: pending, in_progress, done, skipped.
addTodoDeps(planId, todoId, blockerIds)
Add dependency edges to a todo. The todo becomes blocked until all specified blocker todos are done or skipped. Requires dependenciesEnabled: true in the agent's plan config.
await client.addTodoDeps('plan-id', 'todo-b-id', ['todo-a-id']);
// todo-b is now blocked until todo-a is done/skipped
Returns: Promise<TodoDep[]>
type TodoDep = {
id: string;
dependent_id: string;
blocker_id: string;
created_at?: string;
};
getTodoDeps(planId, todoId)
Get all dependency edges for a todo — both the todos that block it and the todos it blocks.
const { blockers, dependents } = await client.getTodoDeps('plan-id', 'todo-id');
Returns: Promise<{ blockers: TodoDep[]; dependents: TodoDep[] }>
removeTodoDep(planId, todoId, blockerId)
Remove a dependency edge between two todos.
await client.removeTodoDep('plan-id', 'todo-b-id', 'todo-a-id');
// todo-b is no longer blocked by todo-a
getReadyTodos(planId)
Get all todos that are ready to work on — pending or in_progress todos whose blocking dependencies are all done or skipped.
const ready: PlanTodo[] = await client.getReadyTodos('plan-id');
Returns: Promise<PlanTodo[]>
Analytics
getAnalytics(options?)
Get usage analytics for your organization or a specific agent.
const analytics: AnalyticsResult = await client.getAnalytics({
period: '30d',
agentId: 'agent-id', // optional filter
});
| Name | Type | Description |
|---|---|---|
options.period | '7d' | '30d' | '90d'? | Time range |
options.agentId | string? | Filter by agent |
options.orgId | string? | Target org |
Returns: Promise<AnalyticsResult> — includes summary, daily[], by_agent[], by_model[], top_tools[].
Marketplace Profile
Manage an agent's marketplace listing.
getMarketplaceProfile(agentId)
const profile: MarketplaceProfileResponse = await client.getMarketplaceProfile('agent-id');
// { profile, capabilities, readiness: { score, checklist } }
Returns: Promise<MarketplaceProfileResponse>
updateMarketplaceProfile(agentId, profile)
await client.updateMarketplaceProfile('agent-id', {
handle: 'my-agent',
category: 'productivity',
tags: ['ai', 'support'],
});
Returns: Promise<MarketplaceProfileResponse>
deleteMarketplaceProfile(agentId)
await client.deleteMarketplaceProfile('agent-id');
Returns: Promise<void>
uploadAgentAvatar(agentId, file) / deleteAgentAvatar(agentId)
const { avatarUrl, avatarSmUrl } = await client.uploadAgentAvatar('agent-id', file);
await client.deleteAgentAvatar('agent-id');
generateMarketplaceDescription(agentId)
AI-generate a marketplace description based on the agent's configuration.
const { description } = await client.generateMarketplaceDescription('agent-id');
detectCapabilities(agentId) / updateCapabilities(agentId, capabilities)
Auto-detect or manually set agent capabilities for the marketplace.
const detected: MarketplaceCapability[] = await client.detectCapabilities('agent-id');
await client.updateCapabilities('agent-id', detected);
checkHandleAvailability(handle)
const { available, reason } = await client.checkHandleAvailability('my-agent');
Official Marketplace Agents
Browse and use official agent templates from the Flapjack marketplace.
listOfficialAgents(options?)
const agents: OfficialMarketplaceAgent[] = await client.listOfficialAgents({ featured: true });
| Name | Type | Description |
|---|---|---|
options.category | string? | Filter by category |
options.featured | boolean? | Filter to featured agents only |
Returns: Promise<OfficialMarketplaceAgent[]>
useOfficialTemplate(options)
Create a new agent from an official template.
const agent = await client.useOfficialTemplate({
templateId: 'template-id',
name: 'My Support Agent',
});
| Name | Type | Description |
|---|---|---|
options.templateId | string | Template agent ID (required) |
options.name | string? | Custom name for the new agent |
options.orgId | string? | Target org (multi-org only) |
Returns: Promise<Agent>
Runners
Runners are headless, schedulable AI pipelines. See Runners for concepts and architecture.
Runner CRUD
const runners = await client.listRunners();
const runner = await client.createRunner({ name: 'Daily Report', defaultModel: 'gpt-5.4' });
const runner = await client.getRunner('runner-id');
await client.updateRunner('runner-id', { name: 'Weekly Report' });
await client.deleteRunner('runner-id');
Runner Steps
const steps = await client.listRunnerSteps('runner-id');
const step = await client.addRunnerStep('runner-id', {
kind: 'agent',
name: 'Analyze',
agentId: 'agent-id',
});
await client.updateRunnerStep('runner-id', 'step-id', { name: 'Updated' });
await client.removeRunnerStep('runner-id', 'step-id');
Step kinds: agent, webhook, condition, computer.
Runner Triggers
const triggers = await client.listRunnerTriggers('runner-id');
const trigger = await client.addRunnerTrigger('runner-id', {
kind: 'cron',
cronExpression: '0 9 * * MON',
});
await client.updateRunnerTrigger('runner-id', 'trigger-id', { enabled: false });
await client.removeRunnerTrigger('runner-id', 'trigger-id');
Trigger kinds: manual, api, cron, webhook, poll, bulk_import, button.
Runner Runs
const run = await client.triggerRun('runner-id', { input: { topic: 'AI' } });
const runs = await client.triggerRunBulk('runner-id', [{ input: { topic: 'AI' } }, { input: { topic: 'ML' } }]);
const allRuns = await client.listRuns('runner-id', { status: 'completed', limit: 10 });
const run = await client.getRun('runner-id', 'run-id');
await client.cancelRun('runner-id', 'run-id');
Runner Analytics
const analytics: RunnerAnalytics = await client.getRunnerAnalytics('runner-id', { period: '30d' });
// { runner, period, summary: { success_rate, avg_duration_ms, ... }, daily[] }
Resume Run
Resume a run that was paused due to budget limits. Optionally add more budget before resuming.
const run = await client.resumeRun('runner-id', 'run-id', { additionalBudgetUsd: 2.00 });
| Name | Type | Description |
|---|---|---|
runnerId | string | The runner's UUID |
runId | string | The run's UUID |
options.additionalBudgetUsd | number? | Extra USD to add to the run's budget before resuming |
Returns: Promise<RunnerRun>
Runner Budget Config
Configure per-runner budget enforcement. Each run inherits the runner's budget at creation time.
const config = await client.getRunnerBudgetConfig('runner-id');
// config uses snake_case: { runner_id, org_id, enabled, budget_usd, on_exceed, warning_threshold_1, warning_threshold_2 }
await client.updateRunnerBudgetConfig('runner-id', {
enabled: true,
budgetUsd: 5.00,
onExceed: 'pause',
warningThreshold1: 0.80,
warningThreshold2: 0.95,
});
The getter returns RunnerBudgetConfig (snake_case). The updater accepts UpdateRunnerBudgetConfig (camelCase):
| Field | Type | Description |
|---|---|---|
enabled | boolean | Whether budget enforcement is active |
budgetUsd | number | null? | Max USD per run (null = unlimited) |
onExceed | 'pause' | 'fail'? | pause halts the run for approval; fail stops immediately |
warningThreshold1 | number? | First warning at this fraction (default: 0.80) |
warningThreshold2 | number? | Critical warning at this fraction (default: 0.95) |
See Runners: Budget Controls for the full lifecycle.
Runner Connections
Attach MCP servers, database integrations, web tools, and plans directly to runners. These runner-level connections serve as defaults for all steps in the pipeline.
MCP Servers
const mcps: RunnerMcp[] = await client.listRunnerMcps('runner-id');
await client.attachRunnerMcp('runner-id', 'mcp-server-id', ['tool_a']);
await client.detachRunnerMcp('runner-id', 'mcp-server-id');
Database Integrations
const integrations: RunnerIntegration[] = await client.listRunnerIntegrations('runner-id');
await client.attachRunnerIntegration('runner-id', 'integration-id', ['users', 'orders']);
await client.detachRunnerIntegration('runner-id', 'integration-id');
Web Config
const config: RunnerWebConfig = await client.getRunnerWebConfig('runner-id');
await client.updateRunnerWebConfig('runner-id', {
enabled: true,
searchEnabled: true,
researchEnabled: true,
});
Plan Config
const config: RunnerPlanConfig = await client.getRunnerPlanConfig('runner-id');
await client.updateRunnerPlanConfig('runner-id', {
enabled: true,
remindOnTurnEnd: true,
});
Persistent Computer
For external-app integrations where each app gets its own agent with a persistent Linux computer. See the computer API reference and the external-app integration guide for the full flow.
createAgentFromTemplate(options)
Creates an agent with a persistent agent-scoped sandbox in one call.
Idempotent on externalAppId.
const { agent, bootstrapRunId } = await client.createAgentFromTemplate({
name: 'Grocery Tracker',
template: 'nextjs-fullstack',
repo: { url: 'https://github.com/acme/grocery', installCmd: 'pnpm install' },
envVars: [{ key: 'NODE_ENV', value: 'development' }],
sizeClass: 'medium',
webhookUrl: 'https://dassie.example.com/api/flapjack/webhook',
externalAppId: 'app_abc123',
});
getComputerStatus(agentId)
Aggregate status of the persistent sandbox. Server-cached 10 s, safe to poll at 6/minute.
const status = await client.getComputerStatus(agent.id);
// status.computer.status: 'bootstrapping' | 'ready' | 'idle' | ...
// status.signals.devServer?.{ port, listening }
// status.signals.disk?.{ used, total }
execComputer(agentId, options)
Runs a shell command in the sandbox. Returns an AsyncIterable of
events; streams stdout/stderr in real time.
for await (const ev of client.execComputer(agent.id, { command: 'pnpm test' })) {
if (ev.type === 'stdout') process.stdout.write(ev.chunk);
if (ev.type === 'exit') console.log('exit', ev.exitCode);
}
Rate limits: 60/min per agent, 3 concurrent per agent. 429 responses
include Retry-After.
verifyWebhookSignature(body, signature, secret)
Standalone helper (imported separately) that verifies inbound Flapjack webhooks. Uses Web Crypto, works in Node 16+ and browsers.
import { verifyWebhookSignature } from '@maats/flapjack';
const ok = await verifyWebhookSignature(rawBody, sigHeader, secret);
Skills (Skill Marketplace)
Skills are reusable bundles installed once at the org level and attached to any agent or runner. Three sources are supported:
skill_md— Prompt content from aSKILL.mdfile in a GitHub repo. Appended to the agent'sstable_preamble.openapi_action— REST tools auto-generated from an OpenAPI spec URL. Each operation becomes a callable tool.mcp_registry— A remote MCP server pulled from the registry. Tools are loaded at runtime over HTTP/SSE.
When a skill is attached and enabled, its content is composed into the agent's system prompt and tool list at the next message. No restart needed.
listSkills(options?)
List skills installed in the org.
const skills: Skill[] = await client.listSkills();
const onlyMd = await client.listSkills({ source: 'skill_md' });
| Name | Type | Description |
|---|---|---|
options.source | SkillSource? | Filter by skill_md, mcp_registry, openapi_action, or custom |
Returns: Promise<Skill[]>
getSkill(skillId)
const skill = await client.getSkill('skill-id');
Returns: Promise<Skill>
installSkill(options)
Install a skill into the org from an external source.
// SKILL.md from a GitHub repo
await client.installSkill({
source: 'skill_md',
sourceUrl: 'https://github.com/anthropics/skills',
sourceRef: 'anthropics/skills/superpowers/git',
});
// OpenAPI spec → REST action skill
await client.installSkill({
source: 'openapi_action',
sourceUrl: 'https://api.example.com/openapi.json',
name: 'Example API',
});
// Remote MCP server from the MCP registry
await client.installSkill({
source: 'mcp_registry',
sourceUrl: 'https://registry.modelcontextprotocol.io/servers/abc',
sourceRef: 'abc-server-id',
});
| Field | Type | Description |
|---|---|---|
source | 'skill_md' | 'openapi_action' | 'mcp_registry' | Source type. custom skills are created in the dashboard, not installed via this method. |
sourceUrl | string | GitHub repo URL, OpenAPI spec URL, or registry server URL |
sourceRef | string? | For skill_md: owner/repo/path; for mcp_registry: registry server ID |
name | string? | Override the imported skill name |
description | string? | Override the imported skill description |
Returns: Promise<Skill> — the installed skill, or 409 SKILL_ALREADY_INSTALLED.
uninstallSkill(skillId)
Removes the skill from the org and detaches it from all agents/runners.
await client.uninstallSkill('skill-id');
Returns: Promise<void>
syncSkill(skillId)
Re-fetch source content for skill_md (re-reads SKILL.md) or openapi_action (re-fetches the spec). mcp_registry skills sync via the MCP server's tool cache.
const { syncedAt } = await client.syncSkill('skill-id');
Returns: Promise<{ ok: true; syncedAt: string }>
browseSkills(options?)
Browse the unified skill marketplace across all sources. Already-installed entries carry an installedId.
const { skills, nextCursor } = await client.browseSkills({
q: 'github',
source: 'mcp_registry',
limit: 20,
});
| Field | Type | Description |
|---|---|---|
q | string? | Free-text search |
source | SkillSource? | Restrict to a single source |
limit | number? | Max per source (default 20, max 50) |
cursor | string? | Pagination cursor (MCP registry) |
Returns: Promise<{ skills: SkillCatalogEntry[]; nextCursor?: string }>
Agent Bindings
Attach a skill to an agent. The skill's preamble (or its tools, for openapi_action/mcp_registry) becomes part of that agent's runtime config.
const attached = await client.listAgentSkills(agentId);
await client.attachSkillToAgent(agentId, {
skillId: 'skill-id',
promptOverride: 'Optional per-agent override of the skill prompt',
credentialId: 'cred-id', // optional, for OAuth/API-key skills
});
await client.detachSkillFromAgent(agentId, 'skill-id');
listAgentSkills returns AttachedSkill[]:
type AttachedSkill = {
id: string;
skillId: string;
enabled: boolean;
promptOverride: string | null;
hasCredentials: boolean;
createdAt: string;
skill: { id; name; slug; description; iconUrl; source; category; tags; authType; version; status } | null;
};
Runner Bindings
Identical shape, scoped to a runner:
await client.listRunnerSkills(runnerId);
await client.attachSkillToRunner(runnerId, { skillId: 'skill-id' });
await client.detachSkillFromRunner(runnerId, 'skill-id');
Projects
Projects are optional organizational containers for grouping agents and runners. They are aesthetic only — no runtime effect.
listProjects(orgId?)
const projects: Project[] = await client.listProjects();
Returns: Promise<Project[]>
type Project = {
id: string;
org_id: string;
name: string;
slug: string | null;
description: string | null;
created_by: string | null;
created_at: string;
updated_at: string;
};
createProject(options)
const project = await client.createProject({
name: 'Customer Support',
slug: 'customer-support',
description: 'All support-related agents and runners',
});
| Name | Type | Description |
|---|---|---|
options.name | string | Display name (required) |
options.slug | string? | URL-friendly identifier |
options.description | string? | Project description |
options.orgId | string? | Target org (multi-org only) |
Returns: Promise<Project>
getProject(projectId) / updateProject(projectId, options) / deleteProject(projectId)
const project = await client.getProject('project-id');
await client.updateProject('project-id', { name: 'Updated Name' });
await client.deleteProject('project-id'); // members become ungrouped
Deleting a project does not delete its agents or runners — they become ungrouped.
getProjectMembers(projectId)
Get all agents and runners belonging to a project.
const members: ProjectMembers = await client.getProjectMembers('project-id');
console.log(members.agents); // AgentSummary[]
console.log(members.runners); // RunnerSummary[]
Returns: Promise<ProjectMembers>
type ProjectMembers = {
runners: RunnerSummary[];
agents: AgentSummary[];
};
// Lightweight subsets of Runner and Agent — heavy fields like learned_instructions are omitted
type RunnerSummary = Pick<Runner, 'id' | 'org_id' | 'name' | 'description' | 'status' | 'default_model' | 'project_id' | 'created_at' | 'updated_at'>;
type AgentSummary = Pick<Agent, 'id' | 'org_id' | 'name' | 'description' | 'default_model' | 'project_id' | 'created_at'>;
Next Steps
- React Hooks —
FlapjackProvider,useChat, andusePlan - Server Proxy — secure production pattern
- Examples — full working examples