Documentation
SDK

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');
NameTypeDescription
agentIdstringThe 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',
});
NameTypeDescription
options.namestringDisplay name (required)
options.descriptionstring?What this agent does
options.stablePreamblestring?System prompt
options.defaultModelstring?LLM model ID (defaults to gpt-5.4)
options.orgIdstring?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 });
MethodConfig TypeKey Fields
getWebConfig(agentId) / updateWebConfig(agentId, config)WebConfigenabled, searchEnabled, researchEnabled, readEnabled, crawlEnabled
getMemoryConfig(agentId) / updateMemoryConfig(agentId, config)MemoryConfigenabled, autoCollect, autoInject, injectCount, maxMemories, agentScope, threadScope, resourceScope
getPlanConfig(agentId) / updatePlanConfig(agentId, config)PlanConfigenabled, remindOnTurnEnd, dependenciesEnabled
getCompactionConfig(agentId) / updateCompactionConfig(agentId, config)CompactionConfig / UpdateCompactionConfigenabled, anthropicTriggerTokens (min 50000), anthropicInstructions?, anthropicPauseAfter?, openaiCompactThreshold (min 1000). See Compaction.
getComputerConfig(agentId) / updateComputerConfig(agentId, config)ComputerConfigenabled, mode (ephemeral/persistent/custom), scope, sizeClass
getDelegatedConfig(agentId) / updateDelegatedConfig(agentId, config)DelegatedConfig / UpdateDelegatedConfigGetter: url, timeout_ms. Updater: url, secret (required), timeoutMs?
getCredentialConfig(agentId) / updateCredentialConfig(agentId, config)CredentialConfig / UpdateCredentialConfigGetter: 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']);
NameTypeDescription
agentIdstringThe agent's UUID
mcpServerIdstringMCP server UUID
allowedToolsstring[]?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
NameTypeDescription
options.scopestring?agent, thread, or resource
options.resourceTypestring?Filter by resource type
options.resourceIdstring?Filter by resource ID
options.limitnumber?Max results
options.offsetnumber?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' }],
});
NameTypeDescription
agentIdstringThe agent's UUID (required)
titlestring?Thread title
kind'single' | 'multiplayer'?Thread mode
resourceBindingsResourceBinding[]?Attach external resource context
activeProfilestring?Set the initial tool profile
systemPromptOverlaystring | string[]?Text appended to the agent's system prompt for this thread
orgIdstring?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' });
NameTypeDescription
threadIdstringThe thread's UUID
updates.activeProfilestring | null?Set or clear the active tool profile
updates.titlestring?Update the thread title
updates.systemPromptOverlaystring | 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,
});
NameTypeDescription
agentIdstringThe agent's UUID
options.cursorstring?ISO 8601 timestamp for cursor pagination
options.limitnumber?Max threads per page (default 50, max 100)
options.orgIdstring?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 });
NameTypeDescription
threadIdstringThe thread's UUID
options.limitnumber?Max messages (default 100, max 200)
options.offsetnumber?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:

NameTypeDescription
threadIdstringThe thread's UUID
contentstringThe user's message text
options.toolsToolDef[]?Custom tool definitions for client-side execution
options.onToolCallFunction?Handler for client-side tool calls (auto-submits results)
options.modelOverridestring?Override the agent's default model for this message
options.webOverridesobject?Override web tool settings
options.memoryOverridesobject?Override memory settings
options.planOverridesobject?Override plan settings
options.computerOverridesobject?Override computer settings
options.anthropicOverridesAnthropicOverrides?Override Anthropic features (thinking, fallback) — Claude models only
options.openaiOverridesOpenAIOverrides?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.displayContentstring?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.resourceBindingsResourceBinding[]?Attach resource context
options.toolProfilestring?Use a specific tool profile for this message
options.systemPromptOverlaystring | string[]?Text appended to the system prompt for this message only
options.senderNamestring?Display name (multiplayer)
options.senderIdstring?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'
);
NameTypeDescription
agentIdstringThe agent's UUID
fileFile | BlobThe document file
titlestringDisplay 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',
});
NameTypeDescription
options.namestringDisplay name (required)
options.transport'streamable_http' | 'sse' | 'stdio'Transport type (required)
options.urlstring?Server URL (required for HTTP/SSE)
options.commandstring?Command (required for stdio)
options.argsstring[]?Arguments (stdio only)
options.credentialsobject?{ headers?, env? } — encrypted at rest
options.allowedToolsstring[]?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_...',
});
NameTypeDescription
options.namestringTool name (required)
options.descriptionstring?What this tool does
options.kind'webhook' | 'custom' | 'delegated'?Tool type (default: custom)
options.webhookUrlstring?HTTP endpoint (required for webhook)
options.inputSchemaobject?JSON Schema for tool arguments
options.secretstring?HMAC secret for webhook signature verification
options.orgIdstring?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' }],
});
NameTypeDescription
options.threadIdstringThread UUID (required)
options.titlestringPlan title (required)
options.todosArray<{ 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
});
NameTypeDescription
options.period'7d' | '30d' | '90d'?Time range
options.agentIdstring?Filter by agent
options.orgIdstring?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 });
NameTypeDescription
options.categorystring?Filter by category
options.featuredboolean?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',
});
NameTypeDescription
options.templateIdstringTemplate agent ID (required)
options.namestring?Custom name for the new agent
options.orgIdstring?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 });
NameTypeDescription
runnerIdstringThe runner's UUID
runIdstringThe run's UUID
options.additionalBudgetUsdnumber?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):

FieldTypeDescription
enabledbooleanWhether budget enforcement is active
budgetUsdnumber | null?Max USD per run (null = unlimited)
onExceed'pause' | 'fail'?pause halts the run for approval; fail stops immediately
warningThreshold1number?First warning at this fraction (default: 0.80)
warningThreshold2number?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 a SKILL.md file in a GitHub repo. Appended to the agent's stable_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' });
NameTypeDescription
options.sourceSkillSource?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',
});
FieldTypeDescription
source'skill_md' | 'openapi_action' | 'mcp_registry'Source type. custom skills are created in the dashboard, not installed via this method.
sourceUrlstringGitHub repo URL, OpenAPI spec URL, or registry server URL
sourceRefstring?For skill_md: owner/repo/path; for mcp_registry: registry server ID
namestring?Override the imported skill name
descriptionstring?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,
});
FieldTypeDescription
qstring?Free-text search
sourceSkillSource?Restrict to a single source
limitnumber?Max per source (default 20, max 50)
cursorstring?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',
});
NameTypeDescription
options.namestringDisplay name (required)
options.slugstring?URL-friendly identifier
options.descriptionstring?Project description
options.orgIdstring?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

Docs last updated June 29, 2026