@chatarmin/os 1.0.0 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +750 -80
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,167 +1,837 @@
1
- # @chatarmin/os-sdk
1
+ # @chatarmin/os
2
2
 
3
- Type-safe SDK for ChatarminOS - Customer & Subscription Management.
3
+ > Type-safe SDK for ChatarminOS — the all-in-one platform for B2B SaaS to manage customers, subscriptions, feature access, and billing.
4
4
 
5
- ## Installation
5
+ [![npm version](https://img.shields.io/npm/v/@chatarmin/os.svg)](https://www.npmjs.com/package/@chatarmin/os)
6
+ [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg)](https://www.typescriptlang.org/)
6
7
 
7
- > **Note**: This is a private npm package. You need authentication to install it.
8
+ ## Features
8
9
 
9
- ### 1. Configure npm Authentication
10
+ - 🏢 **Company Management** — Create, link, and manage customer companies
11
+ - 🎫 **Subscription Tiers** — Define tiers with features and Stripe pricing
12
+ - ✨ **Feature Access** — Check entitlements and enforce usage limits
13
+ - 📊 **Usage Tracking** — Metered billing with idempotency support
14
+ - 💳 **Stripe Integration** — Claim checkout sessions, link subscriptions
15
+ - 🔗 **Smart Linking** — Intelligent company matching by domain/name/email
16
+ - 👥 **Contact Sync** — Bulk upsert contacts for each company
17
+ - 🚀 **Unified Onboarding** — Complete setup in a single API call
10
18
 
11
- Create a `.npmrc` file in your project root:
19
+ ---
12
20
 
13
- ```bash
14
- # .npmrc
15
- @chatarmin:registry=https://registry.npmjs.org/
16
- //registry.npmjs.org/:_authToken=${NPM_TOKEN}
17
- ```
21
+ ## Table of Contents
18
22
 
19
- Set your npm token as an environment variable:
23
+ - [Installation](#installation)
24
+ - [Quick Start](#quick-start)
25
+ - [API Reference](#api-reference)
26
+ - [Companies](#companies)
27
+ - [Contacts](#contacts)
28
+ - [Features](#features)
29
+ - [Billing](#billing)
30
+ - [Subscriptions & Tiers](#subscriptions--tiers)
31
+ - [Links](#links)
32
+ - [Onboarding](#onboarding)
33
+ - [Common Patterns](#common-patterns)
34
+ - [TypeScript Support](#typescript-support)
35
+ - [Configuration](#configuration)
36
+ - [Error Handling](#error-handling)
20
37
 
21
- ```bash
22
- # .env (for local development)
23
- NPM_TOKEN=npm_yourtokenhere
38
+ ---
24
39
 
25
- # Or export it in your shell
26
- export NPM_TOKEN=npm_yourtokenhere
27
- ```
28
-
29
- **For CI/CD**: Add `NPM_TOKEN` as a secret in your deployment platform (GitHub Actions, Vercel, etc.)
30
-
31
- ### 2. Install the Package
40
+ ## Installation
32
41
 
33
42
  ```bash
34
- npm install @chatarmin/os-sdk
43
+ pnpm add @chatarmin/os
35
44
  # or
36
- pnpm add @chatarmin/os-sdk
45
+ npm install @chatarmin/os
37
46
  # or
38
- yarn add @chatarmin/os-sdk
47
+ yarn add @chatarmin/os
39
48
  ```
40
49
 
41
- > See [PUBLISHING.md](./PUBLISHING.md) for detailed setup instructions.
50
+ ---
42
51
 
43
52
  ## Quick Start
44
53
 
45
54
  ```typescript
46
- import { ChatarminOS } from "@chatarmin/os-sdk"
55
+ import { ChatarminOS } from "@chatarmin/os"
47
56
 
48
57
  const os = new ChatarminOS({
49
- apiKey: process.env.CHATARMIN_OS_API_KEY!,
58
+ apiKey: process.env.OS_API_KEY!,
50
59
  // Optional: custom base URL for self-hosted
51
60
  // baseUrl: 'https://your-os.example.com/api/v1'
52
61
  })
62
+
63
+ // Onboard a new customer (single call does everything!)
64
+ const result = await os.onboard({
65
+ externalOrgId: "org_abc123", // Your product's org ID
66
+ hints: {
67
+ companyName: "Acme Inc",
68
+ domain: "acme.com",
69
+ },
70
+ tierCode: "free",
71
+ contactEmail: "admin@acme.com",
72
+ contactName: "John Doe",
73
+ })
74
+
75
+ console.log(result.companyId) // Use this for all future API calls
76
+ console.log(result.linkStatus) // 'created' | 'linked' | 'already_linked'
53
77
  ```
54
78
 
55
- ## Usage
79
+ ---
80
+
81
+ ## API Reference
56
82
 
57
83
  ### Companies
58
84
 
85
+ Manage customer companies and their links to your product.
86
+
87
+ #### `companies.getByProductLink(input)`
88
+
89
+ Look up a company by your external organization ID.
90
+
59
91
  ```typescript
60
- // Find company by your external org ID
61
92
  const company = await os.companies.getByProductLink({
62
- externalOrgId: "org_123",
93
+ externalOrgId: "org_abc123",
63
94
  })
64
95
 
65
- // Create a new company and link to your product
66
- const newCompany = await os.companies.create({
96
+ if (company) {
97
+ console.log(`Found: ${company.name} (${company.id})`)
98
+ } else {
99
+ console.log("Company not linked yet")
100
+ }
101
+ ```
102
+
103
+ #### `companies.create(input)`
104
+
105
+ Create a new company and link it to your product.
106
+
107
+ ```typescript
108
+ const company = await os.companies.create({
67
109
  name: "Acme Inc",
68
110
  domain: "acme.com",
69
- externalOrgId: "org_456",
70
- contactEmail: "billing@acme.com",
111
+ externalOrgId: "org_abc123",
112
+ contactEmail: "admin@acme.com",
71
113
  contactName: "John Doe",
114
+ createdAt: "2024-01-15T10:30:00.000Z", // Optional: historical date
115
+ })
116
+
117
+ console.log(company.id) // UUID
118
+ console.log(company.shortId) // e.g., "acme-inc"
119
+ ```
120
+
121
+ #### `companies.update(input)`
122
+
123
+ Update company metadata (e.g., backfill historical dates).
124
+
125
+ ```typescript
126
+ await os.companies.update({
127
+ companyId: "uuid-here",
128
+ createdAt: "2023-06-01T00:00:00.000Z",
72
129
  })
130
+ ```
131
+
132
+ #### `companies.smartLink(input)`
73
133
 
74
- // Smart link - find existing company by hints
75
- const linked = await os.companies.smartLink({
76
- externalOrgId: "org_789",
134
+ Intelligently find and link a company using hints.
135
+
136
+ ```typescript
137
+ const result = await os.companies.smartLink({
138
+ externalOrgId: "org_abc123",
77
139
  hints: {
78
- companyName: "Acme",
140
+ companyName: "Acme Inc",
79
141
  domain: "acme.com",
80
142
  emails: ["john@acme.com", "jane@acme.com"],
81
143
  },
82
- minConfidence: 0.8,
144
+ minConfidence: 0.7, // Default: 0.6
83
145
  })
146
+
147
+ switch (result.status) {
148
+ case "already_linked":
149
+ console.log("Already linked to your product")
150
+ break
151
+ case "linked":
152
+ console.log(
153
+ `Auto-linked to ${result.customerName} (${result.confidence}% confidence)`
154
+ )
155
+ break
156
+ case "suggestions":
157
+ console.log("Multiple matches found:", result.suggestions)
158
+ break
159
+ case "no_match":
160
+ console.log("No matching company found")
161
+ break
162
+ }
84
163
  ```
85
164
 
86
- ### Subscriptions & Tiers
165
+ **Matching priority:**
166
+
167
+ 1. Domain match → 95% confidence
168
+ 2. Name match → up to 80% confidence
169
+ 3. Email domain match → 70% confidence
170
+
171
+ ---
172
+
173
+ ### Contacts
174
+
175
+ Manage people associated with companies.
176
+
177
+ #### `contacts.create(input)`
178
+
179
+ Create or update a single contact.
87
180
 
88
181
  ```typescript
89
- // Apply a subscription tier to a company
90
- await os.subscriptions.create("pro", {
91
- companyId: company.id,
182
+ const contact = await os.contacts.create({
183
+ companyId: "comp_xxx",
184
+ email: "john@acme.com",
185
+ name: "John Doe",
186
+ role: "CEO",
187
+ isPrimary: true,
188
+ avatarUrl: "https://example.com/avatar.jpg",
189
+ metadata: { slackId: "U123" },
92
190
  })
93
191
 
94
- // With feature overrides
95
- await os.subscriptions.create("enterprise", {
96
- companyId: company.id,
97
- features: {
98
- ai_credits: { included_quantity: 1000 },
99
- },
192
+ if (contact.isNew) {
193
+ console.log("Created new contact")
194
+ } else {
195
+ console.log("Updated existing contact")
196
+ }
197
+ ```
198
+
199
+ #### `contacts.bulkUpsert(input)`
200
+
201
+ Bulk create or update multiple contacts. **Recommended for onboarding.**
202
+
203
+ ```typescript
204
+ const result = await os.contacts.bulkUpsert({
205
+ companyId: "comp_xxx",
206
+ contacts: [
207
+ { email: "ceo@acme.com", name: "Alice", role: "CEO", isPrimary: true },
208
+ { email: "cto@acme.com", name: "Bob", role: "CTO" },
209
+ { email: "dev@acme.com", name: "Charlie", role: "Engineer" },
210
+ ],
100
211
  })
101
212
 
102
- // List available tiers
103
- const tiers = await os.tiers.list()
213
+ console.log(`Created: ${result.created}, Updated: ${result.updated}`)
214
+ // result.contacts contains individual results with IDs
215
+ ```
216
+
217
+ #### `contacts.list(companyId, primaryOnly?)`
104
218
 
105
- // Get tier details
106
- const proTier = await os.tiers.get("pro")
219
+ List contacts for a company.
220
+
221
+ ```typescript
222
+ // Get all contacts
223
+ const contacts = await os.contacts.list("comp_xxx")
224
+
225
+ // Get only the primary contact
226
+ const [primary] = await os.contacts.list("comp_xxx", true)
107
227
  ```
108
228
 
109
- ### Feature Access
229
+ ---
230
+
231
+ ### Features
232
+
233
+ Check and manage feature access for companies.
234
+
235
+ #### `features.check(input)`
236
+
237
+ Check if a company has access to a specific feature.
110
238
 
111
239
  ```typescript
112
- // Check if company has access to a feature
113
240
  const access = await os.features.check({
114
- companyId: company.id,
115
- featureCode: "whatsapp_messages",
241
+ companyId: "comp_xxx",
242
+ featureCode: "ai_credit",
116
243
  })
117
244
 
118
- if (access.hasAccess) {
119
- console.log(`Limit: ${access.limit}, Used: ${access.currentUsage}`)
245
+ console.log({
246
+ enabled: access.isEnabled, // Feature is turned on
247
+ canUse: access.canUse, // Can use right now (not at limit)
248
+ hasQuantity: access.hasQuantity, // Is a quantity-based feature
249
+ usage: access.currentUsage, // Current usage count
250
+ limit: access.quantityLimit, // Max allowed (null = unlimited)
251
+ included: access.includedQuantity, // Free tier before billing
252
+ remaining: access.remaining, // How many left (null = unlimited)
253
+ })
254
+
255
+ if (!access.canUse) {
256
+ throw new Error("Upgrade required")
120
257
  }
258
+ ```
259
+
260
+ #### `features.listAccess(companyId)`
261
+
262
+ List all features and their access status for a company.
121
263
 
122
- // List all feature access for a company
123
- const allAccess = await os.features.listAccess(company.id)
264
+ ```typescript
265
+ const features = await os.features.listAccess("comp_xxx")
266
+
267
+ for (const f of features) {
268
+ console.log(`${f.featureName}: ${f.isEnabled ? "✓" : "✗"}`)
269
+ if (f.quantityLimit) {
270
+ console.log(` Usage: ${f.currentUsage} / ${f.quantityLimit}`)
271
+ }
272
+ }
124
273
  ```
125
274
 
126
- ### Billing & Usage Tracking
275
+ #### `features.setAccess(input)`
276
+
277
+ Update feature access with **partial config merge**.
127
278
 
128
279
  ```typescript
129
- // Track usage for a feature
130
- await os.billing.trackUsage({
131
- companyId: company.id,
132
- featureCode: "ai_credits",
280
+ // Update by company ID
281
+ await os.features.setAccess({
282
+ companyId: "comp_xxx",
283
+ featureCode: "ai_credit",
284
+ isEnabled: true,
285
+ quantityLimit: 1000,
286
+ includedQuantity: 100,
287
+ config: {
288
+ model: "gpt-4",
289
+ maxTokens: 4096,
290
+ },
291
+ source: "manual", // 'subscription' | 'manual' | 'trial' | 'api'
292
+ validUntil: new Date("2025-12-31"), // null for no expiration
293
+ })
294
+
295
+ // Update by external org ID
296
+ await os.features.setAccess({
297
+ externalOrgId: "org_abc123",
298
+ featureCode: "team_seats",
299
+ config: { maxSeats: 50 }, // Only updates maxSeats, preserves other config
300
+ })
301
+ ```
302
+
303
+ #### `features.getAccessByExternalOrgId(externalOrgId)`
304
+
305
+ Get all features using your product's external org ID.
306
+
307
+ ```typescript
308
+ const result = await os.features.getAccessByExternalOrgId("org_abc123")
309
+
310
+ console.log(`Company: ${result.companyId}`)
311
+ for (const f of result.features) {
312
+ console.log(`${f.featureCode}: ${f.isEnabled}`)
313
+ console.log(" Config:", f.configValues)
314
+ }
315
+ ```
316
+
317
+ #### `features.checkByExternalOrgId(externalOrgId, featureCode)`
318
+
319
+ Check a specific feature using your external org ID.
320
+
321
+ ```typescript
322
+ const access = await os.features.checkByExternalOrgId("org_abc123", "ai_credit")
323
+
324
+ if (access.canUse) {
325
+ console.log(`Remaining: ${access.remaining}`)
326
+ }
327
+ ```
328
+
329
+ ---
330
+
331
+ ### Billing
332
+
333
+ Track usage, claim subscriptions, and manage billing.
334
+
335
+ #### `billing.trackUsage(input)`
336
+
337
+ Track usage for a metered feature.
338
+
339
+ ```typescript
340
+ const result = await os.billing.trackUsage({
341
+ companyId: "comp_xxx",
342
+ featureCode: "ai_credit",
133
343
  quantity: 5,
134
- idempotencyKey: "unique-request-id", // Optional, for deduplication
135
- metadata: { model: "gpt-4" }, // Optional
344
+ idempotencyKey: `request_${requestId}`, // Prevents double-counting
345
+ metadata: { model: "gpt-4", tokens: 1500 },
346
+ })
347
+
348
+ console.log({
349
+ currentUsage: result.currentUsage,
350
+ remaining: result.remaining,
351
+ billable: result.billableQuantity, // Amount beyond included tier
352
+ isAtLimit: result.isAtLimit,
136
353
  })
137
354
  ```
138
355
 
139
- ### Manual Linking
356
+ #### `billing.claimCheckout(input)`
357
+
358
+ Claim a subscription from a completed Stripe Checkout Session.
359
+
360
+ ```typescript
361
+ // In your Stripe checkout success handler
362
+ const result = await os.billing.claimCheckout({
363
+ companyId: "comp_xxx",
364
+ checkoutSessionId: "cs_test_xxx", // From Stripe callback
365
+ })
366
+
367
+ if (result.alreadyClaimed) {
368
+ console.log("Subscription was already claimed")
369
+ } else {
370
+ console.log(`Claimed subscription: ${result.subscriptionId}`)
371
+ }
372
+ ```
373
+
374
+ #### `billing.linkSubscription(input)`
375
+
376
+ Link an existing Stripe subscription to a company.
377
+
378
+ ```typescript
379
+ // When you create subscriptions on YOUR Stripe account
380
+ const result = await os.billing.linkSubscription({
381
+ companyId: "comp_xxx",
382
+ stripeSubscriptionId: "sub_xxx",
383
+ stripeCustomerId: "cus_xxx", // Optional: fetched from subscription if omitted
384
+ tierCode: "pro", // Apply tier features
385
+ displayName: "Pro Plan",
386
+ })
387
+
388
+ console.log({
389
+ subscriptionId: result.subscriptionId,
390
+ isNew: !result.alreadyLinked,
391
+ featuresApplied: result.featuresApplied,
392
+ })
393
+ ```
394
+
395
+ #### `billing.getStatus(companyId)`
396
+
397
+ Get billing status overview for a company.
398
+
399
+ ```typescript
400
+ const status = await os.billing.getStatus("comp_xxx")
401
+
402
+ console.log({
403
+ hasBilling: status.hasBillingProfile,
404
+ subscriptions: status.activeSubscriptions.length,
405
+ features: status.enabledFeaturesCount,
406
+ })
407
+
408
+ // Check active tier
409
+ if (status.activeSubscriptions[0]?.tier) {
410
+ console.log(`Current tier: ${status.activeSubscriptions[0].tier.name}`)
411
+ }
412
+ ```
413
+
414
+ ---
415
+
416
+ ### Subscriptions & Tiers
417
+
418
+ Manage subscription tiers and apply features.
419
+
420
+ #### `tiers.list()`
421
+
422
+ List all available tiers for your product.
423
+
424
+ ```typescript
425
+ const tiers = await os.tiers.list()
426
+
427
+ for (const tier of tiers) {
428
+ console.log(`${tier.name} (${tier.code})`)
429
+ }
430
+ // ['Free', 'Pro', 'Enterprise']
431
+ ```
432
+
433
+ #### `tiers.get(tierCode)`
434
+
435
+ Get detailed tier information including Stripe prices.
436
+
437
+ ```typescript
438
+ const tier = await os.tiers.get("pro")
439
+
440
+ // Check base fee
441
+ if (tier.baseFee) {
442
+ console.log(`Base fee: ${tier.baseFee.stripe.unitAmount / 100}€/mo`)
443
+ console.log(`Price ID: ${tier.baseFee.stripe.priceId}`)
444
+ }
445
+
446
+ // Access features
447
+ for (const feature of tier.features) {
448
+ console.log(`${feature.code}:`, feature.configValues)
449
+ }
450
+
451
+ // Get all Stripe price IDs (base fee + feature prices)
452
+ console.log("Stripe prices:", tier.stripePriceIds)
453
+ // ['price_base', 'price_feature1', 'price_feature2']
454
+ ```
455
+
456
+ #### `tiers.apply(tierCode, options)`
457
+
458
+ Apply a tier's features to a company (without Stripe).
459
+
460
+ ```typescript
461
+ // Apply free tier
462
+ await os.tiers.apply("free", {
463
+ companyId: "comp_xxx",
464
+ })
465
+
466
+ // Apply with custom limits
467
+ await os.tiers.apply("trial", {
468
+ companyId: "comp_xxx",
469
+ features: {
470
+ ai_credit: { included_quantity: 100 },
471
+ seats: { max_quantity: 10 },
472
+ },
473
+ })
474
+ ```
475
+
476
+ #### `subscriptions.create(tierCode, options)`
477
+
478
+ Alias for `tiers.apply()` — apply a subscription tier.
479
+
480
+ ```typescript
481
+ await os.subscriptions.create("pro", {
482
+ companyId: "comp_xxx",
483
+ features: {
484
+ ai_credit: { included_quantity: 500 },
485
+ },
486
+ })
487
+ ```
488
+
489
+ ---
490
+
491
+ ### Links
492
+
493
+ Manually manage product links.
494
+
495
+ #### `links.create(input)`
496
+
497
+ Create a manual link between your external org ID and an existing company.
140
498
 
141
499
  ```typescript
142
- // Link an external org ID to an existing company
143
500
  await os.links.create({
501
+ companyId: "comp_xxx",
502
+ externalOrgId: "org_abc123",
503
+ label: "Store Vienna", // Optional: display label
504
+ externalUserId: "user_xyz", // Optional: user who created the link
505
+ })
506
+ ```
507
+
508
+ #### `links.reset(options?)`
509
+
510
+ Delete all product links for your product. Use before re-importing.
511
+
512
+ ```typescript
513
+ // Basic reset (links only)
514
+ const result = await os.links.reset()
515
+ console.log(`Deleted ${result.linksDeleted} links`)
516
+
517
+ // Full reset (links + contacts + MRR)
518
+ const result = await os.links.reset({
519
+ deleteContacts: true,
520
+ resetMrr: true,
521
+ })
522
+ console.log(
523
+ `Deleted ${result.linksDeleted} links, ${result.contactsDeleted} contacts`
524
+ )
525
+ ```
526
+
527
+ ---
528
+
529
+ ### Onboarding
530
+
531
+ Complete customer onboarding in a single API call.
532
+
533
+ The `onboard()` method handles:
534
+
535
+ 1. Smart company matching/creation
536
+ 2. Product linking
537
+ 3. Subscription claiming/linking
538
+ 4. Tier feature application
539
+
540
+ #### Basic Usage
541
+
542
+ ```typescript
543
+ const result = await os.onboard({
544
+ externalOrgId: "org_abc123", // Your product's org ID (required)
545
+ hints: {
546
+ companyName: "Acme Inc",
547
+ domain: "acme.com",
548
+ emails: ["john@acme.com"],
549
+ },
550
+ tierCode: "free",
551
+ contactEmail: "admin@acme.com",
552
+ contactName: "John Doe",
553
+ createdAt: "2024-01-15T10:30:00.000Z", // Optional: historical date
554
+ })
555
+
556
+ console.log({
557
+ companyId: result.companyId,
558
+ companyName: result.companyName,
559
+ linkStatus: result.linkStatus, // 'created' | 'linked' | 'already_linked'
560
+ tierApplied: result.billingStatus?.tierApplied,
561
+ })
562
+ ```
563
+
564
+ #### Scenario 1: User Completed Stripe Checkout
565
+
566
+ ```typescript
567
+ const result = await os.onboard({
568
+ externalOrgId: "org_abc123",
569
+ hints: { companyName: "Acme Inc", domain: "acme.com" },
570
+ checkoutSessionId: "cs_test_xxx", // From Stripe checkout callback
571
+ })
572
+
573
+ // result.billingStatus.claimed = true
574
+ // result.billingStatus.subscriptionId = 'sub_xxx'
575
+ ```
576
+
577
+ #### Scenario 2: Free Tier Signup
578
+
579
+ ```typescript
580
+ const result = await os.onboard({
581
+ externalOrgId: "org_abc123",
582
+ hints: { companyName: "Acme Inc" },
583
+ tierCode: "free",
584
+ contactEmail: "admin@acme.com",
585
+ })
586
+
587
+ // result.billingStatus.tierApplied = true
588
+ ```
589
+
590
+ #### Scenario 3: You Create Stripe Subscription
591
+
592
+ ```typescript
593
+ // 1. Get tier prices
594
+ const tier = await os.tiers.get("pro")
595
+
596
+ // 2. Create subscription on YOUR Stripe account
597
+ const stripeSub = await stripe.subscriptions.create({
598
+ customer: stripeCustomerId,
599
+ items: tier.stripePriceIds.map((price) => ({ price })),
600
+ })
601
+
602
+ // 3. Onboard with the subscription
603
+ const result = await os.onboard({
604
+ externalOrgId: "org_abc123",
605
+ hints: { companyName: "Acme Inc" },
606
+ stripeSubscriptionId: stripeSub.id,
607
+ stripeCustomerId: stripeCustomerId,
608
+ tierCode: "pro",
609
+ })
610
+
611
+ // result.billingStatus.linked = true
612
+ // result.billingStatus.tierApplied = true
613
+ ```
614
+
615
+ #### Handling Existing Customers
616
+
617
+ ```typescript
618
+ const result = await os.onboard({
619
+ externalOrgId: "org_abc123",
620
+ hints: { companyName: "Acme Inc" },
621
+ })
622
+
623
+ switch (result.linkStatus) {
624
+ case "already_linked":
625
+ console.log("Welcome back!")
626
+ break
627
+ case "linked":
628
+ console.log("Found existing company, linked!")
629
+ break
630
+ case "created":
631
+ console.log("New company created!")
632
+ break
633
+ }
634
+ ```
635
+
636
+ ---
637
+
638
+ ## Common Patterns
639
+
640
+ ### Feature Gating
641
+
642
+ ```typescript
643
+ async function checkFeatureAccess(orgId: string, feature: string) {
644
+ const access = await os.features.checkByExternalOrgId(orgId, feature)
645
+
646
+ if (!access.isEnabled) {
647
+ throw new Error(`Feature ${feature} is not available on your plan`)
648
+ }
649
+
650
+ if (!access.canUse) {
651
+ throw new Error(`Usage limit reached for ${feature}. Please upgrade.`)
652
+ }
653
+
654
+ return access
655
+ }
656
+
657
+ // Usage
658
+ await checkFeatureAccess("org_abc123", "ai_credit")
659
+ await performAIOperation()
660
+ await os.billing.trackUsage({
144
661
  companyId: company.id,
145
- externalOrgId: "org_new_123",
146
- externalUserId: "user_456", // Optional
662
+ featureCode: "ai_credit",
663
+ quantity: 1,
147
664
  })
148
665
  ```
149
666
 
150
- ## Configuration
667
+ ### Stripe Subscription Flow
668
+
669
+ ```typescript
670
+ // 1. User selects a plan → Get tier prices
671
+ const tier = await os.tiers.get("pro")
672
+
673
+ // 2. Create Stripe Checkout Session with tier prices
674
+ const session = await stripe.checkout.sessions.create({
675
+ customer: customerId,
676
+ line_items: tier.stripePriceIds.map((price) => ({
677
+ price,
678
+ quantity: 1,
679
+ })),
680
+ success_url: `${baseUrl}/success?session_id={CHECKOUT_SESSION_ID}`,
681
+ cancel_url: `${baseUrl}/cancel`,
682
+ })
683
+
684
+ // 3. After checkout completes → Claim subscription
685
+ const result = await os.onboard({
686
+ externalOrgId: "org_abc123",
687
+ checkoutSessionId: session.id,
688
+ })
689
+ ```
151
690
 
152
- | Option | Type | Required | Default | Description |
153
- | --------- | -------- | -------- | --------------------------------- | ------------------------------ |
154
- | `apiKey` | `string` | ✅ | - | API key from ChatarminOS admin |
155
- | `baseUrl` | `string` | - | `https://os.chatarmin.com/api/v1` | API endpoint |
691
+ ### Backfilling Historical Data
692
+
693
+ ```typescript
694
+ import { ChatarminOS } from "@chatarmin/os"
695
+ import historicalData from "./migration-data.json"
696
+
697
+ const os = new ChatarminOS({ apiKey: process.env.OS_API_KEY! })
698
+
699
+ for (const org of historicalData) {
700
+ // Create company with historical date
701
+ const result = await os.onboard({
702
+ externalOrgId: org.id,
703
+ hints: {
704
+ companyName: org.name,
705
+ domain: org.domain,
706
+ },
707
+ tierCode: org.plan,
708
+ contactEmail: org.adminEmail,
709
+ createdAt: org.createdAt, // Historical timestamp
710
+ })
711
+
712
+ // Sync contacts
713
+ await os.contacts.bulkUpsert({
714
+ companyId: result.companyId,
715
+ contacts: org.users.map((u) => ({
716
+ email: u.email,
717
+ name: u.name,
718
+ role: u.role,
719
+ })),
720
+ })
721
+ }
722
+ ```
723
+
724
+ ---
156
725
 
157
726
  ## TypeScript Support
158
727
 
159
- The SDK is fully typed. All methods return properly typed responses based on the ChatarminOS API schema.
728
+ The SDK is fully typed with comprehensive TypeScript definitions.
729
+
730
+ ### Importing Types
731
+
732
+ ```typescript
733
+ import { ChatarminOS } from "@chatarmin/os"
734
+ import type {
735
+ ChatarminOSConfig,
736
+ CreateCompanyInput,
737
+ SmartLinkInput,
738
+ SmartLinkResult,
739
+ ContactInput,
740
+ FeatureCheckInput,
741
+ FeatureSetAccessInput,
742
+ TrackUsageInput,
743
+ OnboardInput,
744
+ OnboardResult,
745
+ ApplyTierInput,
746
+ ClaimCheckoutInput,
747
+ LinkSubscriptionInput,
748
+ } from "@chatarmin/os"
749
+ ```
750
+
751
+ ### Router Types
752
+
753
+ ```typescript
754
+ import type { AppRouter } from "@chatarmin/os"
755
+ ```
756
+
757
+ ---
758
+
759
+ ## Configuration
760
+
761
+ | Option | Type | Required | Default | Description |
762
+ | --------- | -------- | -------- | --------------------------------- | --------------------------- |
763
+ | `apiKey` | `string` | ✅ | — | API key from OS admin panel |
764
+ | `baseUrl` | `string` | — | `https://os.chatarmin.com/api/v1` | API endpoint |
160
765
 
161
766
  ```typescript
162
- import type { AppRouter } from "@chatarmin/os-sdk"
767
+ const os = new ChatarminOS({
768
+ apiKey: process.env.OS_API_KEY!,
769
+ baseUrl: "https://os.chatarmin.com/api/v1", // Default
770
+ })
771
+
772
+ // Local development
773
+ const osLocal = new ChatarminOS({
774
+ apiKey: process.env.OS_API_KEY!,
775
+ baseUrl: "http://localhost:3001/api/v1",
776
+ })
163
777
  ```
164
778
 
779
+ ### Getting Your API Key
780
+
781
+ 1. Go to ChatarminOS → **Settings** → **Developers** → **API Keys**
782
+ 2. Click "Create API Key"
783
+ 3. Copy the key (format: `os_sk_xxxxxxxxxxxx`)
784
+ 4. Store in environment variables
785
+
786
+ ---
787
+
788
+ ## Error Handling
789
+
790
+ The SDK uses tRPC under the hood. Errors include structured information:
791
+
792
+ ```typescript
793
+ try {
794
+ await os.billing.trackUsage({
795
+ companyId: "comp_xxx",
796
+ featureCode: "ai_credit",
797
+ quantity: 1000,
798
+ })
799
+ } catch (error) {
800
+ if (error instanceof TRPCClientError) {
801
+ console.error("Code:", error.data?.code) // e.g., 'FORBIDDEN'
802
+ console.error("Message:", error.message) // Human-readable message
803
+ console.error("HTTP Status:", error.data?.httpStatus)
804
+ }
805
+ }
806
+ ```
807
+
808
+ ### Common Error Codes
809
+
810
+ | Code | Description |
811
+ | --------------------- | -------------------------- |
812
+ | `UNAUTHORIZED` | Invalid or missing API key |
813
+ | `FORBIDDEN` | No access to this resource |
814
+ | `NOT_FOUND` | Company/feature not found |
815
+ | `BAD_REQUEST` | Invalid input parameters |
816
+ | `PRECONDITION_FAILED` | Usage limit exceeded |
817
+
818
+ ---
819
+
820
+ ## Related Documentation
821
+
822
+ - **[QUICKSTART.md](./QUICKSTART.md)** — Quick publishing & installation guide
823
+ - **[PUBLISHING.md](./PUBLISHING.md)** — Detailed publishing instructions
824
+ - **[test-sdk.ts](./test-sdk.ts)** — Complete usage examples
825
+
826
+ ---
827
+
828
+ ## Support
829
+
830
+ - **Email**: hello@chatarmin.com
831
+ - **Documentation**: https://os.chatarmin.com/docs
832
+
833
+ ---
834
+
165
835
  ## License
166
836
 
167
837
  MIT © Chatarmin GmbH
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@chatarmin/os",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Type-safe SDK for ChatarminOS - Customer & Subscription Management",
5
5
  "type": "module",
6
6
  "publishConfig": {
7
- "access": "restricted"
7
+ "access": "public"
8
8
  },
9
9
  "main": "./dist/index.js",
10
10
  "types": "./dist/index.d.ts",