CNBS

SDK reference

SDK reference

Last updated 5/24/2026

CNBS TypeScript SDK Reference

Version: 0.1.0 Base URL: https://your-domain.com/api Authentication: Clerk session tokens


Table of Contents

  1. Installation
  2. Authentication Setup
  3. Client Initialization
  4. Type Definitions
  5. Method Reference
  6. Error Handling
  7. Pagination Patterns
  8. Rate Limiting

Installation

npm

npm install @cnbs/sdk

yarn

yarn add @cnbs/sdk

pnpm

pnpm add @cnbs/sdk

Note: The CNBS SDK requires Node.js ≥ 18.0.0. The SDK is not distributed as a standalone pip or Go module; it is a first-party TypeScript package shipped alongside the dispensary-pos application.


Authentication Setup

CNBS uses Clerk for authentication. Every API request requires a valid Clerk session token passed as a Bearer token in the Authorization header.

Obtaining a Session Token (Browser)

import { useAuth } from '@clerk/nextjs';

function useApiClient() {
  const { getToken } = useAuth();

  const fetchWithAuth = async (url: string, options: RequestInit = {}) => {
    const token = await getToken();
    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        Authorization: `Bearer ${token}`,
        'Content-Type': 'application/json',
      },
    });
  };

  return { fetchWithAuth };
}

Obtaining a Session Token (Server-Side)

import { auth } from '@clerk/nextjs/server';

async function getServerToken(): Promise<string> {
  const { getToken } = auth();
  const token = await getToken();
  if (!token) throw new Error('Unauthenticated: no active Clerk session');
  return token;
}

Organization Context

Most endpoints scope data to a Clerk organization. Pass the active organization ID in the x-org-id header or include it in the request body where the API contract requires it.

const headers = {
  Authorization: `Bearer ${token}`,
  'x-org-id': organizationId,
  'Content-Type': 'application/json',
};

Client Initialization

import { CNBSClient } from '@cnbs/sdk';

const client = new CNBSClient({
  baseUrl: process.env.NEXT_PUBLIC_APP_URL ?? 'http://localhost:3000',
  getToken: async () => {
    // Supply your Clerk token retrieval function
    return clerkGetToken();
  },
  organizationId: 'org_xxxxxxxxxxxx', // Active Clerk org ID
  timeout: 30_000,                    // Request timeout in ms (default: 30 000)
  retries: 3,                         // Automatic retry count on 5xx (default: 3)
});

CNBSClientConfig

OptionTypeRequiredDefaultDescription
baseUrlstringYesRoot URL of the CNBS deployment
getToken() => Promise<string>YesAsync function that returns a valid Clerk session token
organizationIdstringNoClerk organization ID for org-scoped requests
timeoutnumberNo30000Request timeout in milliseconds
retriesnumberNo3Number of retries on transient 5xx errors

Type Definitions

// ─── Shared primitives ──────────────────────────────────────────────────────

export type UUID = string;
export type ISODateString = string; // e.g. "2024-11-01T12:00:00.000Z"
export type CurrencyAmount = number; // In cents

export interface PaginationParams {
  page?: number;     // 1-indexed (default: 1)
  limit?: number;    // Records per page (default: 20, max: 100)
  cursor?: string;   // Cursor-based pagination token
}

export interface PaginatedResponse<T> {
  data: T[];
  meta: {
    total: number;
    page: number;
    limit: number;
    hasNextPage: boolean;
    nextCursor?: string;
  };
}

export interface ApiError {
  error: string;
  message: string;
  statusCode: number;
  details?: Record<string, unknown>;
}

// ─── Admin ───────────────────────────────────────────────────────────────────

export interface OnboardingCompleteRequest {
  organizationId: string;
  adminUserId: string;
  dispensaryName: string;
  licenseNumber: string;
  stateCode: string;
  timezone: string;
}

export interface OnboardingCompleteResponse {
  success: boolean;
  organizationId: string;
  onboardedAt: ISODateString;
}

// ─── AI ──────────────────────────────────────────────────────────────────────

export interface ExtractColorsRequest {
  imageUrl: string;
}

export interface ExtractColorsResponse {
  primary: string;    // Hex color
  secondary: string;
  accent: string;
  palette: string[];
}

