@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:
| Domain | What it covers |
|---|---|
| Workflows | Multi-step conversational processes with branching logic, task assignment, and templates |
| Assistants | AI personas with configurable models, voices, and a two-tier supervisor system |
| Agents | Autonomous AI workspace members with their own identity, config, and multi-channel presence |
| Chats | Conversations across all channels — labels, filters, summaries, message history, and export |
| Members | Contacts and team — roles, labels, filters, custom fields, import/export, anonymous resolution |
| Data Types | Schema-driven forms (14 field types) with computed fields and conversational collection |
| Events | Trigger-based automation with CEL expressions |
| Calendars | Scheduling, availability, event types, Google Calendar sync, and iCal feeds |
| Files | Folder hierarchy with semantic search, role-based sharing, and public access |
| Sites | Customer-facing portals with custom domains, page builder, and web crawling |
| Communications | Phone numbers, SMS, email inboxes, and notification rules |
| Org Domains | Register org-owned domains, verify ownership, verify email MX/DKIM/SPF, and verify site CNAMEs |
| Toolkits | Custom tools, MCP server integrations, and AI model configuration |
| Operator Groups | Live human routing with presence tracking and configurable strategies |
| Qualifications | Skills evaluation with expression-based and rubric-based scoring |
| Billing | API keys, subscriptions, and usage reports (AI, voice, SMS, storage) |
| Integrations | Discord, 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.
Related
@gravity-rail/cli— Command-line interface for interactive workspace management- Developer Docs — Full API reference and guides
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.