Skip to main content

@gravity-rail/sdk

TypeScript SDK for Gravity Rail — the AI-native operating system for organizations that communicate with people at scale.

Build AI assistants that handle voice calls, SMS, web chat, email, Discord, and Slack — all backed by configurable workflows, structured data collection, and real-time streaming. One SDK, every channel.

Installation​

npm install @gravity-rail/sdk
# or
yarn add @gravity-rail/sdk
# or
pnpm add @gravity-rail/sdk

Zod schemas are available as an optional peer dependency:

npm install zod  # optional, for runtime validation

Quick Start​

Create an account or organization API key with the scopes your integration needs. See Authentication for key reach, OAuth, and step-up requirements. Keep credentials in your server's environment or secrets manager; do not embed an API key in a public browser bundle.

import { GravityRailClient } from '@gravity-rail/sdk';

const client = new GravityRailClient(
process.env.GRAVITY_RAIL_API_KEY,
'https://api.gravityrail.com'
);

const workspaces = await client.getWorkspaces();
if (workspaces.length === 0) throw new Error('No workspace available');
const workspaceId = workspaces[0].id;
const workflows = await client.getWorkflows(workspaceId);
const chats = await client.getChats(workspaceId);

Use Creating Members to select the appropriate creation/import endpoint and role. A direct SDK call uses the request type's wire names:

const roleId = Number(process.env.GRAVITY_RAIL_MEMBER_ROLE_ID);
if (!Number.isInteger(roleId) || roleId <= 0) {
throw new Error('Set GRAVITY_RAIL_MEMBER_ROLE_ID to an authorized role ID');
}
const member = await client.createWorkspaceMember(workspaceId, {
name: 'Integration Test',
email: 'integration-test@example.com',
memberRoleId: roleId, // an authorized role selected for the new Member
});

Handle API errors​

import { ApiError } from '@gravity-rail/sdk';

try {
await client.getWorkflows(workspaceId);
} catch (error) {
if (error instanceof ApiError && error.statusCode === 403) {
// Check the acting identity and scopes for this workspace.
}
throw error;
}

See Error Handling for status codes and safe reporting. The SDK Reference and GravityRailClient reference document methods and types. This package README also supplies the canonical SDK usage guide.

Authentication​

API Key​

Create an account or organization API key with the required permissions:

const client = new GravityRailClient(
'your-api-key',
'https://api.gravityrail.com'
);

Select the required scopes and expiry using the granular scope system. Key reach depends on the issuing account or organization; a key created in workspace settings is not inherently limited to that workspace.

OAuth2 with PKCE​

For browser-based applications:

const client = new GravityRailClient(undefined, 'https://api.gravityrail.com');

// Handle step-up authentication when the API requires it
client.setEnhancedAuthHandler(async (authInfo) => {
// Redirect to login or show auth modal
});

Core Concepts​

Every Gravity Rail workspace is a self-contained environment with isolated data, workflows, and configuration. The SDK organizes its 500+ methods by domain:

DomainWhat it covers
WorkflowsMulti-step conversational processes with branching logic, task assignment, and templates
AssistantsAI personas with configurable models, voices, and a two-tier supervisor system
AgentsAutonomous AI workspace members with their own identity, config, and multi-channel presence
ChatsConversations across all channels — labels, filters, summaries, message history, and export
MembersContacts and team — roles, labels, filters, custom fields, import/export, anonymous resolution
Data TypesSchema-driven forms (14 field types) with computed fields and conversational collection
EventsTrigger-based automation with CEL expressions
CalendarsScheduling, availability, event types, Google Calendar sync, and iCal feeds
FilesFolder hierarchy with semantic search, role-based sharing, and public access
SitesCustomer-facing portals with custom domains, page builder, and web crawling
CommunicationsPhone numbers, SMS, email inboxes, and notification rules
Org DomainsRegister org-owned domains, verify ownership, verify email MX/DKIM/SPF, and verify site CNAMEs
ToolkitsCustom tools, MCP server integrations, and AI model configuration
Operator GroupsLive human routing with presence tracking and configurable strategies
QualificationsSkills evaluation with expression-based and rubric-based scoring
BillingAPI keys, subscriptions, and usage reports (AI, voice, SMS, storage)
IntegrationsDiscord, Slack, Monday.com, FHIR, and OAuth app connections

