Skip to content
API Blog

@lobu/connector-sdk

API reference for @lobu/connector-sdk. For a tutorial-style introduction see the Connector SDK guide; for the reactions surface (also exported from this package) see the Reactions reference.

Every symbol below is a re-export from the package’s top-level entry point:

import {
ConnectorRuntime,
type ConnectorDefinition,
type SyncContext,
type SyncResult,
type EventEnvelope,
type ActionContext,
type ActionResult,
} from "@lobu/connector-sdk";

Abstract base class. Every connector extends it.

abstract class ConnectorRuntime {
abstract readonly definition: ConnectorDefinition;
abstract sync(ctx: SyncContext): Promise<SyncResult>;
abstract execute(ctx: ActionContext): Promise<ActionResult>;
authenticate(ctx: AuthContext): Promise<AuthResult>; // default throws
}
MemberRequiredDescription
definitionyesStatic metadata: key, name, version, auth, feeds, actions. See ConnectorDefinition.
sync(ctx)yesPull data. Receives last checkpoint, returns events + new checkpoint.
execute(ctx)yesRun an action. Stub it with { success: false, error: "no actions" } for read-only connectors.
authenticate(ctx)noOnly required when authSchema.methods includes { type: "interactive" }. Stream AuthArtifacts, await UI signals.

interface ConnectorDefinition {
key: string; // 'google.gmail'
name: string;
description?: string;
version: string; // semver
authSchema?: ConnectorAuthSchema;
feeds?: Record<string, FeedDefinition>;
actions?: Record<string, ActionDefinition>;
optionsSchema?: Record<string, unknown>; // JSON Schema for global options
faviconDomain?: string; // e.g. 'x.com'
mcpConfig?: { upstreamUrl: string }; // proxy to an upstream MCP server
openapiConfig?: { // auto-generate from OpenAPI
specUrl: string;
includeOperations?: string[];
excludeOperations?: string[];
includeTags?: string[];
serverUrl?: string;
};
requiredCapability?: string; // e.g. 'screentime' (device-only)
runtime?: ConnectorRuntimeInfo; // pin to a device platform
}
interface ConnectorRuntimeInfo {
platforms: Array<"ios" | "android" | "macos" | "windows" | "linux">;
scopes?: string[]; // forwarded to the native platform adapter
nix?: { packages: string[] }; // native deps provisioned at run time
}

platforms and scopes describe device-bound connectors; omit the runtime block for cloud-fleet connectors that do not pin to a device.

nix.packages lists native system dependencies as nixpkgs attribute refs (for example ["ffmpeg", "imagemagick"]) the connector needs on PATH at execution time. npm dependencies are bundled into the connector at compile time and do not go here. Backends that can run native deps (embedded, container, machine) provision them via nix-shell; backends that cannot (for example edge workers) reject a connector that declares them. See Dependencies in the guide for the npm-vs-native split.


interface ConnectorAuthSchema {
methods: ConnectorAuthMethod[];
}
type ConnectorAuthMethod =
| ConnectorAuthNone
| ConnectorAuthEnvKeys
| ConnectorAuthOAuth
| ConnectorAuthBrowser
| ConnectorAuthInteractive;
{ type: "none" }
interface ConnectorAuthEnvKeys {
type: "env_keys";
required?: boolean;
scope?: "connection" | "organization";
fields: ConnectorAuthEnvField[];
description?: string;
}
interface ConnectorAuthEnvField {
key: string;
label?: string;
description?: string;
example?: string;
secret?: boolean;
required?: boolean;
}
interface ConnectorAuthOAuth {
type: "oauth";
provider: string;
requiredScopes: string[];
optionalScopes?: string[];
required?: boolean;
description?: string;
scope?: "connection" | "organization";
authorizationUrl?: string;
tokenUrl?: string;
userinfoUrl?: string;
authParams?: Record<string, string>;
tokenEndpointAuthMethod?: "client_secret_post" | "client_secret_basic" | "none";
usePkce?: boolean;
loginScopes?: string[];
clientIdKey?: string;
clientSecretKey?: string;
setupInstructions?: string;
loginProvisioning?: { autoCreateConnection?: boolean };
}
interface ConnectorAuthBrowser {
type: "browser";
required?: boolean;
description?: string;
capture?: "cli" | "cdp"; // cli = lobu memory browser-auth; cdp = remote debug
requiredDomains?: string[];
defaultCdpUrl?: string; // default: http://127.0.0.1:9222
}
interface ConnectorAuthInteractive {
type: "interactive";
required?: boolean;
description?: string;
scope?: "connection" | "organization";
expectedArtifact?: "qr" | "code" | "redirect" | "prompt" | "status";
timeoutSec?: number; // default 300
}

