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
- Installation
- Authentication Setup
- Client Initialization
- Type Definitions
- Method Reference
- Error Handling
- Pagination Patterns
- 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-posapplication.
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
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
baseUrl | string | Yes | — | Root URL of the CNBS deployment |
getToken | () => Promise<string> | Yes | — | Async function that returns a valid Clerk session token |
organizationId | string | No | — | Clerk organization ID for org-scoped requests |
timeout | number | No | 30000 | Request timeout in milliseconds |
retries | number | No | 3 | Number 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
| Name | Type | Required | Description |
|---|---|---|---|
organizationId | string | Yes | Clerk organization ID |
adminUserId | string | Yes | Clerk user ID of the admin |
dispensaryName | string | Yes | Legal dispensary name |
licenseNumber | string | Yes | State-issued cannabis license number |
stateCode | string | Yes | Two-letter state abbreviation (e.g. "CO") |
timezone | string | Yes | IANA 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
| Name | Type | Required | Description |
|---|---|---|---|
imageUrl | string | Yes | Public 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
| Name | Type | Required | Description |
|---|---|---|---|
dispensaryName | string | Yes | Name used in prompt context |
brandColors | string[] | No | Hex colors to inform generation |
style | 'modern' | 'organic' | 'luxury' | 'street' | No | Visual style preset |
prompt | string | No | Additional 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
| Name | Type | Required | Description |
|---|---|---|---|
prompt | string | Yes | Text description of the desired image |
size | '256x256' | '512x512' | '1024x1024' | '1792x1024' | '1024x1792' | No | Output dimensions (default: '1024x1024') |
style | 'vivid' | 'natural' | No | Image style (default: 'vivid') |
quality | 'standard' | 'hd' | No | Render 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
| Name | Type | Required | Description |
|---|---|---|---|
productName | string | Yes | Product display name |
productCategory | string | Yes | Product category (e.g. "Flower", "Edibles") |
discount | string | No | Discount description (e.g. "20% off") |
targetAudience | string | No | Audience descriptor |
tone | 'casual' | 'professional' | 'playful' | 'luxury' | No | Copy 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
| Name | Type | Required | Description |
|---|---|---|---|
imageUrl | string | Yes | Temporary image URL to persist |
organizationId | string | Yes | Owning organization |
context | 'hero' | 'product' | 'promotion' | 'banner' | Yes | Asset usage context |
metadata | Record<string, string> | No | Arbitrary 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
| Name | Type | Required | Description |
|---|---|---|---|
startDate | ISODateString | No | Range start (inclusive) |
endDate | ISODateString | No | Range end (inclusive) |
preset | DateRangePreset | No | Named 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(