export interface GenerateHeroRequest {
  dispensaryName: string;
  brandColors?: string[];
  style?: 'modern' | 'organic' | 'luxury' | 'street';
  prompt?: string;
}

export interface GenerateHeroResponse {
  imageUrl: string;
  prompt: string;
  generatedAt: ISODateString;
}

export interface GenerateImageRequest {
  prompt: string;
  size?: '256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792';
  style?: 'vivid' | 'natural';
  quality?: 'standard' | 'hd';
}

export interface GenerateImageResponse {
  imageUrl: string;
  revisedPrompt: string;
  generatedAt: ISODateString;
}

export interface GeneratePromotionRequest {
  productName: string;
  productCategory: string;
  discount?: string;
  targetAudience?: string;
  tone?: 'casual' | 'professional' | 'playful' | 'luxury';
}

export interface GeneratePromotionResponse {
  headline: string;
  bodyText: string;
  ctaText: string;
  hashtags: string[];
}

export interface SaveGeneratedImageRequest {
  imageUrl: string;
  organizationId: string;
  context: 'hero' | 'product' | 'promotion' | 'banner';
  metadata?: Record<string, string>;
}

export interface SaveGeneratedImageResponse {
  storedUrl: string;
  assetId: UUID;
  savedAt: ISODateString;
}

// ─── Analytics ───────────────────────────────────────────────────────────────

export type DateRangePreset = '7d' | '30d' | '90d' | '1y' | 'custom';

export interface DateRangeParams {
  startDate?: ISODateString;
  endDate?: ISODateString;
  preset?: DateRangePreset;
}

export interface CustomerAnalyticsResponse {
  totalCustomers: number;
  newCustomers: number;
  returningCustomers: number;
  averageOrderValue: CurrencyAmount;
  topSegments: CustomerSegment[];
  churnRate: number;
  lifetimeValueAvg: CurrencyAmount;
}

export interface CustomerSegment {
  segmentId: UUID;
  name: string;
  customerCount: number;
  revenueContribution: CurrencyAmount;
}

export interface DashboardPerformanceResponse {
  revenueToday: CurrencyAmount;
  revenueThisWeek: CurrencyAmount;
  revenueThisMonth: CurrencyAmount;
  transactionsToday: number;
  averageTransactionValue: CurrencyAmount;
  topProducts: ProductPerformance[];
  topBudtenders: BudtenderPerformance[];
}

export interface ProductPerformance {
  productId: UUID;
  name: string;
  unitsSold: number;
  revenue: CurrencyAmount;
}

export interface BudtenderPerformance {
  associateId: UUID;
  name: string;
  transactionCount: number;
  revenue: CurrencyAmount;
}

export interface RealTimeAnalyticsResponse {
  activeTransactions: number;
  customersInStore: number;
  openRegisters: number;
  lowStockAlerts: number;
  timestamp: ISODateString;
}

export interface DashboardResponse {
  summary: DashboardPerformanceResponse;
  realTime: RealTimeAnalyticsResponse;
  generatedAt: ISODateString;
}

export interface TrendsResponse {
  period: DateRangePreset;
  revenueTimeSeries: TimeSeriesPoint[];
  transactionTimeSeries: TimeSeriesPoint[];
  categoryBreakdown: CategoryBreakdown[];
}

export interface TimeSeriesPoint {
  date: ISODateString;
  value: number;
}

export interface CategoryBreakdown {
  category: string;
  revenue: CurrencyAmount;
  percentage: number;
}

export interface InsightsResponse {
  insights: Insight[];
  generatedAt: ISODateString;
}

export interface Insight {
  type: 'opportunity' | 'risk' | 'trend' | 'anomaly';
  title: string;
  description: string;
  severity: 'low' | 'medium' | 'high';
  metric?: string;
  value?: number;
}

export interface InventoryInsightsResponse {
  lowStockItems: InventoryAlert[];
  overStockItems: InventoryAlert[];
  expiringItems: InventoryAlert[];
  reorderSuggestions: ReorderSuggestion[];
}

export interface InventoryAlert {
  productId: UUID;
  productName: string;
  sku: string;
  currentStock: number;
  threshold: number;
  expiresAt?: ISODateString;
}

