@vennio/sdk
v0.3.0
Published
Scheduling API for JS/TS apps and AI agents — bookings, shareable Venn Links, and bidirectional availability. Full TypeScript types.
Maintainers
Readme
@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/sdkNode 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 webhookFor 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 basisWhen 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: '[email protected]',
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.bookings—create,list,get,update,cancel,statsvennio.vennLinks—create,list,get,update,delete,bookingsvennio.webhooks—verifyfor inbound payloads (see Consuming webhooks below). Webhook management methods (create,list,get,update,delete,regenerateSecret) land in a subsequent release; until then usevennio.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 queriesvennio.raw.eventTypes— event-type managementvennio.raw.consents— consent recordsvennio.raw.apiKeys— API key management
Available on .raw only — curation may follow if usage warrants it:
vennio.raw.invitationsvennio.raw.accessRequestsvennio.raw.hubspotvennio.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 exportLegacyVennio is deprecated and will be removed in 1.0.0.
Documentation
Full API reference: https://docs.vennio.app
License
MIT
