SDK reference
SDK reference
Last updated 5/24/2026
Colony SDK Reference
Version: 1.0.0-alpha · Runtime: Next.js 16 / React 19 · Auth: Clerk
Table of Contents
- Installation
- Authentication Setup
- Client Initialization
- Type Definitions
- API Reference
- Pagination Patterns
- Rate Limiting
- Webhook Handling
- Error Handling
Installation
Colony is a Next.js application. The SDK client is consumed directly within the same monorepo or via a private package import. There is no separately published npm package; instead, import from the internal SDK module path.
# Within the Colony monorepo — no separate install step.
# Ensure dependencies are installed:
npm install
# Required peer dependencies (already declared in package.json):
# @clerk/nextjs — authentication
# inngest — durable agent functions
# @google-cloud/storage — GCS asset access
# @neondatabase/serverless or pg — Postgres / pgvector
If you are building an external integration that calls Colony's HTTP surface, install only the HTTP client:
npm install ky # lightweight fetch wrapper (recommended)
# or
npm install axios
Authentication Setup
Colony uses Clerk for authentication and authorization. All API routes require a valid Clerk session token issued for an authenticated organization member. Three RBAC roles are enforced:
| Role | Clerk Metadata Key | Permissions |
|---|---|---|
owner | role: "owner" | Full access including vault management |
admin | role: "admin" | All operational access, no vault destroy |
member | role: "member" | Read + approval actions; no settings |
Server-Side (Next.js App Router)
// lib/auth.ts
import { auth } from "@clerk/nextjs/server";
export async function requireAuth() {
const { userId, orgId, orgRole } = await auth();
if (!userId || !orgId) {
throw new ColonyAuthError("Unauthenticated — no active Clerk session.");
}
return { userId, orgId, orgRole };
}
Client-Side (React components)
import { useAuth, useOrganization } from "@clerk/nextjs";
function MyComponent() {
const { getToken, orgId } = useAuth();
const { organization } = useOrganization();
async function callColonyAPI(endpoint: string, body: unknown) {
// Clerk session token — always fresh
const token = await getToken();
const response = await fetch(endpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${token}`,
"X-Colony-Org-Id": orgId ?? "",
},
body: JSON.stringify(body),
});
if (!response.ok) {
throw new ColonyAPIError(response);
}
return response.json();
}
}
Service-Account Requests (Inngest / Background Jobs)
Background agents running inside Inngest functions authenticate via the Colony Vault (api_keys table) rather than Clerk session tokens. Vault keys are KMS-encrypted per organization and resolved server-side; they are never exposed to the client.
// Used internally by agent functions — not called by external consumers.
import { getVaultKey } from "@/lib/vault";
const pipedriveToken = await getVaultKey(orgId, "pipedrive");
Client Initialization
The ColonyClient class provides a typed interface over Colony's API routes. Initialize it once per request context (server) or once per session (client).
// lib/colony-client.ts
import { auth } from "@clerk/nextjs/server";
export interface ColonyClientConfig {
/** Base URL of the Colony deployment. Defaults to NEXT_PUBLIC_APP_URL. */
baseUrl?: string;
/** Override the Clerk token for server-to-server calls. */
token?: string;
/** Request timeout in milliseconds. Default: 30000. */
timeoutMs?: number;
/** Number of automatic retries on 429 / 503. Default: 2. */
maxRetries?: number;
}
export class ColonyClient {
private baseUrl: string;
private token: string | null;
private timeoutMs: number;
private maxRetries: number;
constructor(config: ColonyClientConfig = {}) {
this.baseUrl = config.baseUrl ?? process.env.NEXT_PUBLIC_APP_URL ?? "http://localhost:3000";
this.token = config.token ?? null;
this.timeoutMs = config.timeoutMs ?? 30_000;
this.maxRetries = config.maxRetries ?? 2;
}
/** Create a server-side client with the current Clerk session. */
static async fromServerSession(config?: Omit<ColonyClientConfig, "token">): Promise<ColonyClient> {
const { getToken } = await auth();
const token = await getToken();
return new ColonyClient({ ...config, token: token ?? undefined });
}
/** @internal */
async fetch<T>(
path: string,
init: RequestInit & { params?: Record<string, string | number | boolean | undefined> } = {}
): Promise<T> {
const { params, ...fetchInit } = init;
const url = new URL(path, this.baseUrl);
if (params) {
for (const [k, v] of Object.entries(params)) {
if (v !== undefined) url.searchParams.set(k, String(v));
}
}
const headers = new Headers(fetchInit.headers);
if (this.token) headers.set("Authorization", `Bearer ${this.token}`);
headers.set("Content-Type", "application/json");
let attempt = 0;
while (true) {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
try {
const response = await fetch(url.toString(), {
...fetchInit,
headers,
signal: controller.signal,
});
clearTimeout(timeout);
if (response.status === 429 && attempt < this.maxRetries) {
const retryAfter = Number(response.headers.get("Retry-After") ?? 1);
await sleep(retryAfter * 1_000);
attempt++;
continue;
}
if (!response.ok) {
throw await ColonyAPIError.fromResponse(response);
}
return response.json() as Promise<T>;
} catch (err) {
clearTimeout(timeout);
if (err instanceof ColonyAPIError) throw err;
throw new ColonyNetworkError(String(err));
}
}
}
}
function sleep(ms: number) {
return new Promise((r) => setTimeout(r, ms));
}
Type Definitions
// types/colony.ts
// ─── Shared primitives ────────────────────────────────────────────────────────
export type ISO8601 = string;
export type UUID = string;
export type OrgId = string;
export type PaginatedResponse<T> = {
data: T[];
total: number;
page: number;
pageSize: number;
hasMore: boolean;
};
export type ColonyRole = "owner" | "admin" | "member";
// ─── Errors ───────────────────────────────────────────────────────────────────
export interface ColonyErrorPayload {
code: string;
message: string;
details?: Record<string, unknown>;
}
// ─── Contacts & Prospects ─────────────────────────────────────────────────────
export type ContactStatus =
| "candidate"
| "approved"
| "rejected"
| "in_sequence"
| "replied"
| "meeting_booked"
| "disqualified";
export type ContactSource =
| "pipedrive_leadbooster"
| "google_places"
| "serpapi"
| "unipile_search"
| "csv_upload"
| "manual";
export interface Contact {
id: UUID;
orgId: OrgId;
firstName: string;
lastName: string;
email: string | null;
linkedinUrl: string | null;
title: string | null;
companyName: string | null;
companyDomain: string | null;
phone: string | null;
location: string | null;
icpScore: number | null; // 0–100
icpTier: "T1" | "T2" | "T3" | "T4" | "T5" | null;
status: ContactStatus;
source: ContactSource;
pipedrivePersonId: string | null;
pipedriveOrgId: string | null;
tags: string[];
customFields: Record<string, unknown>;
createdAt: ISO8601;
updatedAt: ISO8601;
}
export interface CreateContactInput {
firstName: string;
lastName: string;
email?: string;
linkedinUrl?: string;
title?: string;
companyName?: string;
companyDomain?: string;
phone?: string;
location?: string;
source?: ContactSource;
tags?: string[];
customFields?: Record<string, unknown>;
}
export interface UpdateContactInput extends Partial<CreateContactInput> {
status?: ContactStatus;
icpScore?: number;
icpTier?: Contact["icpTier"];
pipedrivePersonId?: string;
pipedriveOrgId?: string;
}
export interface ContactListFilters {
status?: ContactStatus;
icpTier?: Contact["icpTier"];
source?: ContactSource;
search?: string;
tags?: string[];
page?: number;
pageSize?: number;
}
// ─── Prospect Discovery ───────────────────────────────────────────────────────
export interface ProspectDiscoveryJob {
id: UUID;
orgId: OrgId;
status: "queued" | "running" | "completed" | "failed";
sources: ContactSource[];
filters: ProspectDiscoveryFilters;
candidatesFound: number;
candidatesImported: number;
inngestRunId: string | null;
createdAt: ISO8601;
completedAt: ISO8601 | null;
error: string | null;
}
export interface ProspectDiscoveryFilters {
keywords?: string[];
industries?: string[];
locations?: string[];
titles?: string[];
companySize?: string;
limit?: number;
antiIcpExclusions?: string[];
}
export interface StartDiscoveryInput {
sources: ContactSource[];
filters: ProspectDiscoveryFilters;
}
// ─── Outbound Sequences ───────────────────────────────────────────────────────
export type OutboundAngle =
| "signal"
| "pain"
| "referral"
| "pattern_break"
| "insight";
export type SequenceTier = "T1" | "T2" | "T3" | "T4" | "T5";
export type SequenceStepChannel = "email" | "linkedin" | "phone";
export type SequenceStepStatus =
| "pending"
| "approved"
| "sent"
| "failed"
| "skipped";
export interface SequenceStep {
id: UUID;
sequenceEnrollmentId: UUID;
stepNumber: number;
channel: SequenceStepChannel;
angle: OutboundAngle;
subject: string | null;
body: string;
scheduledAt: ISO8601 | null;
sentAt: ISO8601 | null;
status: SequenceStepStatus;
unipileMessageId: string | null;
resendEmailId: string | null;
}
export interface SequenceEnrollment {
id: UUID;
orgId: OrgId;
contactId: UUID;
tier: SequenceTier;
angle: OutboundAngle;
status: "active" | "paused" | "completed" | "bounced" | "replied" | "unsubscribed";
currentStep: number;
totalSteps: number;
steps: SequenceStep[];
createdAt: ISO8601;
updatedAt: ISO8601;
}
export interface EnrollContactInput {
contactId: UUID;
tier: SequenceTier;
angle: OutboundAngle;
customVariables?: Record<string, string>;
}
// ─── HOT Reply Queue ──────────────────────────────────────────────────────────
export interface HotReply {
id: UUID;
orgId: OrgId;
contactId: UUID;
contact: Pick<Contact, "firstName" | "lastName" | "email" | "companyName">;
inboundMessage: string;
inboundChannel: SequenceStepChannel;
suggestedReply: string | null;
sentiment: "positive" | "neutral" | "negative" | "out_of_office";
priority: "hot" | "warm" | "cold";
status: "pending" | "sent" | "dismissed";
createdAt: ISO8601;
}
// ─── Qualification ────────────────────────────────────────────────────────────
export interface QualificationResult {
id: UUID;
contactId: UUID;
orgId: OrgId;
icpScore: number;
icpTier: Contact["icpTier"];
signals: QualificationSignal[];
antiIcpFlags: string[];
reasoning: string;
recommendedAction: "enroll" | "skip" | "review";
qualifiedAt: ISO8601;
modelVersion: string;
}
export interface QualificationSignal {
type: string;
label: string;
weight: number;
evidence: string;
}
export interface TriggerQualificationInput {
contactIds: UUID[];
forceRefresh?: boolean;
}
// ─── Pipeline & Deals ─────────────────────────────────────────────────────────
export type DealStage =
| "lead"
| "qualified"
| "discovery"
| "solution"
| "proposal"
| "negotiation"
| "closed_won"
| "closed_lost"
| "on_hold";
export interface Deal {
id: UUID;
orgId: OrgId;
title: string;
stage: DealStage;
value: number | null;
currency: string;
contactId: UUID | null;
pipedriveId: string | null;
icpScore: number | null;
probability: number | null;
expectedCloseDate: ISO8601 | null;
ownerId: string;
notes: string | null;
createdAt: ISO8601;
updatedAt: ISO8601;
}
export interface CreateDealInput {
title: string;
stage?: DealStage;
value?: number;
currency?: string;
contactId?: UUID;
expectedCloseDate?: ISO8601;
notes?: string;
}
export interface UpdateDealInput extends Partial<CreateDealInput> {
stage?: DealStage;
probability?: number;
pipedriveId?: string;
}
export interface DealListFilters {
stage?: DealStage;
ownerId?: string;
search?: string;
page?: number;
pageSize?: number;
}
// ─── Recording Intelligence ───────────────────────────────────────────────────
export type RecordingStatus =
| "pending_ingestion"
| "transcribing"
| "extracting_signals"
| "completed"
| "failed";
export interface Recording {
id: UUID;
orgId: OrgId;
title: string;
driveFileId: string;
driveWebViewLink: string | null;
durationSeconds: number | null;
participants: string[];
dealId: UUID | null;
contactId: UUID | null;
transcript: string | null;
summary: string | null;
signals: RecordingSignal[];
status: RecordingStatus;
recordedAt: ISO8601 | null;
ingestedAt: ISO8601 | null;
processedAt: ISO8601 | null;
}
export interface RecordingSignal {
category: "pain_point" | "next_step" | "objection" | "commitment" | "competitor" | "budget";
text: string;
confidence: number;
crmField: string | null;
}
export interface IngestRecordingInput {
driveFileId: string;
dealId?: UUID;
contactId?: UUID;
}
// ─── Content Pipeline ─────────────────────────────────────────────────────────
export type ContentPillar =
| "thought_leadership"
| "social_proof"
| "product_education"
| "industry_insight"
| "behind_the_scenes"
| "community";
export type ContentStatus =
| "draft"
| "review"
| "approved"
| "scheduled"
| "published"
| "archived";
export type ContentChannel =
| "linkedin_post"
| "twitter_thread"
| "newsletter"
| "blog"
| "email_nurture";
export interface ContentPiece {
id: UUID;
orgId: OrgId;
pillar: ContentPillar;
channel: ContentChannel;
title: string;
body: string;
status: ContentStatus;
scheduledAt: ISO8601 | null;
publishedAt: ISO8601 | null;
pipelineInfluence: number | null; // attributed pipeline $ value
attributedDealIds: UUID[];
gcsAssetUrl: string | null;
createdAt: ISO8601;
updatedAt: ISO8601;
}
export interface GenerateContentInput {
pillar: ContentPillar;
channel: ContentChannel;
topic?: string;
referenceContactId?: UUID;
referenceDealId?: UUID;
knowledgeCoreQuery?: string;
}
// ─── Onboarding & Deployment Kit ─────────────────────────────────────────────
export type DeploymentKitAsset =
| "pitch_deck"
| "one_pager"
| "case_study_shortlist"
| "pricing_reference"
| "rollout_calendar"
| "stakeholder_map"
| "risk_register"
| "kpi_dashboard";
export interface DeploymentKit {
id: UUID;
orgId: OrgId;
dealId: UUID;
status: "generating" | "ready" | "failed";
assets: DeploymentKitAssetRecord[];
generatedAt: ISO8601 | null;
createdAt: ISO8601;
}
export interface DeploymentKitAssetRecord {
assetType: DeploymentKitAsset;
gcsUrl: string;
signedUrl: string;
signedUrlExpiresAt: ISO8601;
generatedAt: ISO8601;
}
export interface GenerateDeploymentKitInput {
dealId: UUID;
/** Override the set of assets to generate. Defaults to all 8. */
assets?: DeploymentKitAsset[];
}
// ─── Knowledge Core ───────────────────────────────────────────────────────────
export type KnowledgeDomain =
| "product"
| "market"
| "competition"
| "customer_pain"
| "objection_handling"
| "pricing"
| "case_studies"
| "messaging"
| "team"
| "compliance";
export interface KnowledgeEntry {
id: UUID;
orgId: OrgId;
domain: KnowledgeDomain;
title: string;
body: string;
embedding: number[] | null; // 1536-dim pgvector — omitted from list responses
tags: string[];
sourceUrl: string | null;
createdAt: ISO8601;
updatedAt: ISO8601;
}
export interface CreateKnowledgeEntryInput {
domain: KnowledgeDomain;
title: string;
body: string;
tags?: string[];
sourceUrl?: string;
}
export interface KnowledgeSearchInput {
query: string;
domain?: KnowledgeDomain;
limit?: number;
}
export interface KnowledgeSearchResult {
entry: Omit<KnowledgeEntry, "embedding">;
similarity: number; // cosine similarity 0–1
}
// ─── Approvals ────────────────────────────────────────────────────────────────
export type ApprovalEntityType =
| "sequence_step"
| "content_piece"
| "hot_reply"
| "candidate_contact"
| "deployment_kit_asset";
export type ApprovalStatus = "pending" | "approved" | "rejected" | "auto_approved";
export interface Approval {
id: UUID;
orgId: OrgId;
entityType: ApprovalEntityType;
entityId: UUID;
status: ApprovalStatus;
reviewedBy: string | null;
reviewNote: string | null;
createdAt: ISO8601;
reviewedAt: ISO8601 | null;
}
export interface ReviewApprovalInput {
status: "approved" | "rejected";
note?: string;
}
// ─── Circuit Breakers ─────────────────────────────────────────────────────────
export interface CircuitBreaker {
id: UUID;
orgId: OrgId;
name: string;
description: string;
metric: string;
threshold: number;
windowMinutes: number;
currentValue: number;
state: "closed" | "open" | "half_open";
trippedAt: ISO8601 | null;
resetAt: ISO8601 | null;
updatedAt: ISO8601;
}
export interface UpdateCircuitBreakerInput {
threshold?: number;
windowMinutes?: number;
state?: "closed" | "half_open";
}
// ─── API Keys Vault ───────────────────────────────────────────────────────────
export type VaultKeyProvider =
| "pipedrive"
| "unipile"
| "resend"
| "google_oauth"
| "serpapi"
| "openai"
| "anthropic"
| "gemini"
| "notion";
export interface VaultKeyRecord {
id: UUID;
orgId: OrgId;
provider: VaultKeyProvider;
label: string;
/** Key value is never returned after write. */
maskedValue: string;
isActive: boolean;
createdAt: ISO8601;
updatedAt: ISO8601;
}
export interface UpsertVaultKeyInput {
provider: VaultKeyProvider;
value: string;
label?: string;
}
// ─── Analytics & Daily Brief ──────────────────────────────────────────────────
export interface DailyBrief {
id: UUID;
orgId: OrgId;
date: string; // YYYY-MM-DD
pipelineSnapshot: PipelineSnapshot;
alerts: BriefAlert[];
approvalQueueDepth: number;
yesterdayOutput: YesterdayOutput;
deliveredAt: ISO8601 | null;
createdAt: ISO8601;
}
export interface PipelineSnapshot {
totalDeals: number;
totalValue: number;
currency: string;
byStage: Record<DealStage, { count: number; value: number }>;
closingThisWeek: number;
}
export interface BriefAlert {
severity: "critical" | "warning" | "info";
message: string;
entityType: string | null;
entityId: UUID | null;
}
export interface YesterdayOutput {
emailsSent: number;
linkedinMessagesSent: number;
newReplies: number;
meetingsBooked: number;
contentPiecesPublished: number;
recordingsProcessed: number;
}
export interface AnalyticsSummary {
period: "day" | "week" | "month";
from: ISO8601;
to: ISO8601;
outbound: {
emailsSent: number;
linkedinSent: number;
replyRate: number;
meetingBookRate: number;
};
pipeline: PipelineSnapshot;
content: {
piecesPublished: number;
attributedPipeline: number;
};
topContacts: Pick<Contact, "id" | "firstName" | "lastName" | "companyName" | "icpScore">[];
}
// ─── Command Interface (Chat) ─────────────────────────────────────────────────
export type ChatRole = "user" | "assistant" | "tool";
export interface ChatMessage {
id: UUID;
threadId: UUID;
role: ChatRole;
content: string;
toolName: string | null;
toolInput: Record<string, unknown> | null;
toolOutput: Record<string, unknown> | null;
artifacts: ChatArtifact[];
createdAt: ISO8601;
}
export interface ChatArtifact {
type: "contact_list" | "content_piece" | "deployment_kit" | "deal_card" | "approval_request" | "brief";
entityId: UUID;
preview: Record<string, unknown>;
}
export interface ChatThread {
id: UUID;
orgId: OrgId;
title: string | null;
messages: ChatMessage[];
createdAt: ISO8601;
updatedAt: ISO8601;
}
export interface SendMessageInput {
content: string;
threadId?: UUID; // omit to start new thread
}
// ─── Clerk Webhook Events ─────────────────────────────────────────────────────
export interface ClerkWebhookEvent {
type:
| "organization.created"
| "organization.deleted"
| "organization.updated"
| "organizationMembership.created"
| "organizationMembership.deleted"
| "organizationMembership.updated"
| "user.created"
| "user.deleted";
data: Record<string, unknown>;
object: "event";
timestamp: number;
}
// ─── Error classes ────────────────────────────────────────────────────────────
export class ColonyAPIError extends Error {
constructor(
public readonly statusCode: number,
public readonly code: string,
message: string,
public readonly details?: Record<string, unknown>
) {
super(message);
this.name = "ColonyAPIError";
}
static async fromResponse(res: Response): Promise<ColonyAPIError> {
let payload: Partial<ColonyErrorPayload> = {};
try {
payload = await res.json();
} catch {}
return new ColonyAPIError(
res.status,
payload.code ?? "UNKNOWN",
payload.message ?? res.statusText,
payload.details
);
}
}
export class ColonyAuthError extends Error {
constructor(message: string) {
super(message);
this.name = "ColonyAuthError";
}
}
export class ColonyNetworkError extends Error {
constructor(message: string) {
super(message);
this.name = "ColonyNetworkError";
}
}
API Reference
Contacts & Prospects
listContacts
async listContacts(filters?: ContactListFilters): Promise<PaginatedResponse<Contact>>
| Parameter | Type | Required | Description |
|---|---|---|---|
filters.status | ContactStatus | No | Filter by lifecycle status |
filters.icpTier | "T1" | … | "T5" | No | Filter by ICP tier |
filters.source | ContactSource | No | Filter by discovery source |
filters.search | string | No | Full-text search across name, email, company |
filters.tags | string[] | No | Contacts tagged with ALL provided tags |
filters.page | number | No | Page index, 1-based. Default: 1 |
filters.pageSize | number | No | Records per page, max 100. Default: 25 |
Returns: PaginatedResponse<Contact>
import { ColonyClient } from "@/lib/colony-client";
const client = await ColonyClient.fromServerSession();
const result = await client.fetch<PaginatedResponse<Contact>>("/api/contacts", {
method: "GET",
params: {
status: "candidate",
icpTier: "T1",
page: 1,
pageSize: 25,
},
});
console.log(`Found ${result.total} contacts, showing ${result.data.length}`);
Error codes:
| HTTP | Code | Description |
|---|---|---|
| 401 | UNAUTHENTICATED | No valid Clerk session |
| 403 | FORBIDDEN | User not a member of this org |
getContact
async getContact(contactId: UUID): Promise<Contact>
| Parameter | Type | Required | Description |
|---|---|---|---|
contactId | UUID | Yes | Contact UUID |
const contact = await client.fetch<Contact>(`/api/contacts/${contactId}`);
createContact
async createContact(input: CreateContactInput): Promise<Contact>
| Parameter | Type | Required | Description |
|---|---|---|---|
firstName | string | Yes | |
lastName | string | Yes | |
email | string | No | |
linkedinUrl | string | No | Must be a valid linkedin.com/in/… URL |
title | string | No | Job title |
companyName | string | No | |
companyDomain | string | No | Bare domain, e.g. acme.com |
source | ContactSource | No | Defaults to "manual" |
tags | string[] | No | |
customFields | Record<string, unknown> | No | Arbitrary metadata |
const contact = await client.fetch<Contact>("/api/contacts", {
method: "POST",
body: JSON.stringify({
firstName: "Elena",
lastName: "Vasquez",
email: "