Getting Started

Quickstart

Prerequisites, account setup, the runtime entry point, and verification — the whole path in order.

Prerequisites

Clivly embeds into an app you already have. It does not create one, and it never creates tables in your database.

  • Node.js 22 or newer. Every published package declares engines.node: ">=22".
  • An existing app. Route scaffolding covers Next.js, TanStack Start, SvelteKit, Remix and Nuxt; anywhere else you mount one fetch handler by hand.
  • Drizzle ORM — only required for clivly init to generate a config. Prisma, Kysely and raw SQL are fully supported through a hand-written fetchPage source.
  • A backend. Clivly Cloud posts to a route your app owns; a static-only site has nowhere to mount it.
  • npm, pnpm, yarn and bun all work. Examples use npm.

The five-minute path

The shortest working path, start to finish. Every step below expands on one of these five.

Package manager
terminal
# 1. Create your account + organization at https://clivly.com/sign-up
# 2. In your app's directory:
npm install clivly

# 3. Scaffold clivly.config.ts, the routes, the boot hook, and .env —
#    then pair this machine.
npx clivly init

# 4. If you skipped pairing (--no-login), or need to re-pair:
npx clivly login

# 5. Start your app. That is what connects it.
npm run dev
  • clivly init writes four files: clivly.config.ts (yours to edit), the tick route, the auth-verify route, and the boot hook for your framework. Plus env placeholders.
  • It creates no database tables and does not modify your ORM schema.
  • Then start your app the way you normally do. The boot hook checks in for you, so the dashboard shows the app as Live within seconds — no command needed.
  • npx clivly status is a read-only diagnostic for when something looks wrong. It is not part of connecting.

Create your account and organization

Sign up, create the organization, and collect the two keys before you touch any code.

  • Sign up at clivly.com/sign-up. The onboarding flow creates your organization — the tenant every key, mapping and widget belongs to.
  • Open the Connect app dialog from the dashboard or the ⌘K palette. It shows your framework's install command and an .env snippet with both keys already filled in.
  • The secret key is shown once. Regenerating it revokes the previous key, so redeploy after rotating.
  • npx clivly login does this for you: it pairs the machine over a browser approval and writes the credentials into your framework's env file.

Define the SDK once

Constructing the SDK is pure. Nothing connects until you call an entry point.

clivly.config.ts
import { createClivlySDK, defineClivlyConfig } from "clivly/sdk";
import { discoverFromDrizzle, fromDrizzle } from "clivly/drizzle";
import { db, participants, accounts } from "./db";
import * as schema from "./db/schema";

const entities = defineClivlyConfig({
entities: {
  participants: {
    concept: "contact",
    source: "participants",
    fields: { name: "full_name", email: "email" },
  },
  accounts: {
    concept: "company",
    source: "accounts",
    fields: { name: "legal_name", domain: "website" },
  },
},
});

export default createClivlySDK({
apiKey: process.env.CLIVLY_SECRET_KEY ?? process.env.CLIVLY_API_KEY!,
entities,
schema: discoverFromDrizzle(schema),
source: {
  contacts: fromDrizzle(db, participants, {
    entity: "participants",
    cursorField: "updatedAt",
  }),
  companies: fromDrizzle(db, accounts, {
    entity: "accounts",
    cursorField: "updatedAt",
  }),
},
syncTrigger: {
  path: "/api/clivly/tick",
  url: process.env.CLIVLY_SYNC_TRIGGER_URL,
  secret: process.env.CLIVLY_SYNC_TRIGGER_SECRET,
},
// Long-lived Node hosts only. The HTTP heartbeat resumes automatically
// while a socket is connecting or reconnecting.
presence: { transport: "websocket" },
});
  • source is keyed by CRM conceptcontacts / companies / deals / custom. The entity's own source: field is a host table name. Conflating them means an entity resolves to no source and silently syncs nothing.
  • cursorField and idField are Drizzle property keys (updatedAt), not database column names (updated_at).
  • A bare import "./clivly.config" will never connect — the factory is pure by design, so the SDK stays tree-shakeable.
  • After editing this file, restart your app. It reads the config once, at boot.

Choose the runtime entry point

The right call depends on whether the host is long-lived, request-scoped, or just validating credentials.

runtime.ts
const status = await clivly.connect();
if (!status.ok) throw new Error(status.reason);

await clivly.start(); // long-lived Node / Vite dev only

await clivly.runScheduled(); // one-shot cron/serverless run

export const POST = (request: Request) =>
clivly.createClivlyHandler()(request);
  • connect() is the one-shot health check and returns structured status.
  • start() is for long-lived Node processes and Vite dev only. With presence.transport: "websocket", it uses a WebSocket for liveness once the server confirms it is ready.
  • WebSocket presence is opt-in today. While it connects or reconnects, the existing HTTP heartbeat cadence stays active automatically; sync keeps its own cadence.
  • runScheduled() and createClivlyHandler() are the production path for serverless and edge runtimes.
  • Getting this wrong is the most common integration failure: on serverless, start()'s setInterval stops ticking after the first response.

Verify

One command tells you which step of the setup you are on, and what to do about it.

Package manager
terminal
npx clivly status
npx clivly status --verbose
npx clivly status --config ./apps/web/clivly.config.ts
  • The ladder: credential → Clivly reachable → config loads → config is edge-safe → setup checks → sync trigger URL.
  • Read-only. It writes nothing.
  • A working integration shows every step green, clivly: connected to <your org> on dev boot, and recent presence on the Integrations page. A connected WebSocket deployment reports its own transport and state.