Skip to content

Node.js Server SDK

TypeScript-first, zero-dependency Node.js SDK for the Synapse API. Published as @pyrx/synapse on npm.

Requires Node.js 18+ (uses native fetch).


Installation

bash
npm install @pyrx/synapse
bash
pnpm add @pyrx/synapse
bash
yarn add @pyrx/synapse

Quick Start

typescript
import { Synapse } from '@pyrx/synapse';
 
const synapse = new Synapse({
apiKey: 'psk_live_your_api_key',
workspaceId: 'your_workspace_id',
});
 
// Track an event
await synapse.track({
externalId: 'user_123',
eventName: 'purchase_completed',
attributes: {
orderId: 'ord_456',
amount: 99.99,
currency: 'USD',
},
});
 
// Identify a contact
await synapse.identify({
externalId: 'user_123',
email: '[email protected]',
firstName: 'Jane',
lastName: 'Doe',
properties: { plan: 'pro', signupSource: 'website' },
tags: ['paying', 'beta-tester'],
});
 
// Send a transactional email
await synapse.send({
templateSlug: 'order-confirmation',
to: {
userId: 'user_123',
email: '[email protected]',
firstName: 'Jane',
},
attributes: {
orderId: 'ord_456',
items: [{ name: 'Widget', price: 99.99 }],
},
});
Tip

Get your API key and workspace ID from the dashboard at Settings > API Keys.


Configuration

typescript
const synapse = new Synapse({
apiKey: 'psk_live_xxx', // Required. API key from workspace settings.
workspaceId: 'ws_xxx', // Required. Your workspace ID.
baseUrl: 'https://...', // Default: https://synapse-api.pyrx.tech
timeout: 30000, // Default: 30000ms
maxRetries: 3, // Default: 3. Set to 0 to disable retries.
debug: false, // Default: false. Logs request URLs to console.
});
ParameterTypeDefaultDescription
apiKeystringrequiredYour Synapse API key (psk_live_* or psk_test_*)
workspaceIdstringrequiredYour workspace identifier
baseUrlstringhttps://synapse-api.pyrx.techAPI base URL
timeoutnumber30000Request timeout in milliseconds
maxRetriesnumber3Retry count for 429/5xx errors. Set to 0 to disable.
debugbooleanfalseLog every request URL to console

Environment detection: The SDK detects test or live from your API key prefix (psk_test_* vs psk_live_*), available via synapse.environment.

Retry behavior: The SDK automatically retries on 429, 500, 502, 503, and 504 with exponential backoff and jitter. Client errors (400, 401, 403, 404, 422) are never retried.


Track Events

Single Event

typescript
const result = await synapse.track({
externalId: 'user_123',
eventName: 'purchase_completed',
attributes: {
orderId: 'ord_456',
amount: 99.99,
currency: 'USD',
},
contact: {
email: '[email protected]',
first_name: 'Jane',
},
idempotencyKey: 'purchase_ord_456', // optional, prevents duplicate processing
occurredAt: '2026-04-29T10:30:00Z', // optional, defaults to now
});
 
console.log(result.eventId); // "evt_8f14e45f-..."
console.log(result.status); // "accepted"
ParameterTypeRequiredDescription
externalIdstringYesYour unique user identifier
eventNamestringYesEvent name (e.g., purchase_completed)
attributesobjectNoArbitrary key-value event data
contactobjectNoContact fields to upsert alongside the event
idempotencyKeystringNoPrevents duplicate processing (7-day TTL)
occurredAtstringNoISO 8601 timestamp. Defaults to server time.

Batch Events

Track up to 50 events in a single request.

typescript
const result = await synapse.trackBatch({
events: [
{ externalId: 'user_1', eventName: 'page_view', attributes: { page: '/pricing' } },
{ externalId: 'user_2', eventName: 'page_view', attributes: { page: '/docs' } },
{ externalId: 'user_1', eventName: 'button_clicked', attributes: { button: 'upgrade' } },
],
});
 