Usage Examples​

Workflows & Assistants​

// Create a multi-step workflow
const workflow = await client.createWorkflow(workspaceId, {
name: 'Customer Onboarding',
description: 'Guide new customers through setup',
});

// Create an AI assistant
const assistant = await client.createAssistant(workspaceId, {
name: 'Onboarding Guide',
model: 'claude-sonnet-4-20250514',
bio: 'A friendly assistant that helps new customers get started.',
});

// Add a supervisor for quality assurance
const supervisor = await client.createSupervisor(workspaceId, {
name: 'QA Reviewer',
model: 'claude-sonnet-4-20250514',
instructions: 'Review assistant responses for accuracy and tone.',
});

// Build from templates
const templates = await client.getWorkflowTemplates(workspaceId);
await client.createWorkflowFromTemplate(workspaceId, {
template_id: templates[0].id,
});

Chat Operations​

// Get conversations across all channels
const chats = await client.getChats(workspaceId);

// Full message history with tool calls
const messages = await client.getChatMessages(workspaceId, chatId);
const toolCalls = await client.getChatToolCallMessages(workspaceId, chatId);

// Trigger an AI response
await client.sendAssistantMessage(workspaceId, chatId);

// Direct messaging between members
const dm = await client.findOrCreateDMChat(workspaceId, { member_id: targetMemberId });
await client.sendChatMessage(workspaceId, dm.id, { content: 'Hey!' });

// Export for analysis
const exported = await client.exportChat(workspaceId, chatId);

// Organize with labels and filters
await client.createChatLabel(workspaceId, { name: 'VIP', color: '#FFD700' });
await client.createChatFilter(workspaceId, { name: 'Unresolved', conditions: { needs_response: true } });

Structured Data Collection​

// Define a schema — AI assistants collect this conversationally
const dataType = await client.createDataType(workspaceId, {
name: 'Contact Form',
slug: 'contact-form',
is_collection: true,
fields: [
{ name: 'Full Name', field_type: 'text', required: true },
{ name: 'Email', field_type: 'email', required: true },
{ name: 'Priority', field_type: 'dropdown', options: ['Low', 'Medium', 'High'] },
{ name: 'Score', field_type: 'formula', formula: 'Priority == "High" ? 100 : 50' },
],
});

// Or create records programmatically
const record = await client.createDataRecord(workspaceId, dataType.id, {
field_values: { 'Full Name': 'Alice Chen', 'Email': 'alice@example.com', 'Priority': 'High' },
});

// Query and upsert
const records = await client.getDataRecords(workspaceId, dataType.id);
await client.upsertDataRecord(workspaceId, dataType.id, {
match_field: 'Email',
field_values: { 'Email': 'alice@example.com', 'Priority': 'Medium' },
});

Communication Channels​

// Email — manage inboxes and threads
const inboxes = await client.getInboxes(workspaceId);
const threads = await client.getInboxThreads(workspaceId, inboxes[0].id);
const emails = await client.getThreadEmails(workspaceId, inboxes[0].id, threadId);

// Notifications
await client.createNotificationRule(workspaceId, {
name: 'New chat alert',
event_type: 'chat.created',
channel: 'email',
});

Organization Domains​

// Register an org-owned domain and publish the returned TXT proof.
const orgDomain = await client.registerOrgDomain(orgId, 'example.com');
console.log(orgDomain.ownershipDnsRecord);

// Verify domain ownership.
await client.verifyOrgDomainOwnership(orgId, orgDomain.uuid);

// Enable and verify email DNS, including MX.
const emailConfig = await client.enableOrgDomainEmail(orgId, orgDomain.uuid);
console.log(emailConfig.dnsRecords);
await client.verifyOrgDomainEmail(orgId, orgDomain.uuid);

// Enable and verify custom site routing through CNAME.
const siteConfig = await client.enableOrgDomainSite(orgId, orgDomain.uuid);
console.log(siteConfig.dnsRecords);
await client.verifyOrgDomainSite(orgId, orgDomain.uuid);

// Or host many sites under one domain: the customer publishes a single
// `*.example.com` CNAME, and each host beneath it needs no DNS of its own.
await client.enableOrgDomainSite(orgId, orgDomain.uuid, { childHosting: true });
await client.verifyOrgDomainSite(orgId, orgDomain.uuid);

