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 |
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.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');
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[] }
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');
Next Steps
- React Hooks —
FlapjackProvider,useChat, andusePlan - Server Proxy — secure production pattern
- Examples — full working examples