console.log(result.accepted); // 3
console.log(result.rejected); // 0

Identify Contacts

Single Contact

Create or update (upsert) a contact by externalId.

typescript
const contact = await synapse.identify({
externalId: 'user_123',
email: '[email protected]',
firstName: 'Jane',
lastName: 'Doe',
phone: '+1234567890',
timezone: 'America/New_York',
properties: { plan: 'pro', signupSource: 'website' },
tags: ['paying', 'beta-tester'],
});
 
console.log(contact.id); // UUID
console.log(contact.externalId); // "user_123"
console.log(contact.email); // "[email protected]"

Batch Identify

Upsert up to 1,000 contacts in a single request.

typescript
const result = await synapse.identifyBatch({
contacts: [
{ externalId: 'user_1', email: '[email protected]', firstName: 'Alice' },
{ externalId: 'user_2', email: '[email protected]', firstName: 'Bob' },
],
onConflict: 'merge', // "merge" | "skip" | "replace"
});
 
console.log(result.total); // 2
console.log(result.created); // 1
console.log(result.updated); // 1

Send Transactional Email

Send a one-off email using an NLT template, without a flow.

typescript
const result = await synapse.send({
templateSlug: 'otp-verification',
to: {
userId: 'user_123',
email: '[email protected]',
firstName: 'Jane',
},
attributes: {
otpCode: '847293',
expiryMinutes: 10,
},
idempotencyKey: `otp_user_123_${Date.now()}`,
});
 
console.log(result.status); // "sent" or "suppressed"
console.log(result.emailLogId); // "el_8f14e45f-..."
Note

Requires a data-scoped API key (or higher). The template must exist in your workspace.


Contact Management

The synapse.contacts sub-client provides full CRUD operations. Requires a management or full scoped API key.

List Contacts

typescript
const { data, meta } = await synapse.contacts.list({
search: 'jane',
page: 1,
perPage: 25,
sortBy: 'created_at',
sortOrder: 'desc',
});
 
console.log(meta.total); // 142
console.log(meta.totalPages); // 6
data.forEach((c) => console.log(c.email, c.firstName));

Get a Contact

typescript
const contact = await synapse.contacts.get('contact_uuid');

Update a Contact

typescript
await synapse.contacts.update('user_123', {
email: '[email protected]',
addTags: ['vip'],
removeTags: ['trial'],
});

Delete a Contact

typescript
await synapse.contacts.delete('user_123');

Template Management

The synapse.templates sub-client manages email templates. Requires a management or full scoped API key.

List Templates

typescript
const templates = await synapse.templates.list();

Get a Template

typescript
const template = await synapse.templates.get('welcome-email');

Create a Template

typescript
await synapse.templates.create({
name: 'Welcome Email',
slug: 'welcome-email',
subject: 'Welcome, [first name of contact]!',
bodyHtml: '<h1>Welcome!</h1><p>Thanks for joining.</p>',
senderName: 'PYRX Team',
fromEmail: '[email protected]',
});

Update a Template

typescript
await synapse.templates.update('welcome-email', {
subject: 'Welcome aboard, [first name of contact]!',
});

Preview with Sample Data

typescript
const preview = await synapse.templates.preview('welcome-email', {
contact: { firstName: 'Jane', email: '[email protected]' },
triggerEvent: { orderId: 'ord_123' },
});
 
console.log(preview.subject); // Rendered subject
console.log(preview.html); // Rendered HTML
console.log(preview.suppressed); // false
console.log(preview.suppressedReason); // null

Delete a Template

typescript
await synapse.templates.delete('old-template');

Error Handling

The SDK provides typed error classes for every failure mode.

typescript
import {
SynapseError,
SynapseAuthError,
SynapseRateLimitError,
SynapsePlanLimitError,
SynapseValidationError,
} from '@pyrx/synapse';
 