Toolkits & MCP Servers​

// Connect an MCP server for custom AI tool capabilities
const server = await client.createMcpServer(workspaceId, {
name: 'Internal Tools',
url: 'https://tools.example.com/mcp',
});
await client.testMcpServerConnection(workspaceId, server.id);

// Discover tools, resources, and prompts
const tools = await client.getMcpServerTools(workspaceId, server.id);
const resources = await client.getMcpServerResources(workspaceId, server.id);

// Or build custom tools directly
await client.createCustomTool(workspaceId, toolkitId, {
name: 'lookup_order',
description: 'Look up an order by ID',
input_schema: {
type: 'object',
properties: { order_id: { type: 'string' } },
required: ['order_id'],
},
});

Calendar & Scheduling​

const calendar = await client.createCalendar(workspaceId, { name: 'Appointments' });

// Check availability and book
const slots = await client.getAvailableSlots(workspaceId, calendar.id, {
start_date: '2025-03-01',
end_date: '2025-03-07',
duration_minutes: 30,
});

await client.createCalendarEvent(workspaceId, calendar.id, {
title: 'Consultation',
start_time: slots[0].start,
end_time: slots[0].end,
attendees: [{ member_id: memberId }],
});

// Sync with Google Calendar
await client.linkCalendarToGoogle(workspaceId, calendar.id, {
google_calendar_id: 'primary',
});

Event Automation​

// Trigger-based automation with CEL conditions
await client.createEventRule(workspaceId, {
name: 'VIP Follow-up',
trigger: 'task.exited',
condition: '"vip" in member.labels',
actions: [{
type: 'send_ai_message',
delay_minutes: 60,
config: { message: 'Thank you for your time today.' },
}],
});

// Scheduled events (CRON)
const event = await client.createEvent(workspaceId, {
name: 'Daily Check-in',
schedule: '0 9 * * *',
target_filter: { labels: ['active'] },
});

// Trigger manually
await client.runEvent(workspaceId, event.id);

Operator Groups & Live Handoff​

// Route conversations to live human operators
const group = await client.createOperatorGroup(workspaceId, {
name: 'Support Team',
routing_strategy: 'round_robin',
timeout_seconds: 30, // falls back to AI if no one accepts
});

await client.addGroupMember(workspaceId, group.id, { member_id: agentMemberId });
const operators = await client.getLiveOperators(workspaceId);

Sites & Portals​

// Build customer-facing portals with AI chat built in
const site = await client.createSite(workspaceId, {
name: 'Help Center',
slug: 'help',
domain: 'help.example.com',
});

await client.createPage(workspaceId, site.id, {
title: 'Getting Started',
slug: 'getting-started',
content: '# Welcome\nHere is how to get started...',
});

// Import existing documentation automatically
await client.crawlSite(workspaceId, site.id, { url: 'https://docs.example.com' });

Workspace Management​

// Workspaces this credential can reach
const workspaces = await client.getWorkspaces();

// Companion-device picker (Lantern): compact rows for only the
// workspaces a paired device could enter — no device credential needed
const deviceWorkspaces = await client.getDeviceAdmissibleWorkspaces();

// Export/import full workspace configuration
const config = await client.exportWorkspace(workspaceId);
const preview = await client.previewWorkspaceImport(targetId, config);
await client.importWorkspace(targetId, config);

// White-label: provision client workspaces
const clientWs = await client.createClientWorkspace(workspaceId, {
name: 'Acme Corp',
product_id: productId,
});
await client.provisionClientWorkspace(workspaceId, clientWs.id);

Helpers (@gravity-rail/sdk/helpers)​

Small compound operations built on the client methods, for the common "register → populate a form → enroll → verify webhooks" integration flow:

import {
// member resolution + enrollment
resolveMemberId, // { memberId | phone | externalId } → member id
findOrCreateMemberLabels, // idempotent label lookup/create → ids
upsertDataRecordForMember,// idempotent member-scoped record write
enrollMemberInJourney, // resolve member → tag → enroll (409 = already enrolled)
// outbound calling
findMemberByPhone,
resolveOutboundPhoneNumberId,
resolveOutboundWorkflowId,
// webhook signatures (isomorphic HMAC-SHA256; verify the RAW body)
verifyWebhookSignature,
signWebhookPayload,
WEBHOOK_SIGNATURE_HEADER,
} from '@gravity-rail/sdk/helpers';

