Skip to content

@doable/sdk

The Doable SDK lets generated Vite or Next.js apps call any connected integration (Slack, Stripe, GitHub, and the rest of the 630+ ActivePieces catalogue) and any MCP tool through a secure server-side proxy. Credentials never reach the browser. The package is published as @doable/sdk from packages/doable-sdk/.

Three entry points

The package exports from three subpaths:

Import Source Runtime
@doable/sdk src/index.ts Browser-first, framework-agnostic
@doable/sdk/react src/react.ts React hooks, peer-depends on react >= 18
@doable/sdk/server src/server.ts Node, Next.js Server Actions, API Routes

The package is published as ESM ("type": "module"). React is an optional peer dependency, so apps that do not use the React hooks pull in zero React baggage.

Install

pnpm add @doable/sdk
# or
npm install @doable/sdk

In Doable-generated projects the SDK is wired up automatically, including the VITE_DOABLE_PROJECT_KEY build-time injection that pre-authenticates the client.

Browser usage

import { createDoableClient } from "@doable/sdk";

const doable = createDoableClient();

// Call an integration action through the secure proxy.
const result = await doable.integrations.run("slack", "send_channel_message", {
  channel: "#general",
  text: "Hello from Doable",
});

if (!result.success) {
  console.error(result.error?.code, result.error?.message);
}

The client auto-detects credentials in three modes:

  1. Preview (editor open). A token arrives via postMessage from the parent frame. No setup needed.
  2. Standalone preview. The client reads the project ID from /preview/:projectId/... in the URL or a <meta name="doable-project-id"> tag, then POSTs to /preview/:projectId/__doable/token for a token.
  3. Deployed. Pass apiKey and projectId in createDoableClient({ apiKey, projectId }), or let Vite inject VITE_DOABLE_PROJECT_KEY at build time. Vite-generated projects pick it up automatically.

A token-refresh retry on 401 is built in for preview mode (src/index.ts lines 317-323).

Listing what's available

const { data: integrations } = await doable.integrations.list();
//   AvailableIntegration[]: id, displayName, actions[]

const { data: mcpTools } = await doable.mcp.list();
//   McpTool[]: fullName, connectorName, toolName, description?

MCP tools

const result = await doable.mcp.call("mcp_hpca_mcp_get_user_info", {
  username: "alice",
});

Pass either the full AI-prefixed tool name (the value of McpTool.fullName) or a connector-scoped name. Credentials resolve server-side.

React hooks (@doable/sdk/react)

Two hooks. The hooks share a singleton client across the app, so configuration passes once.

useIntegration (mutations and side effects)

import { useIntegration } from "@doable/sdk/react";

function SendButton() {
  const slack = useIntegration("slack", "send_channel_message");

  return (
    <button
      disabled={slack.loading}
      onClick={() => slack.run({ channel: "#general", text: "Hello!" })}
    >
      {slack.loading ? "Sending..." : "Send"}
    </button>
  );
}

Returns { run, loading, error, data, reset }. error is { code, message } | null. data is the last successful result. reset() clears state.

useIntegrationQuery (read-only queries)

import { useIntegrationQuery } from "@doable/sdk/react";

function ChannelList() {
  const { data, loading, error, refetch } = useIntegrationQuery(
    "slack",
    "list_channels",
    {},
    { refetchInterval: 30_000 }, // poll every 30s
  );

  if (loading) return <p>Loading...</p>;
  if (error) return <p>{error.message}</p>;
  return <ul>{data?.map((c) => <li key={c.id}>{c.name}</li>)}</ul>;
}

Options: enabled (default true) and refetchInterval (omit for no polling). Re-runs whenever integrationId, actionName, or a JSON-stringified props changes.

Server usage (@doable/sdk/server)

Use from Next.js Server Actions, API Routes, or any Node server. The server client uses an API key (higher rate limits than browser JWT) and calls the proxy over 127.0.0.1 by default.

// app/api/post-to-slack/route.ts (Next.js App Router)
import { createServerClient } from "@doable/sdk/server";

export async function POST(req: Request) {
  const doable = createServerClient();
  const result = await doable.integrations.run("slack", "send_channel_message", {
    channel: "#general",
    text: "Posted from a server action",
  });
  return Response.json(result);
}

Configuration falls back to env vars:

Env var Default Used when config.<field> is undefined
DOABLE_PROJECT_KEY required apiKey
DOABLE_PROJECT_ID required projectId
DOABLE_PROXY_URL http://127.0.0.1:${API_PORT ?? "4000"}/__doable/connector-proxy proxyUrl

If DOABLE_PROJECT_KEY is unset, the SDK warns once and every call returns success: false with HTTP 401.

Result shape

Both integrations.run and mcp.call resolve to a discriminated union. Either everything in data and meta, or everything in error.

type IntegrationCallResult<T> = {
  success: boolean;
  data: T | null;
  error: { code: string; message: string } | null;
  meta: { integrationId: string; actionName: string; durationMs: number } | null;
};

McpCallResult<T> is the same shape with meta.connectorName plus meta.toolName instead.

Common error codes:

Code Cause
NETWORK_ERROR fetch threw (DNS, offline, CORS)
HTTP_ERROR The proxy returned a non-2xx response on a list call
EXECUTION_FAILED The integration action ran but the underlying service rejected it
UNKNOWN Proxy returned an error envelope with no code

Security model

  • The proxy decrypts integration and MCP credentials server-side. The browser never sees raw secrets.
  • Tokens are scoped to a single project. Cross-project calls return 401.
  • In server mode, the client connects over 127.0.0.1, so a misconfigured public proxy URL cannot leak the project key through DNS rebinding.
  • The DOABLE_PROJECT_KEY env var is server-only by convention. Do not prefix it with VITE_ or NEXT_PUBLIC_.

Type re-exports

createDoableClient, createServerClient, plus types: DoableClient, DoableSDKConfig (aliased Config), IntegrationCallResult, McpCallResult, McpTool, AvailableIntegration, UseIntegrationReturn, UseIntegrationQueryOptions, UseIntegrationQueryReturn.

Source

  • packages/doable-sdk/src/index.ts (browser client, token manager, proxy fetch)
  • packages/doable-sdk/src/react.ts (hooks)
  • packages/doable-sdk/src/server.ts (Node client)
  • Related: User Guide: Integrations, AI: Tools and MCP