try {
await synapse.track({ externalId: 'u1', eventName: 'test' });
} catch (err) {
if (err instanceof SynapsePlanLimitError) {
console.log(`Plan limit: ${err.limitType} (${err.current}/${err.maximum})`);
console.log(`Current plan: ${err.plan}`);
} else if (err instanceof SynapseRateLimitError) {
console.log(`Rate limited. Retry after ${err.retryAfter}s`);
} else if (err instanceof SynapseValidationError) {
err.errors.forEach((e) => console.log(`${e.field}: ${e.message}`));
} else if (err instanceof SynapseAuthError) {
console.log('Authentication failed:', err.message);
} else if (err instanceof SynapseError) {
console.log(`API error ${err.status}: ${err.message}`);
}
}

Error Types

Error ClassHTTP StatusPropertiesWhen
SynapseErrorAnystatus, message, code, requestIdBase class for all API errors
SynapseAuthError401, 403messageInvalid or expired API key, scope mismatch
SynapseValidationError422errors[] with field + messageRequest body validation failed
SynapseRateLimitError429retryAfter (seconds)Rate limit exceeded (auto-retried)
SynapsePlanLimitError403limitType, current, maximum, planPlan limit reached

Environment Variables

For production deployments, load credentials from environment variables.

typescript
const synapse = new Synapse({
apiKey: process.env.SYNAPSE_API_KEY!,
workspaceId: process.env.SYNAPSE_WORKSPACE_ID!,
});
bash
export SYNAPSE_API_KEY=psk_live_a1b2c3d4e5f67890abcdef1234567890
export SYNAPSE_WORKSPACE_ID=your_workspace_id

Full Method Reference

MethodDescriptionRequired Scope
synapse.track(params)Track a single eventdata
synapse.trackBatch(params)Track up to 50 eventsdata
synapse.identify(params)Upsert a single contactdata
synapse.identifyBatch(params)Upsert up to 1,000 contactsdata
synapse.send(params)Send a transactional emaildata
synapse.contacts.list(params)List contacts with paginationmanagement
synapse.contacts.get(id)Get a single contactmanagement
synapse.contacts.update(id, params)Update a contactmanagement
synapse.contacts.delete(id)Delete a contactmanagement
synapse.templates.list()List all templatesmanagement
synapse.templates.get(slug)Get a template by slugmanagement
synapse.templates.create(params)Create a templatemanagement
synapse.templates.update(slug, params)Update a templatemanagement
synapse.templates.preview(slug, data)Preview rendered templatemanagement
synapse.templates.delete(slug)Delete a templatemanagement

Framework Examples

Express.js

typescript
import express from 'express';
import { Synapse } from '@pyrx/synapse';
 
const app = express();
const synapse = new Synapse({
apiKey: process.env.SYNAPSE_API_KEY!,
workspaceId: process.env.SYNAPSE_WORKSPACE_ID!,
});
 
app.post('/signup', async (req, res) => {
const { email, name, plan } = req.body;
 
// Identify the new user
await synapse.identify({
externalId: req.user.id,
email,
firstName: name.split(' ')[0],
properties: { plan },
tags: ['new-signup'],
});
 
// Track the signup event (triggers flows)
await synapse.track({
externalId: req.user.id,
eventName: 'user_signed_up',
attributes: { plan, source: 'web' },
});
 
res.json({ success: true });
});

Next.js API Route

typescript
// app/api/track/route.ts
import { Synapse } from '@pyrx/synapse';
import { NextRequest, NextResponse } from 'next/server';
 
const synapse = new Synapse({
apiKey: process.env.SYNAPSE_API_KEY!,
workspaceId: process.env.SYNAPSE_WORKSPACE_ID!,
});
 
export async function POST(req: NextRequest) {
const { userId, event, properties } = await req.json();
 
await synapse.track({
externalId: userId,
eventName: event,
attributes: properties,
});
 
return NextResponse.json({ status: 'accepted' });
}