// Enroll into a journey by any identifier, applying cohort labels:
await enrollMemberInJourney(client, workspaceUuid, {
externalId: 'patient-123',
journeyId,
labels: ['pilot-cohort'],
});

// Verify an inbound webhook in your POST handler:
const result = await verifyWebhookSignature(
rawBody,
request.headers.get(WEBHOOK_SIGNATURE_HEADER),
signingSecret,
);
if (!result.ok) return new Response(result.reason, { status: 401 });

Type System​

Full TypeScript types for every API entity:

import type {
Workspace, Member, Chat, Task, WorkflowRevision, Assistant,
DataType, DataRecord, EventRule, Calendar, Site,
PhoneNumber, Inbox, OperatorGroup, Qualification,
Subscription, ApiKey, Agent, MemberRole,
} from '@gravity-rail/sdk';

Zod Schemas​

Runtime validation schemas via a separate import (requires zod):

import { taskSchema, memberSchema, workflowSchema } from '@gravity-rail/sdk/schemas';

const task = taskSchema.parse(apiResponse);

const result = memberSchema.safeParse(formData);
if (!result.success) {
console.error(result.error.flatten());
}

Scopes & Permissions​

The SDK includes the complete scope system for building OAuth apps and managing API key permissions:

import { SCOPES, ScopeCategory, ScopeManager, SCOPE_PRESETS } from '@gravity-rail/sdk';

// Browse available scopes by category
const chatScopes = Object.values(SCOPES).filter(
(s) => s.category === ScopeCategory.CHATS
);

22 scope categories covering every domain: workspace, members, chats, DMs, assistants, workflows, assignments, automations, data types, records, files, sites, agents, calendars, inboxes, phones, webhooks, apps, labels, analytics, operator, and org.

Error Handling​

import { GravityRailClient, ApiError } from '@gravity-rail/sdk';

try {
const task = await client.getTask(workspaceId, taskId);
} catch (error) {
if (error instanceof ApiError) {
console.error(`${error.status}: ${error.message}`);
// 403 → check API key scopes
// 404 → verify workspace and entity IDs
// 429 → rate limited, back off
}
}

Security and PHI Use​

The SDK provides scoped API access to workspace-isolated resources. Those technical properties do not by themselves authorize PHI use or establish HIPAA compliance. Before sending PHI, obtain a signed BAA and confirmation from Gravity Rail that the intended organization, workspaces, and services are configured for that use.

License​

MIT

Changing interaction mode within a Chat​

A Chat retains its transport while an interaction session selects text, push-to-talk, or realtime bidirectional voice. Keep one controller per client session and Chat; independent devices should use separate controllers.

import { ChatInteractionController } from '@gravity-rail/sdk';

const interaction = new ChatInteractionController(client, workspaceId, chatId);
const textSession = await interaction.ensure('text');
const recordedVoiceSession = await interaction.ensure('push_to_talk');

Pass the selected session's id as interactionSessionId and a new UUID as interactionTurnId on an AG-UI user turn. Send both fields together. The server checks the Chat and authenticated actor and derives the mode from the stored session. The controller serializes transitions and reuses request IDs after uncertain network failures. Session metadata does not itself open a microphone or establish a realtime connection.

New browser Chat creation accepts interactionMode: 'realtime' for an initial voice greeting while retaining channel: 'web-chat'. browserChannel remains a deprecated create-only alias. Omit both provenance fields for legacy AG-UI clients; historical Messages are not assigned a mode by guessing from channel or content. Message responses expose nullable interactionSessionId, interactionMode, and interactionTurnId so audio and transcript components can follow the same logical turn.

Converting a Start Task opening message​

The workflow client exposes the conservative, draft-only opening conversion. Inspect eligibility first, then pass the reported rule id back as an optimistic concurrency guard:

const report = await client.getWorkflowOpeningTurnConversionReport(workspaceId, workflowId);
if (report.canConvert && report.convertibleRuleId !== null) {
await client.convertWorkflowStartTaskOpeningMessage(
workspaceId,
workflowId,
report.convertibleRuleId
);
}

Conversion refuses multiple emitters, conditional or templated messages, direction variants, conflicting Greeting defaults, and other shapes that cannot be moved without changing behavior.