Connectors declaring this method must implement authenticate(ctx) and stream AuthArtifacts.


interface FeedDefinition {
key: string;
name: string;
description?: string;
requiredScopes?: string[]; // OAuth scopes that must be held
displayNameTemplate?: string; // "{subreddit} - {content_type}"
configSchema?: Record<string, unknown>; // JSON Schema for per-feed config
userManaged?: boolean; // skip auto-wire creation
eventKinds?: Record<string, {
description?: string;
metadataSchema?: Record<string, unknown>;
entityLinks?: EntityLinkRule[]; // declarative entity wiring
}>;
}

Declares how events emitted by a feed link to dimension entities.

interface EntityLinkRule {
entityType: string; // target slug, e.g. "$member"
autoCreate?: boolean;
titlePath?: string;
identities: EntityIdentitySpec[];
traits?: Record<string, EntityTraitSpec>;
}
interface EntityIdentitySpec {
namespace: string; // 'phone', 'email', or custom
eventPath: string; // dot path into the event
matchOnly?: boolean;
}
interface EntityTraitSpec {
eventPath: string;
behavior: "init_only" | "prefer_non_empty" | "overwrite";
}

Per-install override stored on connector_definitions:

interface EntityLinkOverride {
disable?: boolean;
retargetEntityType?: string;
autoCreate?: boolean;
maskIdentities?: string[];
}
type EntityLinkOverrides = Record<string, EntityLinkOverride>;

Canonical namespaces for cross-connector identity. Use these when targeting $member so identities align automatically:

const IDENTITY = {
PHONE: "phone",
EMAIL: "email",
WA_JID: "wa_jid",
SLACK_USER_ID: "slack_user_id",
GITHUB_LOGIN: "github_login",
GITHUB_USER_ID: "github_user_id",
GITHUB_REPO_ID: "github_repo_id",
GITHUB_REPO_FULL_NAME: "github_repo_full_name",
AUTH_USER_ID: "auth_user_id",
GOOGLE_CONTACT_ID: "google_contact_id",
} as const;
type IdentityNamespace = (typeof IDENTITY)[keyof typeof IDENTITY];
enum FeedMode {
sync = "sync", // connector code runs on a worker
virtual = "virtual", // backed by saved queries (future)
}

interface ActionDefinition {
key: string;
name: string;
description?: string;
requiresApproval: boolean;
annotations?: {
destructiveHint?: boolean;
openWorldHint?: boolean;
idempotentHint?: boolean;
};
inputSchema?: Record<string, unknown>; // JSON Schema
outputSchema?: Record<string, unknown>;
}

interface SyncContext {
feedKey: string;
config: Record<string, unknown>;
checkpoint: Record<string, unknown> | null;
credentials: SyncCredentials | null;
entityIds: number[];
sessionState?: Record<string, unknown> | null;
emitEvents?: (events: EventEnvelope[]) => Promise<void>;
updateCheckpoint?: (checkpoint: Record<string, unknown> | null) => Promise<void>;
}
interface SyncCredentials {
provider: string;
accessToken: string;
refreshToken?: string | null;
expiresAt?: string | null;
scope?: string | null;
}

emitEvents and updateCheckpoint are present only on the long-running sync path. For short syncs return the full result and ignore both hooks.

Where credentials land. ctx.credentials is populated only for oauth auth (the gateway hands you the resolved SyncCredentials). For env_keys auth, the values the user filled into the form are merged into ctx.config under the keys you declared on each ConnectorAuthEnvField. For browser auth, captured cookies arrive on ctx.sessionState. For none, all three are null/empty. Same rules apply to ActionContext.


interface SyncResult {
events: EventEnvelope[];
checkpoint: Record<string, unknown> | null;
auth_update?: Record<string, unknown> | null;
metadata?: {
items_found?: number;
items_skipped?: number;
[key: string]: unknown;
};
}

auth_update is for connectors whose credentials rotate during sync (browser cookies, opaque session tokens). The gateway persists it back onto the linked auth profile.


The output shape of every connector-emitted event. Each envelope becomes a row in events.

interface EventEnvelope {
origin_id: string; // required: platform's unique ID
origin_type?: string; // source-native item type
payload_type?: "text" | "markdown" | "json_template" | "media" | "empty";
payload_text: string; // required: main content
payload_data?: Record<string, unknown>; // structured data
payload_template?: Record<string, unknown> | null;
attachments?: unknown[];
title?: string;
author_name?: string;
source_url?: string;
occurred_at: Date; // required: when it happened
semantic_type?: string; // content/note/summary/fact/…
score?: number; // 0-100 engagement
origin_parent_id?: string; // hierarchical content
metadata?: Record<string, unknown>;
embedding?: number[]; // pre-computed
}