export interface ReorderSuggestion {
  productId: UUID;
  productName: string;
  suggestedQuantity: number;
  vendorId?: UUID;
}

export interface InventoryAnalyticsResponse {
  totalSkus: number;
  totalUnits: number;
  totalCostValue: CurrencyAmount;
  totalRetailValue: CurrencyAmount;
  turnoverRate: number;
  stockouts: number;
  topMovingProducts: ProductPerformance[];
}

export interface PerformanceAnalyticsResponse {
  period: DateRangePreset;
  revenue: CurrencyAmount;
  revenueGrowth: number;        // Percentage
  transactionCount: number;
  transactionGrowth: number;    // Percentage
  averageBasket: CurrencyAmount;
  conversionRate: number;
}

export interface PredictionsResponse {
  nextWeekRevenue: CurrencyAmount;
  nextMonthRevenue: CurrencyAmount;
  demandForecasts: DemandForecast[];
  confidence: number;           // 0-1
}

export interface DemandForecast {
  productId: UUID;
  productName: string;
  forecastedUnits: number;
  forecastPeriod: string;
}

export interface ComplianceReportResponse {
  reportId: UUID;
  generatedAt: ISODateString;
  period: { start: ISODateString; end: ISODateString };
  totalSales: CurrencyAmount;
  taxCollected: CurrencyAmount;
  dailyLimitViolations: number;
  metrcSubmissions: number;
  reportUrl?: string;
}

export interface InventoryReportResponse {
  reportId: UUID;
  generatedAt: ISODateString;
  period: { start: ISODateString; end: ISODateString };
  openingStock: number;
  closingStock: number;
  received: number;
  sold: number;
  wasted: number;
  adjustments: number;
  reportUrl?: string;
}

export interface ReportsListResponse {
  reports: ReportSummary[];
}

export interface ReportSummary {
  reportId: UUID;
  type: 'compliance' | 'inventory' | 'sales';
  generatedAt: ISODateString;
  period: { start: ISODateString; end: ISODateString };
  status: 'pending' | 'ready' | 'error';
  reportUrl?: string;
}

export interface SalesReportResponse {
  reportId: UUID;
  generatedAt: ISODateString;
  period: { start: ISODateString; end: ISODateString };
  grossRevenue: CurrencyAmount;
  netRevenue: CurrencyAmount;
  refunds: CurrencyAmount;
  taxCollected: CurrencyAmount;
  transactionCount: number;
  averageOrderValue: CurrencyAmount;
  reportUrl?: string;
}

export interface RevenueAnalyticsResponse {
  today: CurrencyAmount;
  thisWeek: CurrencyAmount;
  thisMonth: CurrencyAmount;
  thisYear: CurrencyAmount;
  revenueByPaymentMethod: Record<string, CurrencyAmount>;
  revenueByCategory: Record<string, CurrencyAmount>;
}

export interface VitalsResponse {
  uptime: number;               // Percentage
  apiLatencyP50: number;        // ms
  apiLatencyP99: number;        // ms
  errorRate: number;            // Percentage
  activeUsers: number;
  queueDepth: number;
  timestamp: ISODateString;
}

// ─── Associates ──────────────────────────────────────────────────────────────

export interface Associate {
  associateId: UUID;
  organizationId: string;
  firstName: string;
  lastName: string;
  email: string;
  role: 'budtender' | 'manager' | 'admin';
  status: 'active' | 'inactive' | 'suspended';
  licenseNumber?: string;
  hireDate: ISODateString;
  createdAt: ISODateString;
  updatedAt: ISODateString;
}

export interface CreateAssociateRequest {
  firstName: string;
  lastName: string;
  email: string;
  role: 'budtender' | 'manager' | 'admin';
  licenseNumber?: string;
  hireDate: ISODateString;
}

export interface UpdateAssociateRequest extends Partial<CreateAssociateRequest> {
  status?: 'active' | 'inactive' | 'suspended';
}

// ─── Auth ─────────────────────────────────────────────────────────────────────

export interface CheckRoleResponse {
  userId: string;
  organizationId: string;
  role: string;
  permissions: string[];
  isAdmin: boolean;
}

