Official TypeScript/JavaScript SDK for integrating with the Contio Partner API.
📚 Full Documentation: docs.contio.ai/partner-api
npm install @contio/partner-sdk
import { ContioPartnerSDK } from '@contio/partner-sdk';
const { oauth, user } = ContioPartnerSDK.forUser({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'https://your-app.com/callback'
});
// Generate authorization URL
const authUrl = oauth.getAuthorizationUrl('csrf-state');
// After user authorizes, exchange code for tokens
const tokens = await oauth.exchangeCodeForToken(authorizationCode);
// Access user data
const meetings = await user.getMeetings();
const actionItems = await user.getActionItems();
Provision new users into existing shared workspaces instead of creating individual workspaces:
import { ContioPartnerSDK } from '@contio/partner-sdk';
const { oauth } = ContioPartnerSDK.forUser({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
redirectUri: 'https://your-app.com/callback'
});
// Provision a new user into a shared workspace
const result = await oauth.initiatePartnerAuth(
'newuser@example.com',
'New User',
{ workspace_id: 'existing-workspace-uuid' }
);
// Provision as a workspace admin
const adminResult = await oauth.initiatePartnerAuth(
'admin@example.com',
'Admin User',
{ workspace_id: 'existing-workspace-uuid', is_admin: true }
);
// Check a user's workspace after provisioning
const profile = await user.getUserProfile();
console.log(`Workspace: ${profile.workspace_name} (${profile.workspace_role})`);
import { ContioPartnerSDK } from '@contio/partner-sdk';
const { admin } = ContioPartnerSDK.forAdmin({
apiKey: 'your-api-key'
});
const automations = await admin.getAutomations();
const stats = await admin.getStats();
const disabledApp = await admin.updateWebhookStatus({
enabled: false
});
const filteredApp = await admin.setWebhookFilter({
type: 'include',
events: ['meeting.created', 'meeting.completed']
});
const unfilteredApp = await admin.removeWebhookFilter();
console.log(disabledApp.webhook_enabled);
console.log(filteredApp.webhook_filter);
console.log(unfilteredApp.webhook_filter); // null
Access user calendar events and retrieve detailed event information:
// List calendar events in a date range
const events = await user.getCalendarEvents({
start: '2026-01-01T00:00:00Z',
end: '2026-01-31T23:59:59Z'
});
// Get detailed information about a specific calendar event
const event = await user.getCalendarEvent('cal-event-123');
console.log(`Event: ${event.title}`);
console.log(`Organizer: ${event.organizer?.email}`);
// Create a meeting from a calendar event
const result = await user.createMeetingFromCalendarEvent('cal-event-123');
console.log(`Created: ${result.created}, Meeting ID: ${result.meeting?.id}`);
console.log(`Applied templates: ${result.meeting?.applied_template_ids?.join(', ') ?? 'none'}`);
Search the authenticated user's meetings with the same full-text index used in the Contio app. Queries match meeting titles and the summary and notes content, so a query like draft email or external send surfaces meetings where those terms were only discussed in the notes.
const results = await user.searchMeetings({
q: 'draft email',
start_time_from: '2026-01-01T00:00:00Z',
start_time_to: '2026-01-31T23:59:59Z',
status: 'completed',
limit: 25,
});
console.log(`Found ${results.total} meetings`);
for (const meeting of results.items) {
console.log(`${meeting.title} — ${meeting.start_time}`);
console.log(`Applied templates: ${meeting.applied_template_ids?.join(', ') ?? 'none'}`);
}
// Automatically paginate through all search results
const allMeetings = await user.searchAllMeetings({ q: 'external send' });
Search supports quoted phrases, -term exclusions, and filters for start time, status, title substrings, participant emails, and whether meetings have action items.
Import call recordings and transcripts through a single multipart upload. The API selects the pipeline from the filename extension: text files (.txt, .md, .srt, .vtt, .pdf) are parsed synchronously (kind: 'text'), while audio files are transcribed asynchronously (kind: 'audio') and return a job to poll.
import { readFile } from 'node:fs/promises';
// Text: synchronous import into an existing meeting
const text = await user.importMeetingTranscript('meeting-uuid', {
file: new Blob([await readFile('call.vtt')], { type: 'text/vtt' }),
filename: 'call.vtt',
});
if (text.kind === 'text') {
console.log(`Imported ${text.segments_count} segments into ${text.meeting_id}`);
}
// Audio: asynchronous import, creating a meeting from metadata
const accepted = await user.importTranscript({
file: new Blob([await readFile('call.m4a')], { type: 'audio/mp4' }),
filename: 'call.m4a',
title: 'Q3 pipeline review',
starts_at: '2026-09-16T14:00:00Z',
participants: ['alice@example.com', 'bob@example.com'],
});
if (accepted.kind === 'audio') {
const job = await user.waitForAudioTranscriptImport(accepted.job_id, {
intervalMs: 10_000,
timeoutMs: 20 * 60_000,
});
if (job.state === 'completed') {
console.log(`Transcript ${job.transcript_id} attached to ${job.meeting_id}`);
} else {
console.error(`Import failed: ${job.failure_code}`);
}
}
importTranscript also accepts an optional meeting_id to bind to an existing meeting (equivalent to importMeetingTranscript) and calendar_event_id to bind an audio import to a calendar event. Audio imports require the Elite plan and consume workspace credits. Poll job status manually with getAudioTranscriptImport(jobId).
The SDK supports all Partner API webhook events with full TypeScript typing:
Meeting Events:
meeting.created - New meeting createdmeeting.updated - Meeting properties changed (title, scheduled time, etc.)meeting.completed - Meeting ended with notes/summary availableCalendar Events:
calendar_event.created - Calendar event synced from external calendarcalendar_event.updated - Calendar event updated in external calendarcalendar_event.deleted - Calendar event cancelled/deletedAgenda Items:
agenda_item.created - Agenda item created in meeting or backlogagenda_item.updated - Agenda item title, status, or properties changedagenda_item.deleted - Agenda item removedAction Items:
action_item.created - New action item createdaction_item.updated - Action item status or details changedaction_item.completed - Action item marked as completedOther Events:
automation.assignment.created - Action item assigned to your automationparticipant.added - Participant(s) added to meetingparticipant.removed - Participant removed from meetinguser.connection.revoked - User revoked connection to your appimport { verifyWebhookSignature, parseWebhook } from '@contio/partner-sdk';
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const signature = req.headers['x-contio-signature'] as string;
try {
const event = parseWebhook(req.body, signature, process.env.WEBHOOK_SECRET!);
// Handle event...
res.status(200).json({ received: true });
} catch (error) {
res.status(401).json({ error: 'Invalid signature' });
}
});
All API errors are thrown as ContioAPIError instances with structured information:
import { ContioAPIError } from '@contio/partner-sdk';
try {
const meeting = await user.getMeeting('invalid-id');
} catch (error) {
if (error instanceof ContioAPIError) {
console.error('Code:', error.code); // e.g., 'not_found', 'unauthorized'
console.error('Message:', error.message); // Human-readable description
console.error('Status:', error.statusCode); // HTTP status code (404, 401, etc.)
console.error('Response:', error.response); // Full error response body
}
}
| Code | Status | Description |
|---|---|---|
unauthorized |
401 | Invalid or expired access token |
forbidden |
403 | Insufficient permissions for this resource |
not_found |
404 | Resource does not exist |
validation_error |
400 | Invalid request parameters |
rate_limited |
429 | Too many requests; retry after backoff |
workspace_not_found |
400 | Specified workspace_id does not exist |
workspace_not_authorized |
403 | Workspace not owned by a user from the same partner |
workspace_conflict |
409 | User already belongs to a different workspace |
internal_error |
500 | Server error; retry with exponential backoff |
The SDK automatically retries transient errors (5xx) with exponential backoff.
For complete documentation, visit:
Contio uses opaque tokens for improved security. Access tokens (cto_at_v1_...) and refresh tokens (cto_rt_v1_...) contain no readable claims.
| Token | Format | Validity |
|---|---|---|
| Authorization code | Opaque | 5 minutes |
| Access token | cto_at_v1_... |
24 hours |
| Refresh token | cto_rt_v1_... |
30 days |
| ID token | JWT | 24 hours |
Important: Do not attempt to decode or parse access/refresh tokens. Use introspection to get token metadata:
const tokenInfo = await oauth.introspectToken(accessToken);
console.log('Scopes:', tokenInfo.scope);
console.log('Expires:', new Date(tokenInfo.exp * 1000));
// Example: Storing tokens securely (server-side)
async function storeTokens(userId: string, tokens: AuthTokens) {
await db.userTokens.upsert({
where: { userId },
data: {
accessToken: encrypt(tokens.accessToken),
refreshToken: encrypt(tokens.refreshToken),
expiresAt: tokens.expiresAt,
},
});
}
parseWebhook() or verifyWebhookSignature()express.raw() to preserve the raw request body for signature verification// Track processed webhook IDs to handle retries
const processedEvents = new Set<string>();
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
const event = parseWebhook(req.body, signature, secret);
if (processedEvents.has(event.event_id)) {
return res.status(200).json({ received: true, duplicate: true });
}
processedEvents.add(event.event_id);
// Process event...
});
git clone https://github.com/Contio-AI/partner-sdk.git
cd partner-sdk
npm install
npm run build
npm test # Run all tests
npm test -- --coverage # Run with coverage report
npm test -- oauth.test.ts # Run specific test file
| Script | Description |
|---|---|
npm run build |
Compile TypeScript to JavaScript |
npm test |
Run the test suite |
npm run lint |
Run ESLint on source files |
npm run docs |
Generate TypeDoc documentation |
npm run gen-types |
Regenerate types from OpenAPI spec |
For support, contact partner-support@contio.ai
This SDK is licensed under the MIT License. See the LICENSE file for details.
Note: This license applies to the SDK source code only. Use of the Contio Partner API is governed by your Partner Agreement with Contio, Inc.