npm.io
0.4.0 • Published 1 week ago

@vennio/sdk

Licence
MIT
Version
0.4.0
Deps
1
Size
796 kB
Vulns
0
Weekly
0

@vennio/sdk

Scheduling API primitives for JavaScript and TypeScript apps. List bookings, create shareable Venn Links, accept availability from your customers — without building calendar OAuth, availability logic, or timezone handling yourself.

Works in Node.js and the browser; designed to plug into AI agents that schedule on behalf of users.

Installation

npm install @vennio/sdk

Node 18+. ES modules and CommonJS both supported.

Quick Start

import { Vennio, VennioApiError } from '@vennio/sdk';

const vennio = new Vennio(process.env.VENNIO_API_KEY!);

// List the authenticated business's recent bookings
const { bookings } = await vennio.bookings.list({ limit: 10 });
console.log(bookings.map((b) => `${b.customer_email}${b.start_time}`));

// Create a Venn Link — a shareable URL recipients use to book time with you
const link = await vennio.vennLinks.create({
  title: '30-min intro call',
  duration_minutes: 30,
  expiresIn: '7d',
});
console.log(`Share this URL: ${link.url}`);

API Keys

Get keys from https://vennio.app/api-keys. Four shapes — the prefix tells you both scope (publishable / secret) and environment (live / test):

  • vennio_pk_live_… — publishable, live. Read-only-ish; safe to ship in a browser bundle. Use for client-side widgets and embeds.
  • vennio_sk_live_… — secret, live. Full access: booking creation, cancel, admin operations. Server-side only. Never ship in a client bundle.
  • vennio_pk_test_… / vennio_sk_test_… — same scopes, but every request runs in test mode (see below).
Test mode

Requests authenticated with a _test_ key are real API calls that hit the real database — bookings, Venn Links, and proposals all persist and are queryable — but side effects are suppressed server-side:

  • No email is sent to customers or principals.
  • No calendar event is created in Google / Microsoft / iCal feeds.
  • No webhooks fire to your registered endpoints.
  • No CRM sync (HubSpot, Salesforce) runs.

This is the safe way to iterate against your own account from a script without spamming yourself or real customers. Test data is fully isolated from live data — listing bookings with a test key returns only test-mode bookings.

const vennio = new Vennio(process.env.VENNIO_API_KEY!); // sk_test_… while iterating
const created = await vennio.bookings.create({ /* … */ });
// → persisted in DB, no email, no calendar event, no webhook

For one-off dry runs against a live key (e.g. an integration test in CI that uses production credentials), pass demo_mode: true on the request:

await vennio.bookings.create({ /* … */, demo_mode: true });
// → same side-effect suppression, but on a per-request basis

When you're ready to ship, swap the key prefix from _test_ to _live_. No other code changes.

Error handling

Every method on vennio.bookings, vennio.vennLinks, and the other resource namespaces throws a VennioApiError on API errors (4xx/5xx). Network errors and request-construction errors throw the underlying TypeError / AbortError etc.

import { Vennio, VennioApiError } from '@vennio/sdk';

const vennio = new Vennio(process.env.VENNIO_API_KEY!);

try {
  const booking = await vennio.bookings.create({
    business_id: 'your-business-uuid',
    customer_email: 'jane@example.com',
    customer_name: 'Jane Doe',
    start_time: '2026-06-01T14:00:00Z',
    end_time: '2026-06-01T14:30:00Z',
  });
  console.log('created', booking);
} catch (err) {
  if (err instanceof VennioApiError) {
    // err.code:    short string — 'unauthorized', 'validation_error',
    //              'not_found', 'rate_limit_exceeded', etc.
    // err.status:  HTTP status (e.g. 401, 400, 404, 429).
    // err.details: raw API response body for debugging / form-level errors.
    console.error(`vennio: ${err.code} (${err.status}) — ${err.message}`);
  } else {
    throw err;
  }
}

Resource namespaces

The Vennio class exposes a curated surface for the most common scheduling primitives. Endpoints outside the curated surface remain reachable through vennio.raw — the underlying generated client.

Curated
  • vennio.bookingscreate, list, get, update, cancel, stats
  • vennio.vennLinkscreate, list, get, update, delete, bookings
  • vennio.webhooksverify for inbound payloads (see Consuming webhooks below). Webhook management methods (create, list, get, update, delete, regenerateSecret) land in a subsequent release; until then use vennio.raw.webhooks.*.
  • vennio.proposals, vennio.calendars, vennio.availability — scaffolded; fuller method coverage lands in subsequent releases.
Available via vennio.raw