export interface CheckUserContextResponse {
  userId: string;
  email: string;
  organizationId: string;
  organizationRole: string;
  onboardingComplete: boolean;
  activeSession: boolean;
}

export interface EmployeeAuthRequest {
  pinCode: string;
  stationId: string;
  organizationId: string;
}

export interface EmployeeAuthResponse {
  success: boolean;
  associateId: UUID;
  firstName: string;
  role: string;
  sessionToken: string;
  expiresAt: ISODateString;
}

// ─── Backup ───────────────────────────────────────────────────────────────────

export interface BackupResponse {
  backupId: UUID;
  status: 'initiated' | 'in_progress' | 'complete' | 'failed';
  initiatedAt: ISODateString;
  estimatedCompletionAt?: ISODateString;
  downloadUrl?: string;
}

// ─── Canonical Data ───────────────────────────────────────────────────────────

export interface CanonicalBrand {
  brandId: UUID;
  name: string;
  slug: string;
  logoUrl?: string;
  websiteUrl?: string;
  stateAvailability: string[];
}

export interface CanonicalCategory {
  categoryId: UUID;
  name: string;
  slug: string;
  parentCategoryId?: UUID;
  description?: string;
}

export interface CanonicalEffect {
  effectId: UUID;
  name: string;
  slug: string;
  type: 'feeling' | 'medical' | 'flavor' | 'terpene';
  description?: string;
}

// ─── Careers ──────────────────────────────────────────────────────────────────

export interface JobApplicationRequest {
  firstName: string;
  lastName: string;
  email: string;
  phone?: string;
  positionId: string;
  resumeUrl: string;
  coverLetter?: string;
}

export interface JobApplicationResponse {
  applicationId: UUID;
  status: 'received';
  submittedAt: ISODateString;
}

// ─── Cash Drawer ──────────────────────────────────────────────────────────────

export interface CashDrawerStatus {
  drawerId: UUID;
  stationId: string;
  status: 'open' | 'closed' | 'counted';
  openingBalance: CurrencyAmount;
  currentBalance: CurrencyAmount;
  lastOpenedAt?: ISODateString;
  lastClosedAt?: ISODateString;
  associateId?: UUID;
}

export interface CashDrawerActionRequest {
  action: 'open' | 'close' | 'count' | 'drop';
  stationId: string;
  associateId: UUID;
  amount?: CurrencyAmount;
  note?: string;
}

export interface CashDrawerActionResponse {
  success: boolean;
  drawerId: UUID;
  action: string;
  performedAt: ISODateString;
  newBalance?: CurrencyAmount;
}

// ─── Clerk Sync ───────────────────────────────────────────────────────────────

export interface AddToOrganizationRequest {
  userId: string;
  organizationId: string;
  role: string;
}

export interface AddToOrganizationResponse {
  success: boolean;
  membershipId: string;
}

export interface ClerkSyncCustomerRequest {
  clerkUserId: string;
  email: string;
  firstName?: string;
  lastName?: string;
  phone?: string;
}

export interface ClerkSyncCustomerResponse {
  customerId: UUID;
  synced: boolean;
  syncedAt: ISODateString;
}

export interface ClerkSyncMembershipRequest {
  userId: string;
  organizationId: string;
  role?: string;
}

export interface ClerkSyncMembershipResponse {
  success: boolean;
  userId: string;
  organizationId: string;
  action: 'added' | 'removed';
}

export interface ClerkSyncOrganizationRequest {
  clerkOrganizationId: string;
  name: string;
  slug?: string;
  metadata?: Record<string, unknown>;
}

export interface ClerkSyncOrganizationResponse {
  organizationId: string;
  synced: boolean;
  syncedAt: ISODateString;
}

export interface ClerkSyncRequest {
  event: string;
  data: Record<string, unknown>;
}

export interface ClerkSyncResponse {
  processed: boolean;
  event: string;
  processedAt: ISODateString;
}

export interface ClerkSyncUserRequest {
  clerkUserId: string;
  email: string;
  firstName?: string;
  lastName?: string;
  imageUrl?: string;
  metadata?: Record<string, unknown>;
}

