Contacts
TypeScript SDK
Use @misar/contacts to call the Contacts API from any Misar product server.
Installation
The SDK is a workspace package — no npm install needed inside the monorepo.
{
"dependencies": {
"@misar/contacts": "workspace:*"
}
}Never import @misar/contacts inside non-misar-io repos (MisarMail, MisarReach, etc.). Use the REST API directly from those products — the service key and base URL are sufficient.
Setup
import { createContactsClient } from "@misar/contacts";
const contacts = createContactsClient({
baseUrl: "https://api.misar.io/io/contacts",
serviceKey: process.env.AUDIENCE_SERVICE_KEY,
});Types
import type { Contact, ContactInput, SocialProfiles, ContactStatus } from "@misar/contacts";
// Social network handles / URLs
interface SocialProfiles {
linkedin?: string | null;
twitter?: string | null;
instagram?: string | null;
telegram?: string | null;
whatsapp?: string | null;
reddit?: string | null;
youtube?: string | null;
}
// Input shape for create / upsert / update
interface ContactInput {
email: string; // required for create
firstName?: string | null; // max 100 chars
lastName?: string | null; // max 100 chars
phone?: string | null; // max 50 chars
company?: string | null; // max 150 chars
jobTitle?: string | null; // max 100 chars
socialProfiles?: SocialProfiles;
status?: ContactStatus; // "subscribed" | "unsubscribed" | "bounced" | "complained"
customFields?: Record<string, unknown>; // max 20 keys, 8 KB
source?: string | null; // max 100 chars
workspaceId?: string | null;
}Contact methods
// List (paginated)
const page = await contacts.listContacts(userId, { limit: 50, status: "subscribed" });
// Get single
const contact = await contacts.getContact(userId, contactId);
// Create with full profile
const created = await contacts.createContact(userId, {
email: "[email protected]",
firstName: "Alice",
lastName: "Brown",
phone: "+1 555 000 9876",
company: "Acme Corp",
jobTitle: "Engineer",
socialProfiles: {
linkedin: "https://linkedin.com/in/alicebrown",
twitter: "@alice",
},
});
// Upsert on email collision (updates existing contact if email matches)
const upserted = await contacts.upsertContact(userId, {
email: "[email protected]",
company: "New Company",
socialProfiles: { telegram: "@alice_tg" },
});
// Update specific fields
const updated = await contacts.updateContact(userId, contactId, {
phone: "+44 20 7946 0958",
jobTitle: "Senior Engineer",
socialProfiles: { linkedin: "https://linkedin.com/in/alice-new" },
status: "unsubscribed",
});
// Delete
await contacts.deleteContact(userId, contactId);
// Bulk action
const result = await contacts.bulkContacts(userId, "unsubscribe", [id1, id2]);
// Import (up to 5000 rows — supports profile fields)
const imported = await contacts.importContacts(userId, [
{ email: "[email protected]", company: "ACME", jobTitle: "Dev" },
{ email: "[email protected]", source: "csv", phone: "+1 555 111 2222" },
]);
// Stats
const stats = await contacts.getContactStats(userId);
// → { total: 1240, subscribed: 980, unsubscribed: 210, bounced: 38, complained: 12 }Segment methods
const segments = await contacts.listSegments(userId);
const segment = await contacts.getSegment(userId, segmentId);
const count = await contacts.refreshSegment(userId, segmentId);
const preview = await contacts.previewSegment(userId, {
operator: "AND",
rules: [{ field: "status", operator: "eq", value: "subscribed" }],
});Lead scoring
const rules = await contacts.listScoringRules(userId);Error handling
import { ContactsError } from "@misar/contacts";
try {
await contacts.getContact(userId, "bad-id");
} catch (err) {
if (err instanceof ContactsError) {
console.error(err.status, err.message);
// err.status === 429 → rate limited; check Retry-After header
}
}Rate limits
Write endpoints (create, upsert, update, delete, bulk, import) are limited to 200 requests per user per 60 seconds. On breach, the API returns 429 Too Many Requests with a Retry-After: <seconds> header. ContactsError.status will be 429.