Anything not on the curated surface is reachable through vennio.raw.<namespace>.

Always on .raw — these admin, meta, and network-graph endpoints aren't part of the curated surface:

  • vennio.raw.network — network-graph queries
  • vennio.raw.eventTypes — event-type management
  • vennio.raw.consents — consent records
  • vennio.raw.apiKeys — API key management

Available on .raw only — curation may follow if usage warrants it:

  • vennio.raw.invitations
  • vennio.raw.accessRequests
  • vennio.raw.hubspot
  • vennio.raw.schedules

.raw methods return a hey-api RequestResult — a discriminated union shaped as { data, error, request, response } — instead of throwing. Narrow on result.error (or result.data) before reading the payload:

const result = await vennio.raw.eventTypes.list();
if (result.error) {
  console.error('eventTypes.list failed', result.error);
} else {
  console.log(result.data);
}

Note the error-model difference from the curated namespaces: methods on vennio.bookings, vennio.vennLinks, etc. throw VennioApiError on 4xx/5xx (see Error handling above); .raw methods do not throw — they place the error in result.error for you to handle.

Consuming webhooks

Every Vennio webhook delivery is signed with the webhook's secret. The signature arrives in the X-Vennio-Signature header as sha256=<hex_digest>, where the digest is HMAC-SHA256 of the raw request body keyed by the webhook secret. Verify every payload before trusting it — vennio.webhooks.verify handles the HMAC, the sha256= prefix, and the timing-safe comparison.

Pass the raw bytes the server received, not a re-stringified copy. Re-serialising changes key ordering and whitespace and invalidates the signature.

// Express
import express from 'express';
import { Vennio } from '@vennio/sdk';

const vennio = new Vennio(process.env.VENNIO_API_KEY!);
const app = express();

app.post(
  '/webhooks/vennio',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const ok = vennio.webhooks.verify(
      req.body, // Buffer — the raw bytes
      req.header('X-Vennio-Signature') ?? '',
      process.env.VENNIO_WEBHOOK_SECRET!,
    );
    if (!ok) return res.status(401).end();

    const event = JSON.parse(req.body.toString('utf8'));
    // Idempotency: dedupe on event.id. Retries reuse the same id.
    // Respond 2xx fast; do slow work after the response (queue, defer, …).
    res.status(200).end();

    handle(event).catch(console.error);
  },
);
// Next.js App Router route handler
import { Vennio } from '@vennio/sdk';

const vennio = new Vennio(process.env.VENNIO_API_KEY!);

export async function POST(request: Request) {
  const raw = await request.text(); // read BEFORE .json()
  const ok = vennio.webhooks.verify(
    raw,
    request.headers.get('x-vennio-signature') ?? '',
    process.env.VENNIO_WEBHOOK_SECRET!,
  );
  if (!ok) return new Response('invalid signature', { status: 401 });

  const event = JSON.parse(raw);
  // ...dedupe on event.id, fan out by event.type...
  return new Response(null, { status: 200 });
}

Operational notes. Vennio retries failed deliveries on an exponential backoff (1 min → 5 min → 30 min). Retries carry the same event.id and a fresh signature — dedupe on the id, not on the signature. The webhook secret is returned once when you create the webhook; reveal it later from the dashboard or rotate via vennio.raw.webhooks.regenerateSecret.

TypeScript

Full types included — no @types package needed:

import {
  Vennio,
  VennioApiError,
  type Booking,
  type VennLink,
  type CreateBookingResult,
} from '@vennio/sdk';

Every request shape, response shape, and error shape is generated from the canonical OpenAPI spec at https://api.vennio.app and re-exported.

AI Agent usage

The SDK works well with agents that schedule on behalf of users. Surface the methods as tools; let the model pick:

// Tool: list_bookings
async function list_bookings(args: { limit?: number }) {
  const { bookings } = await vennio.bookings.list(args);
  return bookings.map((b) => ({
    id: b.id,
    when: `${b.start_time}${b.end_time}`,
    with: b.customer_email,
    status: b.status,
  }));
}

For schedule-on-behalf-of-user flows that need consent gates, mutual availability, or proposal back-and-forth, drop down to vennio.raw.* until those namespaces are curated.

Migrating from 0.1.x

The Vennio class is the modern, namespaced API. The 0.1.x flat surface (vennio.getSlots, vennio.book) is still exported as LegacyVennio for incremental migration:

import { LegacyVennio } from '@vennio/sdk';
// ...behaves exactly like the old default export

LegacyVennio is deprecated and will be removed in 1.0.0.

Documentation

Full API reference: https://docs.vennio.app

License

MIT