SDK

Build integrations in minutes

Create custom integrations using simple JavaScript. Connect any API and run it inside workflows.

Shipping to the builder? Follow the library guide for integrationRegistry, web display metadata, and the add-step menu: Add your integration to the library →

Quick start

An integration action is a small object: a stable key, a human name, what the user configures as inputs, and an async run function that returns JSON for the next step.

Minimal shape (illustrative)
export const helloWorld = {
  key: "hello.world",
  name: "Hello World",

  inputs: {
    name: "string",
  },

  run: async ({ inputs }) => {
    return { message: `Hello ${inputs.name}` };
  },
};
  • key — unique id (e.g. slack.sendMessage). Steps reference this.
  • inputs — fields your UI collects; in the real registry these are validated with Zod (see below).
  • run — call remote APIs, map responses, throw on failure; return value becomes step output.

Integration structure

key
Dot-separated id. Must match the registry entry the worker resolves.
name
Short label shown in the builder (e.g. "Send message").
provider
Vendor namespace for docs and credential typing (e.g. slack).
authType
Expected credential shape: oauth2, api_key, or bearer_token.
inputSchema
Zod schema — validates config.inputs before run.
run(ctx)
Receives auth, parsed inputs, and ids like tenantId / runId for logging.

Authentication

Credentials live in the vault. The worker decrypts them and passes a normalized auth object into run() — never logged to the client after save.

API key (e.g. OpenAI, Stripe secret key)
// auth in run()
{
  "provider": "openai",
  "type": "api_key",
  "api_key": "sk-..."
}
OAuth2 (e.g. Slack, Gmail)
// auth in run()
{
  "provider": "slack",
  "type": "oauth2",
  "access_token": "xoxb-...",
  "refresh_token": "..." // optional
}

In the app, bind a credential on the step; the worker picks the secret JSON and validates it against the integration's expected authType.

Real examples

Patterns match the open-source registry (@orchestrator/integrations). Adjust URLs and error handling for your environment.

1. Slack — send message

slack.sendMessage
import { z } from "zod";

const SlackInputs = z.object({
  channel: z.string().min(1),
  text: z.string().min(1),
});

export const slackSendMessage = {
  key: "slack.sendMessage",
  name: "Send message",
  provider: "slack",
  authType: "oauth2",
  inputSchema: SlackInputs,
  run: async ({ auth, inputs }) => {
    const parsed = SlackInputs.parse(inputs);
    const token =
      auth.type === "oauth2" ? auth.access_token : auth.type === "bearer_token" ? auth.token : null;
    if (!token) throw new Error("Slack requires oauth2 or bearer_token");

    const res = await fetch("https://slack.com/api/chat.postMessage", {
      method: "POST",
      headers: {
        Authorization: `Bearer ${token}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ channel: parsed.channel, text: parsed.text }),
    });
    const body = await res.json();
    if (!res.ok || body.ok === false) throw new Error(body.error || "Slack request failed");
    return body;
  },
};

2. Gmail — send email

gmail.sendEmail (RFC 2822 raw + base64url)
// OAuth2 user token with gmail.send scope
const token = auth.type === "oauth2" ? auth.access_token : auth.token;

const raw =
  `To: ${to}\r\nSubject: ${subject}\r\nContent-Type: text/plain; charset=UTF-8\r\n\r\n` + bodyText;

const b64 = Buffer.from(raw, "utf8")
  .toString("base64")
  .replace(/\+/g, "-")
  .replace(/\//g, "_")
  .replace(/=+$/, "");

const res = await fetch("https://gmail.googleapis.com/gmail/v1/users/me/messages/send", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${token}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ raw: b64 }),
});
if (!res.ok) throw new Error(await res.text());
return res.json();

3. OpenAI — chat completion

openai.chatCompletion
const apiKey = auth.type === "api_key" ? auth.api_key : auth.token;

const res = await fetch("https://api.openai.com/v1/chat/completions", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${apiKey}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: prompt }],
  }),
});
const body = await res.json();
if (!res.ok) throw new Error(body.error?.message || "OpenAI error");
return body;

Testing

  • In the UI— Create a workflow, add an integration step, bind a real credential, publish, and start a run. Inspect each step's output in the run view.
  • Locally — Run API + worker against a dev database; use the same credential vault encryption key as in .env. Trigger a run via the app or API so the worker executes your registry entry.
  • HTTP fallback — Steps that are not in the registry still run as HTTP / sandbox steps, so you can compare behavior side by side.
Next step
Open the app and wire your first run
Use built-in actions or extend the registry in your deployment.