Install and use the official Pivot SDK for JavaScript and TypeScript to interact with the Pivot REST API.
The official Pivot SDK for JavaScript and TypeScript provides a type-safe, ergonomic way to interact with the Pivot REST API. It handles authentication, retries with exponential backoff, and pagination automatically.
npm install @hellopivot/sdkOr with your preferred package manager:
pnpm add @hellopivot/sdkyarn add @hellopivot/sdkThe SDK is ESM-only and requires Node.js 18 or later. It works in any runtime that supports the Fetch API (Node.js, Deno, Bun, Cloudflare Workers, etc.).
Go to Organization Admin > Integrations and create a new integration. Copy the API key — you’ll need it to authenticate requests. See Getting Started with the Pivot API for detailed instructions.
import Pivot from '@hellopivot/sdk';
const pivot = new Pivot({ apiKey: 'your-api-key',});You can also set the PIVOT_API_KEY environment variable instead of passing the key directly:
export PIVOT_API_KEY=your-api-keyimport Pivot from '@hellopivot/sdk';
// Automatically reads from PIVOT_API_KEYconst pivot = new Pivot();// List spaces in an organizationconst response = await pivot.spaces.getSpacesByOrganizationId('your-org-id', 0);
console.log(response.spaces);The SDK organizes API methods into resource namespaces that mirror the API structure:
| Resource | Description |
|---|---|
pivot.blocks | Block operations (pages, assignments, etc) |
pivot.groups | Group management |
pivot.invites | Organization invite operations |
pivot.labels | Label CRUD and space label management |
pivot.rooms | Rooms, messages, recordings, members |
pivot.spaces | Space CRUD, members, roles, invites |
pivot.users | User lookup and management |
pivot.webhooks | Webhook CRUD and delivery logs |
Every method is fully typed — both the parameters and the response — so you get autocomplete and type safety out of the box.
The Pivot constructor accepts an options object:
import Pivot from '@hellopivot/sdk';
const pivot = new Pivot({ // Required: your API key (or set PIVOT_API_KEY env var) apiKey: 'your-api-key',
// Optional: override the base URL (default: https://api.pivot.app) baseUrl: 'https://api.pivot.app',
// Optional: request timeout in milliseconds (default: 30000) timeoutMs: 30_000,
// Optional: retry configuration (or set to false to disable) retry: { maxRetries: 3, // Number of retry attempts initialDelayMs: 500, // Initial delay before first retry maxDelayMs: 30_000, // Maximum delay between retries backoffMultiplier: 2, // Exponential backoff multiplier jitterFactor: 0.25, // Randomness to prevent thundering herd },
// Optional: additional headers for every request defaultHeaders: { 'X-Custom-Header': 'value', },});The SDK automatically retries failed requests with exponential backoff and jitter. By default, the following errors trigger a retry:
Client errors (4xx other than the above) are not retried since they indicate a problem with the request itself.
The SDK also respects the Retry-After header when present.
To disable retries entirely:
const pivot = new Pivot({ apiKey: 'your-api-key', retry: false,});The Pivot API uses two pagination strategies: offset-based and cursor-based. The SDK provides helper functions for both.
Used by endpoints like listing spaces, groups, and space members:
import { autoPageOffset } from '@hellopivot/sdk';
const allSpaces = await autoPageOffset( (offset) => pivot.spaces.getSpacesByOrganizationId(orgId, offset), (response) => ({ items: response.spaces ?? [], nextOffset: response.nextOffset, }));Used by endpoints like listing messages and recordings:
import { autoPageCursor } from '@hellopivot/sdk';
const allMessages = await autoPageCursor( (cursor) => pivot.rooms.getRoomMessages(roomId, { cursorSentAt: cursor?.cursorSentAt, cursorMessageId: cursor?.cursorMessageId, }), (response) => ({ items: response.messages ?? [], hasMore: response.shardHasMore ?? false, nextCursor: { cursorSentAt: response.nextCursorSentAt, cursorMessageId: response.nextCursorMessageId, }, }));Both helpers accept an options parameter to limit the number of items or pages fetched:
// Fetch at most 100 itemsconst spaces = await autoPageOffset(fetchPage, extractPage, { maxItems: 100,});
// Fetch at most 5 pagesconst messages = await autoPageCursor(fetchPage, extractPage, { maxPages: 5,});The SDK provides typed error classes for different failure scenarios:
import Pivot, { PivotAPIError, PivotTimeoutError, PivotAuthenticationError,} from '@hellopivot/sdk';
try { const space = await pivot.spaces.getSpacesByOrganizationId(orgId, 0);} catch (error) { if (error instanceof PivotAPIError) { console.error(`API error ${error.status}: ${error.message}`); console.error('Response body:', error.body); } else if (error instanceof PivotTimeoutError) { console.error('Request timed out'); } else if (error instanceof PivotAuthenticationError) { console.error('Missing or invalid API key'); }}| Error Class | When it’s thrown |
|---|---|
PivotAPIError | API returns a non-2xx status code |
PivotTimeoutError | Request exceeds the configured timeout |
PivotRetryError | All retry attempts exhausted |
PivotAuthenticationError | No API key provided |
const response = await pivot.spaces.getSpacesByOrganizationId(orgId, 0, { status: 'active', sortBy: 'created_at_desc',});
for (const space of response.spaces ?? []) { console.log(`${space.name} (${space.id})`);}// Create a new spaceconst { space } = await pivot.spaces.createSpace(orgId, { name: 'Engineering Team', type: 'SPACE_TYPE_TEAM',});
// Invite members by emailawait pivot.spaces.inviteToSpaceByEmails(orgId, space.id, { roleIds: ['role-id'],});import { autoPageCursor } from '@hellopivot/sdk';
const messages = await autoPageCursor( (cursor) => pivot.rooms.getRoomMessages(roomId, { cursorSentAt: cursor?.cursorSentAt, cursorMessageId: cursor?.cursorMessageId, limit: 50, }), (response) => ({ items: response.messages ?? [], hasMore: response.shardHasMore ?? false, nextCursor: { cursorSentAt: response.nextCursorSentAt, cursorMessageId: response.nextCursorMessageId, }, }), { maxItems: 200 });const { webhook, secret } = await pivot.webhooks.createWebhook(orgId, { name: 'My Webhook', endpointUrl: 'https://example.com/webhook', subscriptions: [ { subjectType: 'WEBHOOK_SUBJECT_TYPE_ROOM', subjectId: 'room-uuid', subscriptionType: 'WEBHOOK_SUBSCRIPTION_TYPE_MESSAGE_SENT', }, ],});
// Store the secret securely — it's only returned onceconsole.log('Webhook secret:', secret);The SDK provides utilities for verifying webhook signatures and parsing webhook payloads with full type safety. See the Webhooks documentation for details on setting up webhooks.
Every webhook delivery includes an X-Pivot-Signature header containing an HMAC-SHA256 signature. Always verify this before processing the event:
import { verifyWebhookSignature } from '@hellopivot/sdk';
app.post('/webhook', (req, res) => { const isValid = verifyWebhookSignature( req.body, // raw body string req.headers['x-pivot-signature'], process.env.WEBHOOK_SECRET );
if (!isValid) { return res.status(401).send('Invalid signature'); }
res.status(200).send('OK');});Use parseWebhookEvent to verify the signature and parse the payload into a typed event in one step:
import { parseWebhookEvent } from '@hellopivot/sdk';
app.post('/webhook', (req, res) => { try { const event = parseWebhookEvent( req.body, req.headers['x-pivot-signature'], process.env.WEBHOOK_SECRET );
switch (event.eventType) { case 'message_sent': console.log('New message:', event.data.messageId); break; case 'message_deleted': console.log('Deleted:', event.data.messageId); break; case 'message_edited': console.log('Edited:', event.data.messageId); break; case 'room_recording_published': console.log('Recording ready:', event.data.recordingId); break; case 'room_recording_transcript_published': console.log('Transcript ready:', event.data.recordingId); break; case 'block_response_sent': console.log('Block response sent:', event.data.blockResponseId); break; }
res.status(200).send('OK'); } catch (err) { res.status(401).send('Invalid webhook'); }});The SDK provides a discriminated union type WebhookEvent that narrows based on eventType:
| Event Type | Data Fields |
|---|---|
message_sent | messageId, roomId, userId, status, createdAt |
message_deleted | messageId, roomId, userId, status, deletedAt |
message_edited | messageId, roomId, userId, status, updatedAt |
room_recording_published | recordingId, roomId, createdAt |
room_recording_transcript_published | recordingId, roomId, createdAt |
block_response_sent | blockResponseId, blockId, userId, status, createdAt, richContent, attachments |
Every event also includes organizationId, spaceId, subject (type + id), and timestamp.
All request and response types are exported from the package:
import type { Space, Block, Room, Message, CreateSpaceContent, GetSpacesByOrganizationIdResponse, WebhookEvent, MessageSentEvent,} from '@hellopivot/sdk';The SDK is generated from the Pivot OpenAPI specification, so types always match the current API.
Was this guide helpful?