export interface ClerkSyncUserResponse {
  userId: UUID;
  synced: boolean;
  syncedAt: ISODateString;
}

Method Reference

Admin

admin.completeOnboarding()

Marks a dispensary organization as fully onboarded in the CNBS system. Called once after initial setup is complete.

Route: POST /api/admin/onboarding/complete File: src/app/api/admin/onboarding/complete/route.ts

completeOnboarding(request: OnboardingCompleteRequest): Promise<OnboardingCompleteResponse>

Parameters

NameTypeRequiredDescription
organizationIdstringYesClerk organization ID
adminUserIdstringYesClerk user ID of the admin
dispensaryNamestringYesLegal dispensary name
licenseNumberstringYesState-issued cannabis license number
stateCodestringYesTwo-letter state abbreviation (e.g. "CO")
timezonestringYesIANA timezone (e.g. "America/Denver")

Returns: Promise<OnboardingCompleteResponse>

Example

const result = await client.admin.completeOnboarding({
  organizationId: 'org_2abc123',
  adminUserId: 'user_2xyz456',
  dispensaryName: 'Green Valley Dispensary',
  licenseNumber: 'CO-MED-2024-001',
  stateCode: 'CO',
  timezone: 'America/Denver',
});

console.log(result.onboardedAt); // "2024-11-01T14:00:00.000Z"

Error Handling

try {
  await client.admin.completeOnboarding(request);
} catch (err) {
  if (err instanceof CNBSApiError) {
    if (err.statusCode === 409) {
      // Organization already onboarded
    }
    if (err.statusCode === 403) {
      // Caller does not have admin role in this organization
    }
  }
}

AI

ai.extractColors()

Extracts a color palette from a product or brand image using AI vision.

Route: POST /api/ai/extract-colors File: src/app/api/ai/extract-colors/route.ts

extractColors(request: ExtractColorsRequest): Promise<ExtractColorsResponse>

Parameters

NameTypeRequiredDescription
imageUrlstringYesPublic URL of the source image

Returns: Promise<ExtractColorsResponse>

Example

const palette = await client.ai.extractColors({
  imageUrl: 'https://cdn.example.com/brand-logo.png',
});

console.log(palette.primary);   // "#2D6A4F"
console.log(palette.palette);   // ["#2D6A4F", "#52B788", "#B7E4C7", ...]

ai.generateHero()

Generates a hero banner image for a dispensary storefront.

Route: POST /api/ai/generate-hero File: src/app/api/ai/generate-hero/route.ts

generateHero(request: GenerateHeroRequest): Promise<GenerateHeroResponse>

Parameters

NameTypeRequiredDescription
dispensaryNamestringYesName used in prompt context
brandColorsstring[]NoHex colors to inform generation
style'modern' | 'organic' | 'luxury' | 'street'NoVisual style preset
promptstringNoAdditional freeform prompt instructions

Returns: Promise<GenerateHeroResponse>

Example

const hero = await client.ai.generateHero({
  dispensaryName: 'Green Valley',
  brandColors: ['#2D6A4F', '#52B788'],
  style: 'modern',
});

console.log(hero.imageUrl); // Temporary OpenAI CDN URL – save immediately

Warning: OpenAI-generated image URLs expire within 1 hour. Call ai.saveGeneratedImage() immediately after generation.


ai.generateImage()

Generates a general-purpose image from a text prompt.

Route: POST /api/ai/generate-image File: src/app/api/ai/generate-image/route.ts

generateImage(request: GenerateImageRequest): Promise<GenerateImageResponse>

Parameters

NameTypeRequiredDescription
promptstringYesText description of the desired image
size'256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792'NoOutput dimensions (default: '1024x1024')
style'vivid' | 'natural'NoImage style (default: 'vivid')
quality'standard' | 'hd'NoRender quality (default: 'standard')

Returns: Promise<GenerateImageResponse>

Example

const image = await client.ai.generateImage({
  prompt: 'Premium cannabis flower in a modern dispensary display case, professional product photography',
  size: '1024x1024',
  quality: 'hd',
});

ai.generatePromotion()

Generates marketing copy for a product promotion.

Route: POST /api/ai/generate-promotion File: src/app/api/ai/generate-promotion/route.ts

