Colony

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

  1. Installation
  2. Authentication Setup
  3. Client Initialization
  4. Type Definitions
  5. API Reference
  6. Pagination Patterns
  7. Rate Limiting
  8. Webhook Handling
  9. 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:

RoleClerk Metadata KeyPermissions
ownerrole: "owner"Full access including vault management
adminrole: "admin"All operational access, no vault destroy
memberrole: "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>>
ParameterTypeRequiredDescription
filters.statusContactStatusNoFilter by lifecycle status
filters.icpTier"T1" | … | "T5"NoFilter by ICP tier
filters.sourceContactSourceNoFilter by discovery source
filters.searchstringNoFull-text search across name, email, company
filters.tagsstring[]NoContacts tagged with ALL provided tags
filters.pagenumberNoPage index, 1-based. Default: 1
filters.pageSizenumberNoRecords 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:

HTTPCodeDescription
401UNAUTHENTICATEDNo valid Clerk session
403FORBIDDENUser not a member of this org

getContact

async getContact(contactId: UUID): Promise<Contact>
ParameterTypeRequiredDescription
contactIdUUIDYesContact UUID
const contact = await client.fetch<Contact>(`/api/contacts/${contactId}`);

createContact

async createContact(input: CreateContactInput): Promise<Contact>
ParameterTypeRequiredDescription
firstNamestringYes
lastNamestringYes
emailstringNo
linkedinUrlstringNoMust be a valid linkedin.com/in/… URL
titlestringNoJob title
companyNamestringNo
companyDomainstringNoBare domain, e.g. acme.com
sourceContactSourceNoDefaults to "manual"
tagsstring[]No
customFieldsRecord<string, unknown>NoArbitrary metadata
const contact = await client.fetch<Contact>("/api/contacts", {
  method: "POST",
  body: JSON.stringify({
    firstName: "Elena",
    lastName: "Vasquez",
    email: "