@fastn-ai/sdk

中文文档
0.5.1发布于 2026/11/01MITTypeScriptESM + CJS近 30 天下载 2378

fastn SDK for Node.js and TypeScript: embedded integrations for multi-tenant SaaS and AI agents. Your customers connect their own apps inside your product, through one typed API.

原始使用文档
文档目录18 节
  1. Install
  2. 60-second tour
  3. Embed fastn in your product (guide for Claude and other AI …
  4. Configuration
  5. Named apps, categories and events
  6. Calendar and CRM, typed
  7. Customer ids, explained once
  8. Responses and errors
  9. Per-call options
  10. Workspace tools: run anything your workspace has turned on
  11. Typed methods for your workspace: fastn-sdk-tools
  12. Events (webhooks)
  13. Pagination
  14. Development
  15. What stays compatible
  16. Releasing
  17. FAQ
  18. License

@fastn-ai/sdk: fastn SDK for Node.js and TypeScript

Embedded integrations for your product and your AI agents.

fastn is the embedded integration platform for B2B SaaS. @fastn-ai/sdk is its official Node.js and TypeScript SDK: add customer-facing integrations (CRM, calendar, Slack, and 1,000+ more apps) to your product through one API, and give your AI agents the same integrations as tools.

Your customers connect their own apps inside your product. Your backend uses those connections through the fastn platform API, and your agents call them as workspace tools or through the fastn MCP gateway.

Built for multi-tenant SaaS:

  • One key, every customer. Address each customer by your own id (fastn.customer("acme-42")); fastn's internal org ids never reach your code. Every call is scoped to that customer, and an account or watch id that belongs to another customer is refused, so one customer's data never leaks into another's.
  • Each customer brings their own accounts. Send a setup link and the customer connects their HubSpot, Google Calendar, Slack, and so on. Credentials stay in fastn's vault, isolated per customer, and refresh automatically.
  • Several accounts per app. A customer can connect two Slack workspaces or two calendars; pick one per call with connectionId.
  • One API across providers. customer.crm and customer.calendar work the same whichever CRM or calendar the customer connected; the provider is picked from their connections.
  • Safe retries and traceability. idempotencyKey makes writes safe to retry, and correlationId lets you ask later what fastn did for a request (customer.callLog(...)).
  • Governed. Role-based access control, audit logs, and per-customer isolation on a SOC 2 Type II platform.

Use fastn when:

  • your customers ask for integrations your team cannot build and maintain one by one;
  • every customer needs the same integration connected to their own account and configured to their own fields;
  • you want one integration to serve your product, your customers, and your AI agents;
  • you need per-customer isolation, audit logs, and SOC 2 for a security review.

The package:

  • Node 20+ (built-in fetch), ESM and CommonJS, full types. The one runtime dependency (json-schema-to-typescript) is used only by the fastn-sdk-tools code generator.
  • 461 operations in 19 areas (orgs, embed, connections, unified, events, workflows, ...), one method per operation, named exactly like the operation in the API docs and the Fastn Workspace tools. Platform-administration routes, which a partner key cannot call, are left out on purpose.
  • Customer context that hides fastn's internal org ids: you only ever use the id you gave fastn.
  • Event signature verification, retries, typed errors, pagination.

Install

npm install @fastn-ai/sdk

Get a platform API key from the fastn dashboard. The client does not read environment variables itself: pass the key in.

import { FastnClient } from "@fastn-ai/sdk";             // ESM
// const { FastnClient } = require("@fastn-ai/sdk");     // CommonJS

60-second tour

import { Apps, Categories, Entities, EventTypes, FastnClient, HubSpotEvents } from "@fastn-ai/sdk";

const fastn = new FastnClient({ apiKey: process.env.FASTN_API_KEY! });   // https://api.fastn.dev

// 1. Register a customer under your account, using YOUR id for them.
const customer = fastn.customer("acme-42");
await customer.register({ name: "Acme Dental" });              // idempotent: returns the existing org on repeat

// 2. Give them a link to connect their tools (Google Calendar, HubSpot, ...), by connector slug.
const link = await customer.connect(Apps.googleCalendar);
console.log(link.url);                                         // send this to the customer

// 3. Typed methods on whatever the customer connected: the provider is picked from their connections.
const booked = await customer.calendar.book(
  { title: "Intro call", start_time: "2026-11-03T15:00:00Z", end_time: "2026-11-03T15:30:00Z", timezone: "Europe/London", attendee_email: "[email protected]" },
  { idempotencyKey: "booking-123", correlationId: "req-abc" },  // safe to retry; traceable later
);
console.log(booked.id, booked.provider, booked.data.web_url);

await customer.calendar.reschedule(booked.id, { start_time: "2026-11-03T16:00:00Z", end_time: "2026-11-03T16:30:00Z" });
await customer.calendar.cancel(booked.id, { idempotencyKey: "cancel-123" });

const { records } = await customer.crm.contacts.find({ email: "[email protected]" }); // one page (the search route has no cursor); list() pages
const contact = records[0] ?? await customer.crm.contacts.create({ first_name: "Jane", last_name: "Doe", email: "[email protected]" });
await customer.crm.notes.add({ body: "Booked an intro call", parent_id: contact.id, parent_type: Entities.contact });

// 4. What did fastn do for my request id?
await customer.callLog("req-abc");

Embed fastn in your product (guide for Claude and other AI coding assistants)

The steps to add customer-facing integrations to a SaaS app with fastn. Use the exact names below; do not invent props or endpoints.

  1. Install and get a key. npm install @fastn-ai/sdk, then create a platform API key in the fastn dashboard and pass it as apiKey.

  2. Register each of your customers by your own id (backend):

    const fastn = new FastnClient({ apiKey: process.env.FASTN_API_KEY! });
    const customer = fastn.customer(account.id);              // your id for this customer
    await customer.register({ name: account.name });          // idempotent
    
  3. Let the customer connect their apps. Either send a hosted setup link, or show the embedded widget in your UI:

    const link = await customer.connect(Apps.hubspot);           // hosted page: redirect the customer to link.url
    const token = await customer.embedToken();                // or: a short-lived token for the in-app widget
    

    On the frontend, the widget is the @fastn-ai/embed package (FastnProvider with FastnHub or FastnConnectCard from @fastn-ai/embed/react, or the hosted script tag). Copy the exact snippet from the dashboard's Embed tab. See the embedding docs.

  4. Use the customer's connections from your backend: customer.crm.contacts.find(...), customer.calendar.book(...), or customer.runAction({ connector, action, input }) for any app action.

  5. React to changes: fastn.webhooks.create(...) once for your account, customer.watch(app, events) per customer, and verify deliveries with parseEvent(secret, headers, rawBody).

  6. Give your AI agents the same integrations: workspace tools below, or connect Claude, Cursor, or any MCP client to the fastn MCP gateway.

Pricing is per customer connection, with a free plan to start: see fastn.ai/pricing. A machine-readable summary of fastn for LLMs is at fastn.ai/llms.txt.

Configuration

new FastnClient({
  apiKey,                          // required: platform API key, sent as x-api-key
  baseUrl,                         // default https://api.fastn.dev; https://api.fastn-test.dev for development
  timeoutMs,                       // per request, default 60000
  retries,                         // GET and idempotent calls on 429/502/503/504 and network errors, default 2
  defaults,                        // options for every call, e.g. { customer: { ref: "acme-42" } }
  onRequest,                       // (log: RequestLog) => void, after every HTTP attempt
  fetch,                           // a fetch implementation, default globalThis.fetch
  userAgent,
  tools,                           // { key, url, timeoutMs }: your fastn Connect workspace, enables fastn.tools
});

Named apps, categories and events

Apps, Categories, Entities, HubSpotEvents, EventTypes and UnifiedActions name every app in the unified catalog, every category and entity, HubSpot's change events, and the events your webhook receives, so calls are completed and checked by your editor instead of typed as strings: customer.connect(Apps.googleCalendar), customer.watch(Apps.hubspot, [HubSpotEvents.contactCreated]), fastn.catalog.fields(Categories.crm, Entities.contact), { provider: Apps.hubspot }. A misspelt member (Apps.googleCalender, HubSpotEvents.contactCraeted) does not compile. Plain strings are still accepted and not checked: watch refuses an unknown event name before subscribing (FastnApiError 400 EVENT_NOT_FOUND, with the valid names), and a missing name (undefined from a misspelt member in plain JavaScript) throws a TypeError naming the call. Each value is the exact string the API takes, so existing string calls keep working. The lists are the unified catalog on production as of 0.5.0; an app, category or entity outside them can still be passed as a string. Snippets below import them from @fastn-ai/sdk.

Calendar and CRM, typed

customer.calendar and customer.crm are the unified API with the fields named: each method takes a TypeScript interface (the catalog's own field names, start_time, first_name, ...; Date values are sent as ISO strings) and answers { id, provider, data, warnings? } or a page { records, nextCursor, hasMore }. The provider is the one the customer has connected for that entity: no connected provider answers 404 NO_CONNECTED_PROVIDER (send them a setup link), several answer 409 PROVIDER_AMBIGUOUS (pass { provider }). warnings lists fields the provider does not map and therefore dropped. Every method takes { provider?, connectionId?, idempotencyKey?, correlationId?, ... } last.

MethodUnderneath
calendar.freeSlots({ start_time, end_time, timezone?, slot_minutes? })scheduling/availability; answers busy, free, and slots of that length
calendar.book(meeting), get(id), find({ start_time, end_time }), list({ page_size, cursor }), reschedule(id, { start_time, end_time, duration_minutes? }), cancel(id)scheduling/meeting
crm.contacts.create / get / find({ email, phone, first_name, last_name }) / list / update / deletecrm/contact
crm.notes.add({ body, parent_id, parent_type }), create, get, find, list, update, deletecrm/note (created_at defaults to now)
crm.tasks.add({ subject, due_date, status, ... }), create, get, find, list, update, deletecrm/task
crm.deals.create({ name, amount, stage, ... }), get, find, list, update, moveStage(id, stage), delete, stages()crm/opportunity, crm/stage

What they do not cover is still one call away: customer.records({ category, entity, provider }) takes any entity in the catalog with untyped input (create, get, list, update, delete, plus search(filters) and providers()), and customer.runAction(...) runs a connector's own action.

// The catalog: which categories and entities exist, the fields each takes, which providers a customer connected.
const categories = await fastn.catalog.categories();                 // [{ slug: "crm", entities: [{ slug: "contact", operations, createFields }] }]
const fields = await fastn.catalog.fields(Categories.crm, Entities.contact);         // [{ field: "email", type: "string", required: false }, ...]
const providers = await customer.catalog.providers(Categories.crm, Entities.contact); // [{ connectorSlug: "hubspot", connected: true, operations: [...] }]

Every area is on the client directly, with typed inputs. The partner flow lives in orgs, embed, connections, unified, events and workflows. Platform-administration areas (admin, platform config, support) and backend-only routes (OAuth callbacks, credential reads, staff approvals) are not generated: a partner key cannot call them, and leaving them out keeps the surface the SDK promises to keep stable small. The generator's HIDDEN_TAGS / PUBLIC_INTERNAL lists in scripts/generate.ts are the single place that rule lives.

await fastn.orgs.listOrgs({ type: OrgTypes.endOrg });
await fastn.connections.listConnections({ endOrgId: await customer.id() });
await fastn.events.listEventSubscriptions();
await fastn.embed.getSetupLink({ id: "stp_..." });
await fastn.orgs.getTenant({ id: "..." });                     // every operation is a typed method on its area
await fastn.me();                                               // who this key is: actor, role, org, parentOrg

Customer ids, explained once

Use fastn.customer(ref) (or the customer option) and nothing else. The raw API names the same customer differently per operation; those generated input fields still exist, but they are the raw API and will be renamed on the platform side. Scoping calls to a customer needs a multi-tenant partner API key; the platform rejects the customer headers on a single-tenant key (not exercised by the tests here).

You see in the API docsWhat it isIn this SDK
orgId (createTenant body)the id YOU choose for the customerfastn.customer(ref)
externalRef, endOrgRef, x-end-org-refthe same id, under other namesfilled in for you
endOrgId, id (createTenant answer), x-end-org-idfastn's own id of that customer orgawait customer.id() (cached)

customer.find() returns the customer's org row (or undefined when fastn does not know the ref). customer.register(), connect(app), links, watch(), disconnect(), renameAccount(), update(), callLog(), setupLink(), embedToken(), connectWithCredentials(), connections(), get(), remove(), calendar, crm and records(...) all take the ref and resolve fastn's id only where the API insists on it. The lookup is limited to orgs under your own org, so a key that can see more than your account can still never resolve someone else's customer; the resolved id is cached per client.

customer.api.<area>.<operation>() runs any operation for that customer. It sends x-end-org-ref and, on routes that take the customer in the body instead (endOrgId or externalRef: OAuth initiation, stored connections, event subscriptions, secrets, setup links, ...), fills that field in too. The platform only reads the header on unified record and workflow execute routes; without the body field those calls would silently run as your own org.

// A setup link by app: the account's widget for that connector is found for you (several: pass widgetId).
const link = await customer.connect(Apps.hubspot, { connectionName: "Main CRM", redirectUrl: "https://app.acme.com/connected" });
redirect(link.url);                                                   // then customer.links.get(link.setupId) / links.cancel(...)

// Or start OAuth directly, without a widget:
const { data } = await customer.connect({ connectorId: Apps.googleCalendar, connectionName: "Google Calendar", returnUrl: "https://app.acme.com/connected" });
redirect(data.authorizationUrl);                                      // the connection lands on the customer org

// Typed-in credentials (API key, basic auth): the fields come from the connector's auth method. The platform checks
// them; an incomplete set answers 400 CREDENTIALS_INCOMPLETE with the missing names (err.body.missing).
const fields = await customer.authFields("acme-crm");                 // { names, required, schema, authMethodId }
await customer.connectWithCredentials({ connectorId: "acme-crm", credentials: { apiKey: "..." }, name: "Acme CRM" });
// Changes at an app, sent to your webhook as connector.event / unified.event: watch them per customer.
const accountId = "...";                                              // one of the customer's accounts, from customer.connections()
const w = await customer.watch(Apps.hubspot, [HubSpotEvents.contactCreated, HubSpotEvents.dealStageChanged], { accountId });
// HubSpotEvents.dealStageChanged delivers every deal property change: keep stage moves with propertyName === "dealstage".
await customer.watches(Apps.hubspot);                                 // [{ id, app, events, status }]
await customer.unwatch(Apps.hubspot);                                 // or unwatch(w.id)
await customer.watchableEvents(Apps.hubspot);                         // the app's event names you can watch

await customer.disconnect(accountId);                                 // the customer gets connection.revoked
await customer.renameAccount(accountId, "Front desk calendar");
await customer.update({ name: "Acme Dental Ltd" });                   // or { status: "suspended" }
await customer.callLog("req-abc");                                    // what fastn did for your request id

// Your webhook, once for the whole account.
const hook = await fastn.webhooks.create({ url: "https://app.acme.com/fastn", eventTypes: [EventTypes.connectionConnected, EventTypes.setupLinkCompleted, EventTypes.connectorEvent, EventTypes.unifiedEvent] });
hook.secret                                                           // shown once: the signing secret for parseEvent
await fastn.webhooks.test(hook.id); await fastn.webhooks.deliveries(hook.id); await fastn.webhooks.rotateSecret(hook.id);
// Anything the unified API does not cover: run the connector's own action with the customer's connection.
const r = await customer.runAction({ connector: Apps.hubspot, action: "updateDeal", input: { dealId, properties: { dealstage: "qualifiedtobuy" } } });
r.data.response                                                       // the provider's answer

Prefer setupLink() for credentials: fastn's hosted page collects them, so they never pass through your backend. For auth types other than custom-input forms the platform may store the credential under a fixed field name that differs from the auth method's contract; that is a platform limitation being tracked, not something the SDK can see.

Responses and errors

Answers are returned exactly as the REST API sends them (usually { data: ... }; unified records are { id, provider, data }), so the SDK agrees 1:1 with the REST docs and Postman collection. Typed where the spec describes the answer, Record<string, any> otherwise.

Non-2xx answers throw FastnApiError with status, code (e.g. NOT_FOUND, VALIDATION_ERROR, NO_CONNECTED_PROVIDER, OPERATION_NOT_SUPPORTED, PROVIDER_AMBIGUOUS), message, details, body and requestId (quote it when asking fastn for help), plus the helpers isNotFound, isConflict, isRateLimited, isAuth and isValidation. Network failures throw FastnNetworkError. GET calls are retried on 429/502/503/504 (default 2 retries, honouring Retry-After). A write is retried only when it carries an idempotencyKey and the platform de-duplicates that operation by it (unified record create, update and delete, workflow execute; each such method says so in its doc). Every other write is never retried, so a lost answer cannot make it act twice.

try { await fastn.orgs.getTenant({ id }); }
catch (e) { if (e instanceof FastnApiError && e.isNotFound) ... }

Per-call options

Every method takes an optional second argument:

{ idempotencyKey, correlationId, customer, installationId, targetOrg, headers, extraBody, extraQuery, timeoutMs, retries, signal }

extraBody / extraQuery send fields the SDK does not know yet (a new API field before the SDK catches up); they are passed as given and not validated.

customer is { ref: "acme-42" } (your id) or { id: "<fastn org id>" }. new FastnClient({ defaults: { customer: { ref: "acme-42" } } }) applies options to every call.

Workspace tools: run anything your workspace has turned on

The platform API above is one door. The other is your fastn Connect workspace (connect.fastn.dev > Tools): every connector you switch on there, Google Calendar, HubSpot, Slack, the Fastn Workspace itself, becomes a tool the SDK can list and run. Nothing in the SDK is tied to a connector; turn one on in the workspace and it shows up.

import { Apps, ToolsClient } from "@fastn-ai/sdk";        // or new FastnClient({ apiKey, tools: { key, url } }).tools

const connect = new ToolsClient({ key: process.env.FASTN_TOOLS_KEY!, url: "https://mcp.fastn.dev/<workspace>" });
const tools = await connect.tools();                           // every tool your workspace has switched on, with its input schema
const cal = await connect.toolsOf(Apps.googleCalendar);        // one connector's tools

// Call them as typed methods, generated from your workspace (next section): no tool names typed by hand.
// tools.customer("acme-42").googleCalendar.listEvents({ calendarId: "primary", timeMin: new Date().toISOString() })
  • The workspace key is a different credential from the platform API key. Read it from Settings > API keys in connect.fastn.dev.
  • customer(ref) sends X-Org-Id: ref. A delegated key requires it on every call (400 otherwise); set it once with defaults: { customer: { ref } } when one client serves one customer.
  • userId (sent as X-User-Id) is needed when the workspace runs calls as each person.
  • Failures throw ToolError with kind "http" (gateway), "rpc" (JSON-RPC, e.g. unknown tool), "tool" (the tool ran and answered an error) or "auth" (the app is not connected for this customer yet), plus status, code, tool, connector, the gateway's message, and the helpers isUnknownTool and isAuth. For "auth", e.isConnectionRequired is true and e.authorizeUrl is where to send the customer, so an agent can ask the user to connect at runtime instead of failing.
  • In "one shared account" mode every call runs as that one account, whatever customer you name. Per-customer accounts need the "each customer org" mode in the workspace.
  • The customer is named once. Fastn Workspace tools that act for a customer (unified records) take it as the endOrgRef argument, and customer(ref) fills that in for every tool whose input schema has it, from one tools/list per workspace (shared by every customer() clone, kept for 10 minutes). An endOrgRef or endOrgId you pass yourself wins; customerArg: false on the client turns the fill off. Tools of other connectors are left alone: the customer reaches them through X-Org-Id. Call await connect.tools() once at startup if the first call must not pay for the listing.
  • One name for the customer: write orgId in the arguments of any Fastn Workspace tool. Where the tool spells it endOrgId or end_org_id (and has no orgId of its own), the SDK passes the value under that spelling, so your code never carries the older names; a value you pass under the old name is left as it is.
  • Answers of Fastn Workspace tools and parsed events come back without fastn's internal customer id keys (endOrgId, end_org_id): the only customer id you see is the one you chose (orgId / tenantRef). r.text keeps the raw answer; parseEvent(..., { internalIds: true }) keeps the id in an event.

Typed methods for your workspace: fastn-sdk-tools

Tool names as strings work, but your editor cannot check them. The generator turns your workspace's tool list into one TypeScript module with a class per connector and a method per tool, each input typed from the tool's schema:

Run it from a project that has @fastn-ai/sdk installed (the command ships in this package; there is no separate fastn-sdk-tools package on npm). Without a local install, use npx -p @fastn-ai/sdk fastn-sdk-tools ....

# once, and again whenever the workspace changes (commit the file)
export FASTN_TOOLS_KEY=...                                        # the workspace key; it never ends up in the file
npx fastn-sdk-tools --url https://mcp.fastn.dev/<workspace> --out src/fastn-tools.ts
npx fastn-sdk-tools --url https://mcp.fastn.dev/<workspace> --out src/fastn-tools.ts --check   # CI: exit 1 on drift
npx fastn-sdk-tools --import ./my-client.js ...                    # optional: module to import the client from
import { ToolsClient } from "@fastn-ai/sdk";
import { createTools } from "./fastn-tools.js";

const tools = createTools(new ToolsClient({ key: process.env.FASTN_TOOLS_KEY!, url: "https://mcp.fastn.dev/<workspace>" }));
// or createTools(fastn) with a FastnClient created with tools: { key, url }

const acme = tools.customer("acme-42");                                       // X-Org-Id on every call, endOrgRef filled where a tool takes it
const cals = await acme.googleCalendar.listCalendars({});                     // tools.<connector>.<tool>(args, opts?)
const created = await acme.hubspot.createContact({ email: "[email protected]", firstname: "Jane" });   // args typed from the schema
const cats = await tools.fastnPlatform.listUnifiedCategories();               // a tool with no inputs takes no argument
created.data                                                                  // ToolResult, as from call(); ToolError on failure
  • --url + --key (or FASTN_TOOLS_URL / FASTN_TOOLS_KEY; only the CLI reads these) fetch the listing; --from tools-list.json generates offline from a saved tools/list (the same list gives the same file). --customer <ref> fetches the listing as that customer. The header records the url, customer and tool count, never the key.
  • The module exports createTools, Tools, one class per connector (GoogleCalendarTools), one input interface per tool (GoogleCalendarListEventsInput), an output type where the tool publishes an outputSchema (else the answer is unknown), and TOOL_NAMES. Tools without a connector prefix (skill, feedback) are methods on the root.
  • Names are made identifiers (google-calendar -> googleCalendar, create-contact -> createContact); a clash after that gets a name-derived variant (create_contact, createContactTool). Property names stay as the schema spells them (quoted when needed). A tool whose schema says nothing about a field types it unknown; an object with no declared properties is Record<string, unknown>.
  • tools.client is the ToolsClient under the methods, for a tool the file does not know yet; client.rpc(method, params) sends any other JSON-RPC call to the workspace.
  • Programmatic use: import { generateToolsModule } from "@fastn-ai/sdk/codegen". The schema-to-type step is json-schema-to-typescript, the package's one runtime dependency (the CLI runs from the installed package, so it cannot be a devDependency); it is loaded by the generator only, never by the client.

Events (webhooks)

import { Apps, EventTypes, parseEvent, verifyEventSignature } from "@fastn-ai/sdk";

// Express: app.post("/fastn/events", express.raw({ type: "application/json" }), (req, res) => {
const event = parseEvent(process.env.FASTN_EVENT_SECRET!, req.headers, req.body);   // throws on a bad signature
// event.id is stable across retries: drop repeats by id.
switch (event.type) {
  case EventTypes.connectionConnected: if (event.app === Apps.hubspot) { /* start a watch */ } break;
  case EventTypes.setupLinkCompleted: /* event.connections: app -> account id */ break;
  case EventTypes.unifiedEvent: /* event.action: UnifiedActions.created | updated | deleted */ break;
}

The signature is sha256=HMAC-SHA256(secret, "<X-Fastn-Timestamp>.<raw body>"); deliveries older than 5 minutes are rejected ({ toleranceSeconds }, 0 disables the check). Always pass the raw body, never a re-serialised object. parseEvent throws a plain Error on a bad signature. verifyEventSignature answers a boolean instead, readEventHeaders reads the fastn headers, and signEventBody signs a body the same way (for tests).

Pagination

import { paginate } from "@fastn-ai/sdk";
for await (const ev of paginate((i) => fastn.events.listEvents(i), { outcome: EventOutcomes.sentToDlq }, { pageSize: 100 })) ...

Works for limit/offset lists and cursor lists; stops on a short page, a missing cursor, or a list that ignores paging.

Development

npm run generate     # spec/openapi-*.json -> src/generated (ops, types, api)
npm test             # unit tests with a mocked fetch
FASTN_API_KEY=... FASTN_TEST_CUSTOMER=... npm run test:live   # live run on api.fastn-test.dev (creates and removes test data)
# FASTN_BASE_URL=https://api.fastn.dev LIVE_PROD=1 ... runs the same suite on production
# FASTN_API_KEY=... npx tsx scripts/sweep/run.ts   # calls every published operation once on dev and writes a per-operation report
npm run build        # dist/ (esm + cjs + d.ts)
npm run spec:refresh -- /path/to/fastn-platform   # pull a newer spec from a platform checkout

spec/SOURCE.md records which platform commit the spec came from. The spec has more operations than the SDK exposes: 461 remain after the platform-administration and backend-only routes are hidden.

What stays compatible

  • The hand-written layer (FastnClient, customer(...), records(...), ToolsClient, parseEvent, paginate, the error classes) follows semver: breaking changes only in a major version. Until 1.0 they can still land in a minor and are listed under Changed in the CHANGELOG.
  • The generated methods mirror the REST API 1:1. spec/surface.lock.json records every operation with its input keys (where each goes on the wire and whether it is required) and its output fields (path, structural type, whether always present; no descriptions). npm run generate refuses a spec that removes an operation, renames or moves an input, makes an optional input required, adds a required input, removes or retypes an output field, or stops guaranteeing one, unless run with --accept-breaking, which goes with a major version bump. New operations, new optional inputs, new output fields and description edits pass. npm run generate:check -- --spec=<file> reports what a spec would change without writing anything (exit 1 on a break), for CI and for trying a refresh.
  • Everything under unstable (the wire manifest OPS, FetchTransport) has no compatibility promise.

Releasing

  1. Bump version in node/package.json and update node/CHANGELOG.md in a PR.
  2. After merge, tag main and push the tag:
    git tag node/v<version> && git push origin node/v<version>
    
  3. The Publish Node SDK workflow checks the tag matches package.json, runs the checks, and runs npm stage publish. It uses the NPM_TOKEN secret (a stage-only token) from the npm GitHub environment.
  4. A maintainer opens npmjs.com > Staged Packages, reviews the version and approves it with 2FA. Only then is it installable.

FAQ

What is fastn? The embedded integration platform for B2B SaaS. Your customers connect their own apps (HubSpot, Google Calendar, Slack, and 1,000+ more) inside your product, and your code and AI agents use those connections through one API.

How do I add customer-facing integrations to my Node.js or TypeScript app? Install @fastn-ai/sdk, register each customer with fastn.customer(yourId).register(...), let them connect apps with a setup link (customer.connect(app)) or the @fastn-ai/embed widget, then call customer.crm, customer.calendar, or customer.runAction(...). See Embed fastn in your product.

Does fastn support multi-tenant apps? Yes. Every call is scoped to one customer, addressed by your own id; credentials are stored and refreshed per customer; a customer can connect several accounts of one app.

Can AI agents like Claude use fastn integrations? Yes: as workspace tools from this SDK (ToolsClient, typed with fastn-sdk-tools), or through the fastn MCP gateway from Claude, Cursor, or any MCP client.

Is there a Python SDK? Yes: fastn-ai (pip install fastn-ai). Its fastn.platform client has the same surface as this package's platform client, and it adds tool calling for OpenAI, Anthropic, Gemini, and Bedrock, flows, and a CLI. This package's workspace tools (ToolsClient, fastn-sdk-tools) are Node only.

License

MIT

版本与发布记录

@fastn-ai/sdk 共发布 11 个版本,首次发布于 2026/10/23;过去 12 个月发布了 11 个版本。

版本发布日期距今
0.5.1latest2026/11/0113 天前
0.5.02026/10/3014 天前
0.4.42026/10/3014 天前
0.4.22026/10/2916 天前
0.4.12026/10/2916 天前
0.4.02026/10/2916 天前

关于 @fastn-ai/sdk 的常见问题

@fastn-ai/sdk 是什么?

fastn SDK for Node.js and TypeScript: embedded integrations for multi-tenant SaaS and AI agents. Your customers connect their own apps inside your product, through one typed API.

如何安装 @fastn-ai/sdk?

在项目目录运行 npm install @fastn-ai/sdk,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 fastn-sdk-tools,可以用 npx fastn-sdk-tools 直接运行。

@fastn-ai/sdk 支持 TypeScript 吗?

支持。包内自带 TypeScript 类型声明(package.json 声明了 types 入口),安装后即可获得类型提示,无需再安装 @types 包。

@fastn-ai/sdk 支持 ESM 还是 CommonJS?

同时提供 ES Module 与 CommonJS 入口,import 和 require() 都可以使用,打包工具会自动选择合适的版本。

@fastn-ai/sdk 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=20。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@fastn-ai/sdk 可以商用吗?

@fastn-ai/sdk 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@fastn-ai/sdk 的下载量有多少?

根据 npm 官方下载统计,@fastn-ai/sdk 在 2026/10/15 – 2026/11/13 这 30 天内被下载 2,378 次,日均约 79 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@fastn-ai/sdk 还在维护吗?

最新版本 0.5.1 发布于 2026/11/01(13 天前);至今共发布 11 个版本,其中过去 12 个月发布了 11 个;首次发布于 2026/10/23。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。