generatePromotion(request: GeneratePromotionRequest): Promise<GeneratePromotionResponse>

Parameters

NameTypeRequiredDescription
productNamestringYesProduct display name
productCategorystringYesProduct category (e.g. "Flower", "Edibles")
discountstringNoDiscount description (e.g. "20% off")
targetAudiencestringNoAudience descriptor
tone'casual' | 'professional' | 'playful' | 'luxury'NoCopy tone (default: 'casual')

Returns: Promise<GeneratePromotionResponse>

Example

const promo = await client.ai.generatePromotion({
  productName: 'Blue Dream Pre-Rolls',
  productCategory: 'Pre-Rolls',
  discount: '3 for $25',
  tone: 'casual',
});

console.log(promo.headline);   // "Dream Bigger for Less"
console.log(promo.hashtags);   // ["#BlueDream", "#PreRolls", "#DealAlert"]

ai.saveGeneratedImage()

Persists an AI-generated image from a temporary URL to permanent storage.

Route: POST /api/ai/save-generated-image File: src/app/api/ai/save-generated-image/route.ts

saveGeneratedImage(request: SaveGeneratedImageRequest): Promise<SaveGeneratedImageResponse>

Parameters

NameTypeRequiredDescription
imageUrlstringYesTemporary image URL to persist
organizationIdstringYesOwning organization
context'hero' | 'product' | 'promotion' | 'banner'YesAsset usage context
metadataRecord<string, string>NoArbitrary key-value tags

Returns: Promise<SaveGeneratedImageResponse>

Example

// Pattern: generate then immediately save
const hero = await client.ai.generateHero({ dispensaryName: 'Green Valley' });

const saved = await client.ai.saveGeneratedImage({
  imageUrl: hero.imageUrl,
  organizationId: 'org_2abc123',
  context: 'hero',
});

console.log(saved.storedUrl); // Permanent CDN URL

Analytics

analytics.getCustomers()

Returns customer acquisition and retention metrics for the organization.

Route: GET /api/analytics/customers File: src/app/api/analytics/customers/route.ts

getCustomers(params?: DateRangeParams): Promise<CustomerAnalyticsResponse>

Parameters

NameTypeRequiredDescription
startDateISODateStringNoRange start (inclusive)
endDateISODateStringNoRange end (inclusive)
presetDateRangePresetNoNamed preset overriding start/end

Returns: Promise<CustomerAnalyticsResponse>

Example

const customers = await client.analytics.getCustomers({ preset: '30d' });

console.log(customers.totalCustomers);    // 1 842
console.log(customers.churnRate);         // 0.032 (3.2%)
console.log(customers.lifetimeValueAvg);  // 38400 (cents)

analytics.getDashboardPerformance()

Returns KPI metrics for the performance sub-view of the main dashboard.

Route: GET /api/analytics/dashboard/performance File: src/app/api/analytics/dashboard/performance/route.ts

getDashboardPerformance(params?: DateRangeParams): Promise<DashboardPerformanceResponse>

Example

const perf = await client.analytics.getDashboardPerformance({ preset: '7d' });

perf.topProducts.forEach(p => {
  console.log(`${p.name}: ${p.unitsSold} units`);
});

analytics.getRealTime()

Returns live operational metrics. Poll this endpoint at intervals no shorter than 10 seconds.

Route: GET /api/analytics/dashboard/real-time File: src/app/api/analytics/dashboard/real-time/route.ts

getRealTime(): Promise<RealTimeAnalyticsResponse>

Parameters: None

Returns: Promise<RealTimeAnalyticsResponse>

Example

// Poll every 15 seconds
const intervalId = setInterval(async () => {
  const live = await client.analytics.getRealTime();
  console.log(`Active transactions: ${live.activeTransactions}`);
}, 15_000);

analytics.getDashboard()

Returns a combined dashboard payload including performance summary and real-time metrics.

Route: GET /api/analytics/dashboard File: src/app/api/analytics/dashboard/route.ts

getDashboard(params?: DateRangeParams): Promise<DashboardResponse>

Example

const dashboard = await client.analytics.getDashboard({ preset: '30d' });
console.log(
SDK reference · CNBS | Midwest