interface ActionContext {
actionKey: string;
input: Record<string, unknown>;
credentials: SyncCredentials | null;
config: Record<string, unknown>;
}
interface ActionResult {
success: boolean;
output?: Record<string, unknown>;
error?: string;
}

For connectors with { type: "interactive" } auth.

interface AuthContext {
config: Record<string, unknown>;
previousCredentials: Record<string, unknown> | null;
emit: (artifact: AuthArtifact) => Promise<void>;
awaitSignal: (
name: string,
options?: { timeoutMs?: number }
) => Promise<Record<string, unknown>>;
signal: AbortSignal;
}
type AuthArtifact =
| { type: "qr"; value: string; expiresAt?: string; instructions?: string }
| { type: "code"; value: string; expiresAt?: string; instructions?: string }
| {
type: "redirect";
url: string;
mode: "popup" | "same-tab";
awaitSignal: string;
instructions?: string;
}
| {
type: "prompt";
fields: Array<{
key: string;
label: string;
kind: "text" | "password" | "otp";
required?: boolean;
}>;
submitSignal: string;
instructions?: string;
}
| { type: "status"; message: string };
interface AuthResult {
credentials: Record<string, unknown>;
metadata?: {
account_id?: string;
display_name?: string;
expires_at?: string;
[key: string]: unknown;
};
}

ctx.emit(artifact) streams the next thing the UI should show. Each emit replaces the previously active artifact. Pause on ctx.awaitSignal("name") until the UI POSTs the matching signal.


DB-backed types the runtime hands you in admin contexts. Reads only — connectors never write these.

interface Connection {
id: number;
organizationId: string;
connectorKey: string;
displayName?: string;
status: "active" | "paused" | "error" | "revoked";
accountId?: string;
credentials?: Record<string, unknown>;
entityIds?: number[];
config?: Record<string, unknown>;
errorMessage?: string;
createdBy?: string;
createdAt: Date;
updatedAt: Date;
}
interface Feed {
id: number;
organizationId: string;
connectionId: number;
feedKey: string;
status: "active" | "paused" | "error";
entityIds?: number[];
config?: Record<string, unknown>;
checkpoint?: Record<string, unknown>;
syncIntervalMs?: number;
nextSyncAt?: Date;
lastSyncAt?: Date;
lastSyncStatus?: string;
lastError?: string;
consecutiveFailures: number;
itemsCollected: number;
createdAt: Date;
updatedAt: Date;
}
type RunType = "sync" | "action" | "code" | "watcher" | "auth";
type RunStatus = "pending" | "running" | "completed" | "failed" | "cancelled" | "timeout";
type ApprovalStatus = "pending" | "approved" | "rejected" | "auto";
interface Run {
id: number;
organizationId: string;
runType: RunType;
feedId?: number;
connectionId?: number;
actionKey?: string;
actionInput?: Record<string, unknown>;
actionOutput?: Record<string, unknown>;
approvalStatus: ApprovalStatus;
status: RunStatus;
claimedBy?: string;
claimedAt?: Date;
lastHeartbeatAt?: Date;
completedAt?: Date;
connectorKey?: string;
connectorVersion?: string;
checkpoint?: Record<string, unknown>;
itemsCollected: number;
errorMessage?: string;
createdAt: Date;
}

The package also re-exports a few utilities so connectors share one implementation:

ExportPurpose
ky, HTTPError, KyInstance, OptionsShared HTTP client.
withHttpRetry(fn, opts?)Retry-with-backoff wrapper for transient HTTP failures.
calculateEngagementScore(signals)Maps raw engagement metrics into a normalised 0–100 score.
Type, StaticRe-exported TypeBox builders for configSchema / inputSchema / outputSchema.
sdkLogger (alias logger)Connector-scoped logger; output is captured by the run record.
normalizeEmail, normalizePhone, normalizeGithubLogin, …Identifier normalisers — call these before populating EntityIdentitySpec paths.
SOURCE_NATIVE_EVENT_TYPES, isSourceNativeEventTypeThe canonical event-type taxonomy.
WATCHER_TIME_GRANULARITIES, alignToWatcherWindowStart, …Time helpers used by watcher scheduling.
Browser SDK: acquireBrowser, launchBrowser, launchStealthBrowser, CdpPage, browserNetworkSync, etc.Headless / CDP / stealth browser primitives for browser and cdp capture.

See the source on GitHub for the full helper surface.