@ecodrix/erix-api 1.4.3 → 1.5.0

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.
@@ -61,6 +61,1485 @@ declare class Agency extends APIResource {
61
61
  createStaff<T = any>(payload: any): Promise<T>;
62
62
  }
63
63
 
64
+ /**
65
+ * Adaptive Onboarding type definitions.
66
+ *
67
+ * These types mirror the Postgres data shapes from the
68
+ * `adaptive-onboarding-ai-persona` spec — see
69
+ * `.kiro/specs/adaptive-onboarding-ai-persona/design.md` §"Data Models" and
70
+ * §"Onboarding engine".
71
+ *
72
+ * NO RUNTIME CODE. Types only. Both saas (Next.js) and server (Express)
73
+ * import from here so the wire shape stays in lockstep.
74
+ */
75
+ /**
76
+ * Renderable step kinds. The saas selects the appropriate React component
77
+ * from `components/onboarding/steps/*` based on this discriminator.
78
+ */
79
+ type OnboardingStepType = "single_choice" | "multi_choice" | "text_input" | "ai_summary_confirm" | "feature_preview" | "completion";
80
+ /**
81
+ * One selectable answer option in a `single_choice` / `multi_choice` step.
82
+ */
83
+ interface OnboardingOption {
84
+ /** Internal value persisted in `session.answers[stepId]`. */
85
+ value: string;
86
+ /** Human-readable label shown to the user. */
87
+ label: string;
88
+ /** Optional short description rendered under the label. */
89
+ description?: string;
90
+ /** Optional icon identifier (Lucide name, image URL, or emoji). */
91
+ icon?: string;
92
+ }
93
+ /**
94
+ * Declares which persona field a step's answer writes to.
95
+ *
96
+ * Engine flow: when the step is answered, the value is written to
97
+ * `session.draftPersona.<targetField>`, optionally passed through a named
98
+ * transformation. On `complete()` the draft persona is upserted to
99
+ * `ecodrix_ai_personas`.
100
+ */
101
+ interface OnboardingMemoryMapping {
102
+ /**
103
+ * Dot-path into the persona document, e.g.
104
+ * `"identity.nicheSubtype"` or `"behaviorRules.outreachTone"`.
105
+ */
106
+ targetField: string;
107
+ /**
108
+ * Optional named transformation applied to the raw answer before write.
109
+ * Engine-defined (`"map_to_niche_config"`, `"lowercase"`, `"split_csv"`,
110
+ * etc.).
111
+ */
112
+ transformation?: string;
113
+ }
114
+ /**
115
+ * A single step in an `OnboardingConfig`.
116
+ *
117
+ * Branching is via `showIf` / `skipIf` JsonLogic expressions evaluated
118
+ * against `session.answers`. The engine never reaches an undefined step —
119
+ * a missing `nextStepId` terminates the flow.
120
+ */
121
+ interface OnboardingStep {
122
+ /** Stable identifier referenced by branch expressions and analytics. */
123
+ stepId: string;
124
+ /** Title rendered above the question. */
125
+ title: string;
126
+ /** Optional secondary line under the title. */
127
+ subtitle?: string;
128
+ /** Discriminator — drives which renderer the saas picks. */
129
+ type: OnboardingStepType;
130
+ /**
131
+ * JsonLogic expression. When present and false, the engine skips this
132
+ * step (does not render). Evaluated against `session.answers`.
133
+ */
134
+ showIf?: unknown;
135
+ /**
136
+ * JsonLogic expression. When present and true, the engine skips this
137
+ * step. Evaluated alongside / after `showIf`.
138
+ */
139
+ skipIf?: unknown;
140
+ /** The actual question text shown to the user. */
141
+ question: string;
142
+ /** Optional helper / "Why we ask" tooltip text (REQ-018). */
143
+ helperText?: string;
144
+ /** Selectable options for `single_choice` / `multi_choice` types. */
145
+ options?: OnboardingOption[];
146
+ /** Where the answer lands in the persona draft. */
147
+ memoryMapping: OnboardingMemoryMapping;
148
+ /**
149
+ * Workspace module keys that this step's completion makes visible
150
+ * (drives `WorkspaceProfile.modules.<key>.locked = false`).
151
+ */
152
+ unlocks?: string[];
153
+ }
154
+ /**
155
+ * A versioned onboarding flow for one product. Stored as a row in
156
+ * `ecodrix_onboarding_configs` and as `niche_pack.onboarding_config`.
157
+ */
158
+ interface OnboardingConfig {
159
+ /** `"crm" | "laie" | "flow"` (string for forward-compat). */
160
+ productId: string;
161
+ /** Monotonic version string, e.g. `"crm-v1"`, `"crm-v2"`. */
162
+ version: string;
163
+ /** Ordered list of steps; engine walks these honoring branch logic. */
164
+ steps: OnboardingStep[];
165
+ }
166
+ /**
167
+ * Lifecycle status of an onboarding session row.
168
+ */
169
+ type OnboardingSessionStatus = "active" | "completed" | "abandoned" | "archived";
170
+ /**
171
+ * Persisted onboarding session — one per `(userId, productId)` until
172
+ * completed or abandoned. ISO strings for timestamps so the shape is
173
+ * JSON-safe over the wire.
174
+ */
175
+ interface OnboardingSession {
176
+ id: string;
177
+ orgId: string;
178
+ userId: string;
179
+ productId: string;
180
+ /** `<top_level>__<leaf>` slug, e.g. `"healthcare__clinic"`. May be null
181
+ * before the niche pick step. */
182
+ nichePackSlug: string | null;
183
+ /** Version of the `OnboardingConfig` driving this session. */
184
+ configVersion: string;
185
+ /** ID of the step the user is currently on, or null when finished. */
186
+ currentStepId: string | null;
187
+ /** `{ [stepId]: answerValue }` map persisted on every PATCH. */
188
+ answers: Record<string, unknown>;
189
+ /** Persona-shaped draft built incrementally from `answers` via memoryMapping. */
190
+ draftPersona: Record<string, unknown>;
191
+ status: OnboardingSessionStatus;
192
+ /** Optional A/B cohort, e.g. `"A"` / `"B"`. */
193
+ cohort: string | null;
194
+ /**
195
+ * Slug of the running experiment that drove the session's config
196
+ * resolution at start-time, or `null` when no experiment was active.
197
+ * Stamped by `POST /:productId/start` from the engine's
198
+ * `resolveSessionConfig` result so analytics can correlate sessions
199
+ * to experiments without re-resolving the cohort routing.
200
+ */
201
+ experimentSlug?: string | null;
202
+ /**
203
+ * `OnboardingConfig` version the session was rendered against — the
204
+ * concrete version chosen by the variant resolver (when an
205
+ * experiment ran) or the latest-published path. May differ from
206
+ * `configVersion` when the resolver picked a variant that maps to a
207
+ * different version than the latest published config.
208
+ */
209
+ variantConfigVersion?: string | null;
210
+ startedAt: string;
211
+ completedAt: string | null;
212
+ lastActiveAt: string;
213
+ }
214
+ /**
215
+ * Visibility + cosmetic configuration for one sidebar/workspace module.
216
+ */
217
+ interface WorkspaceModuleConfig {
218
+ /** Whether the module renders at all in the sidebar. */
219
+ enabled: boolean;
220
+ /** Niche-tuned label, e.g. `"Patients"` instead of `"Contacts"`. */
221
+ label: string;
222
+ /** When true, module renders with a lock icon + tooltip. */
223
+ locked: boolean;
224
+ /** Sort order within the sidebar (ascending). */
225
+ sortOrder: number;
226
+ /**
227
+ * Optional gate name; the saas calls a Server Action that returns
228
+ * whether the gate currently passes given persona state.
229
+ */
230
+ onboardingGate?: string;
231
+ }
232
+ /**
233
+ * Default pipeline produced by the workspace shaper. Mirrors the existing
234
+ * `erix_pipelines` / `erix_pipeline_stages` rows that are materialized.
235
+ */
236
+ interface WorkspaceDefaultPipeline {
237
+ name: string;
238
+ isDefault?: boolean;
239
+ stages: Array<{
240
+ name: string;
241
+ color?: string;
242
+ probability?: number;
243
+ isWon?: boolean;
244
+ isLost?: boolean;
245
+ order: number;
246
+ }>;
247
+ }
248
+ /**
249
+ * Default custom-field definition produced by the workspace shaper.
250
+ * Mirrors `erix_custom_fields` rows.
251
+ */
252
+ interface WorkspaceDefaultField {
253
+ key: string;
254
+ label: string;
255
+ type: string;
256
+ required?: boolean;
257
+ options?: string[] | Array<{
258
+ label: string;
259
+ value: string;
260
+ }>;
261
+ group?: string;
262
+ }
263
+ /**
264
+ * Default automation produced by the workspace shaper. Mirrors
265
+ * `erix_automation_rules` rows.
266
+ */
267
+ interface WorkspaceDefaultAutomation {
268
+ name: string;
269
+ trigger: string;
270
+ /** Free-form condition payload understood by the automation engine. */
271
+ conditions?: Record<string, unknown>;
272
+ /** Ordered list of action descriptors. */
273
+ actions: Array<Record<string, unknown>>;
274
+ enabled?: boolean;
275
+ }
276
+ /**
277
+ * Default dashboard layout — opaque grid descriptor consumed by the
278
+ * dashboard renderer.
279
+ */
280
+ interface WorkspaceDashboardLayout {
281
+ widgets: Array<Record<string, unknown>>;
282
+ [extra: string]: unknown;
283
+ }
284
+ /**
285
+ * The full per-product workspace shape produced by
286
+ * `workspaceShaper.produce(persona, nichePack)` and persisted at
287
+ * `ecodrix_organizations.service_config -> '<productId>' -> 'workspace'`.
288
+ */
289
+ interface WorkspaceProfile {
290
+ workspaceId: string;
291
+ productId: string;
292
+ /** Niche slug this profile was shaped for, e.g. `"agency__digital_marketing"`. */
293
+ niche: string;
294
+ modules: Record<string, WorkspaceModuleConfig>;
295
+ defaultPipelines: WorkspaceDefaultPipeline[];
296
+ defaultFields: WorkspaceDefaultField[];
297
+ defaultAutomations: WorkspaceDefaultAutomation[];
298
+ defaultDashboardLayout: WorkspaceDashboardLayout;
299
+ }
300
+ /**
301
+ * Org-wide onboarding flags under `service_config.global`.
302
+ */
303
+ interface ServiceConfigGlobal {
304
+ /** Product the user picked at signup. */
305
+ primaryProduct: "crm" | "laie" | "flow" | "not_sure";
306
+ /** Feature flag — when false, legacy 4-step wizard is used. */
307
+ useAdaptiveOnboarding: boolean;
308
+ /**
309
+ * A/B-test cohort assigned by `ensureOnboardingCohort` — a stable
310
+ * deterministic bucket in `[0, 99]` derived from `hash(userId)`.
311
+ * Once written, it is never reassigned.
312
+ */
313
+ onboardingCohort?: number;
314
+ }
315
+ /**
316
+ * Per-product onboarding state under `service_config.<productId>`.
317
+ *
318
+ * Drives the dashboard CTA logic (REQ-002):
319
+ * `onboardingComplete=true && enabledAt!=null` → "Open {Product}"
320
+ * `enabledAt!=null && onboardingComplete=false` → "Open {Product}" + banner
321
+ * `enabledAt==null` → "Enable {Product}"
322
+ */
323
+ interface ServiceConfigPerProduct {
324
+ onboardingComplete: boolean;
325
+ /** Last persisted step the user was on (for resumability). */
326
+ onboardingStep?: number | string;
327
+ /** Version of the `OnboardingConfig` that produced the persona. */
328
+ onboardingVersion?: string;
329
+ /** Niche slug, e.g. `"healthcare__clinic"`. */
330
+ niche?: string;
331
+ /** Mirrored from `ecodrix_ai_personas.confidence_score`. */
332
+ personaConfidence?: number;
333
+ /** Opt-in for confidence ≥ 70 auto-pilot features. */
334
+ autoPilotEnabled?: boolean;
335
+ /** ISO timestamp the user clicked "Enable {Product}" (REQ-002). */
336
+ enabledAt?: string;
337
+ /** Materialized workspace shape from the shaper. */
338
+ workspace?: WorkspaceProfile;
339
+ }
340
+ /**
341
+ * Full org-level `service_config` JSONB shape.
342
+ *
343
+ * The shape is open (it lives in JSONB), but every consumer should treat
344
+ * it as if it conforms to this interface and prefer named keys over
345
+ * arbitrary top-level extensions.
346
+ */
347
+ interface ServiceConfig {
348
+ global: ServiceConfigGlobal;
349
+ crm?: ServiceConfigPerProduct;
350
+ laie?: ServiceConfigPerProduct;
351
+ flow?: ServiceConfigPerProduct;
352
+ }
353
+ /**
354
+ * Status lifecycle for a niche pack version. Only `published` rows are
355
+ * served by the runtime engine; `draft` is admin-authoring only and
356
+ * `archived` keeps history accessible to already-bootstrapped personas.
357
+ */
358
+ type NichePackStatus = "draft" | "published" | "archived";
359
+ /**
360
+ * Loose shape of `researchConfig` — the LAIE-side blob that drives lead
361
+ * validation, outreach style, and (now, task 3.1.5) reinforcement
362
+ * signal enrichment.
363
+ *
364
+ * The shape stays open (`[extra: string]: unknown`) because seeded
365
+ * packs and admin-edited packs can carry additional product-specific
366
+ * fields the platform doesn't model strictly.
367
+ *
368
+ * Two naming conventions are supported for the same arrays:
369
+ *
370
+ * - `validation` / `disqualification` — the literal
371
+ * shape the seeded LAIE niche packs ship today.
372
+ * - `validationSignals` / `disqualificationSignals` — the spec's
373
+ * forward-looking name (used by the persona derivation layer).
374
+ *
375
+ * Consumers should accept either, prefer the explicit `*Signals` form
376
+ * when both are present, and fall back to the legacy short name.
377
+ */
378
+ interface NichePackResearchConfig {
379
+ validation?: string[];
380
+ disqualification?: string[];
381
+ validationSignals?: string[];
382
+ disqualificationSignals?: string[];
383
+ /**
384
+ * Per-niche follow-up question templates the persona self-review
385
+ * Loop 1 (REQ-016, confidence-gap detection) fires when a given
386
+ * persona subfield is judged the weakest signal.
387
+ *
388
+ * Keys are persona dot-paths (e.g. `"icp.industryTargets"`,
389
+ * `"behaviorRules.outreachTone"`); values are free-form question
390
+ * strings. The placeholder `{niche}` is interpolated with
391
+ * `persona.identity.nicheSubtype` at fire time (falls back to
392
+ * `"your niche"` when the persona has no niche set).
393
+ *
394
+ * When a key is missing the loop falls back to the platform-wide
395
+ * default in `defaultFollowupTemplate(targetField)`.
396
+ */
397
+ followupTemplates?: Record<string, string>;
398
+ [extra: string]: unknown;
399
+ }
400
+ /**
401
+ * Lifecycle status for an `OnboardingExperiment`. Only `running`
402
+ * experiments influence the runtime engine's variant routing
403
+ * (task 4.2.3) — `paused` keeps the row visible without affecting
404
+ * cohort assignment, and `completed` is a terminal state.
405
+ */
406
+ type OnboardingExperimentStatus = "draft" | "running" | "paused" | "completed";
407
+ /**
408
+ * Half-open cohort bucket range. `from` is INCLUSIVE, `to` is
409
+ * EXCLUSIVE. Both endpoints must satisfy `0 <= from < to <= 100` and
410
+ * align with the bucket assignment in
411
+ * `service_config.global.onboardingCohort` (a stable integer in
412
+ * `[0, 99]`).
413
+ */
414
+ interface OnboardingCohortRange {
415
+ /** Inclusive lower bound. */
416
+ from: number;
417
+ /** Exclusive upper bound. */
418
+ to: number;
419
+ }
420
+ /**
421
+ * One variant in an `OnboardingExperiment`.
422
+ *
423
+ * Variant ranges may not overlap (the admin-side validator rejects
424
+ * such payloads); when a user's cohort falls in a gap (or no variant
425
+ * is defined), the engine serves `controlConfigVersion` instead.
426
+ */
427
+ interface OnboardingExperimentVariant {
428
+ /** Cohort range claimed by this variant — see `OnboardingCohortRange`. */
429
+ cohortRange: OnboardingCohortRange;
430
+ /**
431
+ * Versioned `OnboardingConfig` slug, e.g. `"crm@1.0.0"` or any
432
+ * value the engine's config loader resolves.
433
+ */
434
+ configVersion: string;
435
+ /** Optional sampling weight for analytics; defaults to 1. */
436
+ weight?: number;
437
+ /** Optional human-readable label, e.g. `"treatment"`. */
438
+ label?: string;
439
+ }
440
+ /**
441
+ * Full experiment row. Mirrors the Postgres
442
+ * `ecodrix_onboarding_experiments` table (see
443
+ * `server/src/shared/db/schema/platform/onboarding-experiments.ts`).
444
+ */
445
+ interface OnboardingExperiment {
446
+ /** UUID primary key — opaque to SDK callers. */
447
+ id: string;
448
+ /** Admin-facing handle, unique platform-wide. */
449
+ slug: string;
450
+ /** Product family this experiment targets. */
451
+ productId: "crm" | "laie" | "flow" | string;
452
+ /** Display name for admin surfaces. */
453
+ name: string;
454
+ /** Optional long-form description. */
455
+ description?: string | null;
456
+ status: OnboardingExperimentStatus;
457
+ /** Cohort routing table — empty array for fresh drafts. */
458
+ variants: OnboardingExperimentVariant[];
459
+ /**
460
+ * Fallback config version served when no variant matches. Null
461
+ * defers to the product's latest published `OnboardingConfig` row.
462
+ */
463
+ controlConfigVersion?: string | null;
464
+ /** ISO 8601 timestamps — server clock. */
465
+ createdAt: string;
466
+ updatedAt: string;
467
+ startedAt?: string | null;
468
+ endedAt?: string | null;
469
+ /** Admin staff id (UUID) of the authoring user, when known. */
470
+ createdBy?: string | null;
471
+ }
472
+ /**
473
+ * Versioned bundle of `OnboardingConfig` + `WorkspaceProfile` +
474
+ * `ResearchConfig` for a single niche slug. One row per `(slug,
475
+ * version)` in `ecodrix_niche_packs`.
476
+ *
477
+ * Niche packs are PLATFORM-WIDE reference data — they are NOT
478
+ * tenant-scoped. Per-tenant assignment lives at
479
+ * `ecodrix_organizations.service_config -> '<productId>' -> 'niche'`.
480
+ */
481
+ interface NichePack {
482
+ /** Niche slug, e.g. `"healthcare__clinic"`, `"designer__ui_ux"`. */
483
+ slug: string;
484
+ /** Product family this pack targets. */
485
+ productId: "crm" | "laie" | "flow" | string;
486
+ /** Semantic version, e.g. `"1.0.0"`. */
487
+ version: string;
488
+ /** Lifecycle status. Only `"published"` is served at runtime. */
489
+ status: NichePackStatus;
490
+ /** Optional — full branching onboarding flow. */
491
+ onboardingConfig?: OnboardingConfig;
492
+ /** Optional — default workspace shape. */
493
+ workspaceProfile?: WorkspaceProfile;
494
+ /** Optional — research / validation config (LAIE only today). */
495
+ researchConfig?: NichePackResearchConfig;
496
+ }
497
+
498
+ /**
499
+ * Identity block — who the user is, what they sell, where they operate.
500
+ * Stored at `ecodrix_ai_personas.identity` (JSONB).
501
+ */
502
+ interface PersonaIdentity {
503
+ /** Top-level taxonomy slug, e.g. `"designer"`, `"healthcare"`. */
504
+ niche?: string;
505
+ /** Leaf slug, e.g. `"designer__ui_ux"`. Partition key for niche packs. */
506
+ nicheSubtype?: string;
507
+ /** Free-form business model descriptor, e.g. `"freelancer"`, `"agency"`. */
508
+ businessModel?: string;
509
+ /** `"solo" | "2-5" | "6-20" | "20+"` (string for forward-compat). */
510
+ teamSize?: string;
511
+ /** Revenue stage, e.g. `"pre-revenue"`, `"<10k_mrr"`, `">100k_mrr"`. */
512
+ revenueStage?: string;
513
+ /** Geographies the user serves, ISO-2 country codes or freeform region names. */
514
+ geographyFocus?: string[];
515
+ /** Preferred operating language(s). */
516
+ languages?: string[];
517
+ [extra: string]: unknown;
518
+ }
519
+ /**
520
+ * Behavior rules — how the AI should act on the user's behalf.
521
+ * Stored at `ecodrix_ai_personas.behavior_rules` (JSONB).
522
+ */
523
+ interface BehaviorRules {
524
+ /** Tone, e.g. `"warm"`, `"direct"`, `"playful"`. */
525
+ outreachTone?: string;
526
+ /** Style, e.g. `"audit_first"`, `"value_first"`, `"roi_data_first"`. */
527
+ outreachStyle?: string;
528
+ /** Cadence preset name or descriptor. */
529
+ outreachCadence?: string;
530
+ /** Hard rules — strings/phrases the AI must never use. */
531
+ avoidPatterns?: string[];
532
+ /** Signals that should immediately validate a lead. */
533
+ validationSignals?: string[];
534
+ /** Signals that should immediately disqualify a lead. */
535
+ disqualificationSignals?: string[];
536
+ /**
537
+ * Auto-derived rules from the pattern-generalisation loop (REQ-016 §3),
538
+ * each carrying a `confidence` weight.
539
+ */
540
+ derivedRules?: Array<{
541
+ expression: string;
542
+ confidence: number;
543
+ derivedAt?: string;
544
+ }>;
545
+ /**
546
+ * Bookkeeping for the pattern-generalisation loop (REQ-016 §3,
547
+ * Task 3.5.3.1). The worker uses `lastClusterAt` to short-circuit
548
+ * Loop 3 until the persona's `totalReinforcement` crosses a new
549
+ * 50-event boundary since the previous derived-rule write.
550
+ */
551
+ derivedRulesMeta?: {
552
+ /** `learningMemory.totalReinforcement` at the time of the last
553
+ * derived-rule write. */
554
+ lastClusterAt?: number;
555
+ };
556
+ [extra: string]: unknown;
557
+ }
558
+ /**
559
+ * Vocabulary — phrases the AI should reuse for resonance.
560
+ * Stored at `ecodrix_ai_personas.vocabulary` (JSONB).
561
+ */
562
+ interface Vocabulary {
563
+ clientPainPoints?: string[];
564
+ serviceValueProps?: string[];
565
+ proofStatements?: string[];
566
+ signaturePhrases?: string[];
567
+ [extra: string]: unknown;
568
+ }
569
+ /**
570
+ * Ideal Customer Profile. Stored at `ecodrix_ai_personas.icp` (JSONB).
571
+ */
572
+ interface ICP {
573
+ industryTargets?: string[];
574
+ decisionMakerRole?: string[];
575
+ budgetRange?: {
576
+ min?: number;
577
+ max?: number;
578
+ currency?: string;
579
+ };
580
+ geographyTargets?: string[];
581
+ techStack?: string[];
582
+ fundingStages?: string[];
583
+ companySize?: string[];
584
+ [extra: string]: unknown;
585
+ }
586
+ /**
587
+ * One observed lead pattern — recorded on approve / reject so the
588
+ * confidence and pattern-generalisation loops can converge over time.
589
+ */
590
+ interface LeadPattern {
591
+ /** UUID of the originating reinforcement event (for idempotency). */
592
+ eventId: string;
593
+ /** Signal name, e.g. `"funding_stage"`, `"company_size"`. */
594
+ signal: string;
595
+ /** Observed value, e.g. `"series_a"`, `"1-10"`. */
596
+ value: string;
597
+ /** Soft weight in [0, 1]; older patterns decay. */
598
+ weight: number;
599
+ createdAt: string;
600
+ }
601
+ /**
602
+ * One captured edit of an AI-generated outreach message — feeds the
603
+ * outreach-style-drift detection loop (REQ-016 §4).
604
+ */
605
+ interface OutreachEdit {
606
+ eventId: string;
607
+ outreachId: string;
608
+ /** RFC 6902 JSON Patch describing the user's edit. */
609
+ diff: Array<Record<string, unknown>>;
610
+ /** Optional categorisation supplied by the user. */
611
+ reason?: string;
612
+ createdAt: string;
613
+ }
614
+ /**
615
+ * One user-issued correction of an AI assumption (REQ-007). Surfaced via
616
+ * the inline "Not quite" buttons on persona-displayed assumptions.
617
+ */
618
+ interface Correction {
619
+ eventId: string;
620
+ /** Dot-path to the corrected persona field, e.g. `"icp.industryTargets"`. */
621
+ field: string;
622
+ oldValue: unknown;
623
+ newValue: unknown;
624
+ createdAt: string;
625
+ }
626
+ /**
627
+ * One captured user-action against an AI suggestion surface — the
628
+ * granular audit trail that powers REQ-007 reinforcement learning AND
629
+ * REQ-012 audit-grade memory for every accept / reject the user
630
+ * issues at any AI suggestion site (CRM follow-up, LAIE outreach
631
+ * accept/regenerate, lead-score narrative thumbs, dashboard
632
+ * next-action, …).
633
+ *
634
+ * Stored as `learningMemory.acceptedSuggestionEvents[]` /
635
+ * `learningMemory.rejectedSuggestionEvents[]` (Task 3.1.4). The
636
+ * counters `acceptedSuggestions` / `rejectedSuggestions` continue to
637
+ * carry the cumulative totals so existing confidence math stays
638
+ * untouched; the per-event arrays are the new audit log.
639
+ */
640
+ interface SuggestionEvent {
641
+ /** Idempotency key — the worker dedupes on this. */
642
+ eventId: string;
643
+ /** Stable id of the suggestion the user acted on. */
644
+ suggestionId: string;
645
+ /**
646
+ * Suggestion taxonomy slug, e.g. `"follow_up_message"`,
647
+ * `"lead_score_narrative"`, `"outreach_subject"`, `"next_action"`,
648
+ * `"inbox_reply"`. Used by the pattern-generalisation loop to bucket
649
+ * accept/reject ratios per suggestion type.
650
+ */
651
+ suggestionType: string;
652
+ /**
653
+ * UI surface the user acted from, e.g. `"crm.deal.followup"`,
654
+ * `"saas.inbox.reply"`, `"laie.lead.score"`. Free-form but
655
+ * conventionally a dot-path under `<product>.<area>.<surface>`.
656
+ */
657
+ surface: string;
658
+ /**
659
+ * Surface-specific extra context (`dealId`, `leadId`, `conversationId`,
660
+ * etc) so the audit trail can correlate the action back to the
661
+ * originating row without bloating the top-level shape. Optional.
662
+ */
663
+ productContext?: Record<string, unknown>;
664
+ /** Optional rejection reason (`rejected` arrays only). */
665
+ rejectionReason?: string;
666
+ createdAt: string;
667
+ }
668
+ /**
669
+ * One captured `ai_outreach_sent` event — a row recorded every time
670
+ * an AI-generated outreach actually goes out. Pairs with
671
+ * {@link OutreachEdit} as the denominator side of the rolling
672
+ * edit-ratio that drives REQ-016 §4 (outreach style drift).
673
+ *
674
+ * Stored at `learningMemory.aiOutreachSent[]`. The writer caps the
675
+ * array at 500 entries (oldest rolled off) — same blast-radius
676
+ * pattern as `outreachEditHistory` to keep the JSONB blob bounded.
677
+ */
678
+ interface OutreachSent {
679
+ /** ISO 8601. The detector's window predicate runs on this. */
680
+ createdAt: string;
681
+ /** Optional id of the outreach row this corresponds to. */
682
+ outreachId?: string;
683
+ /**
684
+ * Originating product. Optional — recorded for telemetry / future
685
+ * per-product drift breakdowns. Free-form so non-`crm | laie | flow`
686
+ * surfaces can also log here without a discriminated-union update.
687
+ */
688
+ productId?: string;
689
+ /** Optional channel descriptor, e.g. `"email"`, `"linkedin"`. */
690
+ channel?: string;
691
+ }
692
+ /**
693
+ * Learning memory — append-only signals derived from reinforcement
694
+ * events. Trimmed by the workers; rotated to history on overflow.
695
+ *
696
+ * Stored at `ecodrix_ai_personas.learning_memory` (JSONB).
697
+ */
698
+ interface LearningMemory {
699
+ approvedLeadPatterns: LeadPattern[];
700
+ rejectedLeadPatterns: LeadPattern[];
701
+ wonDealSignals: LeadPattern[];
702
+ lostDealSignals: LeadPattern[];
703
+ outreachEditHistory: OutreachEdit[];
704
+ corrections: Correction[];
705
+ /**
706
+ * Audit log of every `ai_suggestion_accepted` event applied to this
707
+ * persona. Counters below carry the rolling totals; this array is
708
+ * the per-event trail required by REQ-012. Trimmed by the worker
709
+ * when growth becomes a token-budget concern.
710
+ */
711
+ acceptedSuggestionEvents: SuggestionEvent[];
712
+ /** Audit log of every `ai_suggestion_rejected` event. See above. */
713
+ rejectedSuggestionEvents: SuggestionEvent[];
714
+ /** Cumulative count, used by the confidence math (REQ-008). */
715
+ acceptedSuggestions: number;
716
+ /** Cumulative count, used by the confidence math (REQ-008). */
717
+ rejectedSuggestions: number;
718
+ /** Sum of all reinforcement events ever applied to this persona. */
719
+ totalReinforcement: number;
720
+ /**
721
+ * Append-only log of AI-generated outreach that was actually sent.
722
+ * Powers the denominator of the 7-day rolling edit-ratio (REQ-016
723
+ * §4 / Task 3.5.4.1). Optional because pre-Loop 4 personas don't
724
+ * carry the field; the writer initialises it to `[]` on every new
725
+ * row written after Phase 3.5. Capped at 500 entries by the writer
726
+ * (oldest rolled off) — same pattern as `outreachEditHistory`.
727
+ */
728
+ aiOutreachSent?: OutreachSent[];
729
+ /**
730
+ * Cooldown gate for the outreach style drift loop (REQ-016 §4 /
731
+ * Task 3.5.4.2). When the worker emits a `style_drift` warning it
732
+ * stamps this field; subsequent loop runs skip the persona until
733
+ * the configured cooldown elapses (default 7 days). Absent on
734
+ * personas that have never tripped a drift warning.
735
+ */
736
+ lastStyleDriftWarningAt?: string;
737
+ [extra: string]: unknown;
738
+ }
739
+ /**
740
+ * Full AI persona — one row per `(userId, orgId, productId)` in
741
+ * `ecodrix_ai_personas`. Returned by `GET /api/persona/:productId`.
742
+ */
743
+ interface AIPersona {
744
+ id: string;
745
+ orgId: string;
746
+ userId: string;
747
+ productId: "crm" | "laie" | "flow";
748
+ identity: PersonaIdentity;
749
+ behaviorRules: BehaviorRules;
750
+ vocabulary: Vocabulary;
751
+ icp: ICP;
752
+ learningMemory: LearningMemory;
753
+ /** Version string of the `OnboardingConfig` that bootstrapped this row. */
754
+ onboardingVersion: string | null;
755
+ onboardingCompletedAt: string | null;
756
+ lastReinforced: string | null;
757
+ /** Monotonically increasing per persona; bumped on every write. */
758
+ memoryVersion: number;
759
+ /** True only after onboarding completes; false on the generic baseline. */
760
+ isBootstrapped: boolean;
761
+ /** 0–100 — see REQ-008 for the formula. */
762
+ confidenceScore: number;
763
+ createdAt: string;
764
+ updatedAt: string;
765
+ }
766
+ /**
767
+ * Common envelope fields shared by every reinforcement event.
768
+ * `eventId` enables worker-side idempotency (Property 5 in design.md).
769
+ */
770
+ interface ReinforcementEventBase {
771
+ /** Unique id (uuid) for idempotent re-delivery. */
772
+ eventId: string;
773
+ productId: "crm" | "laie" | "flow";
774
+ /** Wall-clock at the originating UI surface. ISO 8601 string. */
775
+ occurredAt?: string;
776
+ }
777
+ /**
778
+ * REQ-007 — discriminated union of every reinforcement event the
779
+ * platform emits. Workers fan these into `learning_memory` mutations.
780
+ */
781
+ type ReinforcementEvent = (ReinforcementEventBase & {
782
+ kind: "lead_approved";
783
+ leadId: string;
784
+ /**
785
+ * Optional `(signal, value, weight?)` tuples. Weight defaults to
786
+ * `1.0` on the writer and is bounded `[0, 1]`. The reinforcement
787
+ * worker enriches this list via the signal-derivation layer
788
+ * (task 3.1.5) before the writer persists it.
789
+ */
790
+ signals?: Array<{
791
+ signal: string;
792
+ value: string;
793
+ weight?: number;
794
+ }>;
795
+ }) | (ReinforcementEventBase & {
796
+ kind: "lead_rejected";
797
+ leadId: string;
798
+ signals?: Array<{
799
+ signal: string;
800
+ value: string;
801
+ weight?: number;
802
+ }>;
803
+ reason?: string;
804
+ }) | (ReinforcementEventBase & {
805
+ kind: "deal_won";
806
+ leadId: string;
807
+ dealValue?: number;
808
+ signals?: Array<{
809
+ signal: string;
810
+ value: string;
811
+ weight?: number;
812
+ }>;
813
+ }) | (ReinforcementEventBase & {
814
+ kind: "deal_lost";
815
+ leadId: string;
816
+ reason?: string;
817
+ signals?: Array<{
818
+ signal: string;
819
+ value: string;
820
+ weight?: number;
821
+ }>;
822
+ }) | (ReinforcementEventBase & {
823
+ kind: "outreach_edited";
824
+ outreachId: string;
825
+ diff: Array<Record<string, unknown>>;
826
+ reason?: string;
827
+ }) | (ReinforcementEventBase & {
828
+ /**
829
+ * Recorded every time an AI-generated outreach actually goes
830
+ * out. Feeds the denominator of the 7-day rolling edit-ratio
831
+ * that drives REQ-016 §4 (outreach style drift detection,
832
+ * Task 3.5.4). The writer appends one
833
+ * {@link OutreachSent} entry to
834
+ * `learningMemory.aiOutreachSent` and caps the array at 500
835
+ * entries (oldest rolled off).
836
+ */
837
+ kind: "outreach_sent";
838
+ /** Optional id of the originating outreach row. */
839
+ outreachId?: string;
840
+ /** Optional channel descriptor, e.g. `"email"`, `"linkedin"`. */
841
+ channel?: string;
842
+ }) | (ReinforcementEventBase & {
843
+ kind: "ai_suggestion_accepted";
844
+ /** Stable id of the AI suggestion the user accepted. */
845
+ suggestionId: string;
846
+ /**
847
+ * Suggestion taxonomy slug, e.g. `"follow_up_message"`,
848
+ * `"lead_score_narrative"`, `"outreach_subject"`, `"next_action"`,
849
+ * `"inbox_reply"`.
850
+ */
851
+ suggestionType: string;
852
+ /**
853
+ * UI surface the user acted from, e.g. `"crm.deal.followup"`,
854
+ * `"saas.inbox.reply"`, `"laie.lead.score"`.
855
+ */
856
+ surface: string;
857
+ /**
858
+ * Surface-specific extra context (`dealId`, `leadId`,
859
+ * `conversationId`, etc).
860
+ */
861
+ productContext?: Record<string, unknown>;
862
+ /**
863
+ * Persona memory version that produced the AI generation.
864
+ * Threaded through history for attribution.
865
+ */
866
+ personaVersion?: number | null;
867
+ }) | (ReinforcementEventBase & {
868
+ kind: "ai_suggestion_rejected";
869
+ suggestionId: string;
870
+ suggestionType: string;
871
+ surface: string;
872
+ /** Optional user-supplied rejection reason. */
873
+ rejectionReason?: string;
874
+ productContext?: Record<string, unknown>;
875
+ personaVersion?: number | null;
876
+ }) | (ReinforcementEventBase & {
877
+ kind: "ai_assumption_corrected";
878
+ field: string;
879
+ oldValue: unknown;
880
+ newValue: unknown;
881
+ }) | (ReinforcementEventBase & {
882
+ kind: "followup_answer";
883
+ questionId: string;
884
+ targetField: string;
885
+ answer: unknown;
886
+ });
887
+ /**
888
+ * Response of `POST /api/persona/reinforce`.
889
+ */
890
+ interface ReinforceResponse {
891
+ ok: true;
892
+ /** Persona memory version after this event was applied. */
893
+ version: number;
894
+ }
895
+ /**
896
+ * Response of `GET /api/persona/confidence`.
897
+ */
898
+ interface ConfidenceResponse {
899
+ /** 0–100 score per REQ-008. */
900
+ score: number;
901
+ /** Same breakdown the saas pill exposes on click. */
902
+ breakdown: {
903
+ acceptRatioContribution: number;
904
+ reinforcementContribution: number;
905
+ bootstrapContribution: number;
906
+ };
907
+ }
908
+ /**
909
+ * One row of a persona's version history. Mirrors the wire shape of
910
+ * `GET /api/persona/:productId/timeline` produced by
911
+ * `personaHistory.listTimeline` on the server.
912
+ *
913
+ * The DB stores 50 of these per persona (`personaHistory.appendAndPrune`
914
+ * caps retention); the saas timeline page renders the diff for each
915
+ * row inline via the `diff` array of RFC 6902 ops.
916
+ */
917
+ interface PersonaTimelineEntry {
918
+ id: string;
919
+ personaId: string;
920
+ /** Monotonically increasing per persona; matches `AIPersona.memoryVersion`
921
+ * at the time of write. */
922
+ memoryVersion: number;
923
+ /** One of `onboarding_edit | reinforcement | admin_override | self_review`,
924
+ * but typed loosely so a new server reason doesn't break SDK consumers. */
925
+ changeReason: string;
926
+ /** User id, `system`, or `worker:<name>`. */
927
+ changedBy: string;
928
+ /** RFC 6902 JSON Patch ops applied by this version. */
929
+ diff: Array<Record<string, unknown>>;
930
+ /** Snapshot of the persona BEFORE this change. */
931
+ prevSnapshot: Record<string, unknown>;
932
+ /** ISO 8601 wall-clock at the time of write. */
933
+ createdAt: string;
934
+ }
935
+ /**
936
+ * Response shape of `GET /api/persona/:productId/timeline`.
937
+ *
938
+ * `hasMore` is a soft "did we hit the page size?" hint — the server
939
+ * doesn't run a second COUNT(*) to compute an exact total. The saas
940
+ * treats it as a CTA gate: render "Load older" when true.
941
+ */
942
+ interface PersonaTimelineResponse {
943
+ entries: PersonaTimelineEntry[];
944
+ hasMore: boolean;
945
+ }
946
+ /** Optional query parameters for `Persona.timeline(...)`. */
947
+ interface PersonaTimelineOptions {
948
+ /** Page size — clamped server-side to `[1, 50]`. Default `50`. */
949
+ limit?: number;
950
+ /** Cursor — return entries with `memoryVersion < before`. */
951
+ before?: number;
952
+ }
953
+ /**
954
+ * One pending followup question synthesised by the
955
+ * confidence-gap-detection loop (REQ-016 Loop 1). The saas
956
+ * `<FollowupQuestionCard>` renders the newest pending entry inline
957
+ * on the relevant product page; the user's answer routes through
958
+ * `POST /api/persona/:productId/followups/:id/answer` (which flips
959
+ * `answeredAt` on the row) and additionally fires a
960
+ * `kind: "followup_answer"` reinforcement event so the persona's
961
+ * `corrections[]` audit slot is updated.
962
+ *
963
+ * Mirrors the wire shape returned by
964
+ * `GET /api/persona/:productId/followups`. The DB row carries
965
+ * `answeredAt` and `answer` columns; we deliberately omit them here
966
+ * because the reader only ever returns pending rows (both columns
967
+ * are NULL until the user answers).
968
+ */
969
+ interface PersonaFollowupQuestion {
970
+ id: string;
971
+ personaId: string;
972
+ question: string;
973
+ /** Persona JSONB dot-path the answer maps to,
974
+ * e.g. `"icp.industryTargets"` or `"behaviorRules.outreachTone"`. */
975
+ targetField: string;
976
+ /** ISO 8601 — when the worker generated the question. */
977
+ askedAt: string;
978
+ /**
979
+ * Open structured payload for advanced followup kinds (REQ-016
980
+ * §4 / Task 3.5.4.2 — outreach style drift recalibration).
981
+ *
982
+ * Loop 4 fills this with `{ kind: "style_recalibration", ratio,
983
+ * options: [{ id, label }, ...] }` so the saas
984
+ * `<FollowupQuestionCard>` can render a multi-choice toggle
985
+ * instead of the simpler free-text textarea. Absent / `null` on
986
+ * rows created by the Loop 1 confidence-gap path.
987
+ *
988
+ * Free-form by design — workers can extend the payload without an
989
+ * SDK rebuild and saas surfaces ignore unknown keys gracefully.
990
+ */
991
+ metadata?: {
992
+ kind?: string;
993
+ options?: Array<{
994
+ id: string;
995
+ label: string;
996
+ }>;
997
+ ratio?: number;
998
+ [k: string]: unknown;
999
+ } | null;
1000
+ }
1001
+ /**
1002
+ * Body shape of `POST /api/persona/:productId/followups/:id/answer`.
1003
+ *
1004
+ * Two acceptable forms:
1005
+ * - Plain string answer — the legacy path used by Loop 1
1006
+ * confidence-gap followups. The string is stored verbatim on the
1007
+ * row and forwarded to the persona as a `kind: "followup_answer"`
1008
+ * reinforcement event.
1009
+ * - Structured `{ dimension, detail? }` answer — the Loop 4
1010
+ * style-recalibration path. The route validates `dimension`
1011
+ * against the five recognised ids (`tone | length |
1012
+ * personalization | cta | structure`), forwards the answer to
1013
+ * `personaWriter.applyStyleRecalibration`, AND additionally fires
1014
+ * the standard reinforcement event for the audit trail.
1015
+ */
1016
+ type AnswerFollowupBody = {
1017
+ answer: string;
1018
+ } | {
1019
+ answer: {
1020
+ dimension: "tone" | "length" | "personalization" | "cta" | "structure";
1021
+ detail?: string;
1022
+ };
1023
+ };
1024
+ /**
1025
+ * Response shape of `GET /api/persona/:productId/followups`.
1026
+ *
1027
+ * The list is ordered newest-first by `askedAt` and capped at 5
1028
+ * server-side. The saas card only ever renders the first entry.
1029
+ */
1030
+ interface PersonaFollowupsResponse {
1031
+ entries: PersonaFollowupQuestion[];
1032
+ }
1033
+ /**
1034
+ * AI persona resource.
1035
+ *
1036
+ * Mounted on the main client as `ecod.persona`. Reads the persona row,
1037
+ * accepts reinforcement events, and exposes the live confidence score.
1038
+ *
1039
+ * @example
1040
+ * ```typescript
1041
+ * const persona = await ecod.persona.get("crm");
1042
+ *
1043
+ * await ecod.persona.reinforce({
1044
+ * eventId: crypto.randomUUID(),
1045
+ * productId: "laie",
1046
+ * kind: "lead_approved",
1047
+ * leadId: "lead_123",
1048
+ * signals: [{ signal: "funding_stage", value: "series_a" }],
1049
+ * });
1050
+ *
1051
+ * const { score } = await ecod.persona.confidence("laie");
1052
+ * ```
1053
+ */
1054
+ declare class Persona extends APIResource {
1055
+ /**
1056
+ * Read the persona for the calling user + given product. Returns the
1057
+ * generic baseline shape when the user hasn't bootstrapped yet
1058
+ * (`isBootstrapped === false`).
1059
+ *
1060
+ * Note: this method shadows `APIResource.get` (which is `protected`).
1061
+ * The override widens visibility to public and narrows the return type
1062
+ * to `AIPersona`, which TypeScript flags as a structural override
1063
+ * mismatch — that mismatch is intentional and benign here, hence the
1064
+ * directive below. Implementation delegates to `super.get(...)`.
1065
+ */
1066
+ get(productId: "crm" | "laie" | "flow"): Promise<AIPersona>;
1067
+ /**
1068
+ * Submit a reinforcement event. The server enqueues it onto the
1069
+ * `persona-reinforcement` queue and returns the post-apply memory
1070
+ * version. Idempotent on `eventId`.
1071
+ */
1072
+ reinforce(event: ReinforcementEvent): Promise<ReinforceResponse>;
1073
+ /**
1074
+ * Read the live confidence score + the per-component breakdown.
1075
+ */
1076
+ confidence(productId: "crm" | "laie" | "flow"): Promise<ConfidenceResponse>;
1077
+ /**
1078
+ * Read the persona's version history — one entry per `personaWriter`
1079
+ * mutation, with the RFC 6902 patch + previous snapshot.
1080
+ *
1081
+ * Returns at most 50 rows per call (matches the per-persona
1082
+ * retention cap enforced server-side by
1083
+ * `personaHistory.appendAndPrune`). When `hasMore === true`, page
1084
+ * back with `{ before: entries[entries.length - 1].memoryVersion }`.
1085
+ *
1086
+ * Note: this method shadows `APIResource.get` (which is `protected`)
1087
+ * via the public override above on `get(...)`. The same caveat
1088
+ * applies here — we intentionally widen visibility and narrow the
1089
+ * return type to the timeline envelope. Implementation delegates
1090
+ * to `super.get(...)`.
1091
+ */
1092
+ timeline(productId: "crm" | "laie" | "flow", options?: PersonaTimelineOptions): Promise<PersonaTimelineResponse>;
1093
+ /**
1094
+ * Read the persona's pending followup questions — the
1095
+ * confidence-gap-detection loop (REQ-016 Loop 1) writes one row
1096
+ * per `(persona, weakest subfield)` pair when
1097
+ * `confidenceScore < 50` AND `learningMemory.totalReinforcement
1098
+ * >= 20`. The saas `<FollowupQuestionCard>` mounts on the
1099
+ * relevant product page and renders the newest pending entry
1100
+ * inline.
1101
+ *
1102
+ * Returns the unwrapped `entries` array (no surrounding `{ success,
1103
+ * data }` envelope) — matches the project's preference of having
1104
+ * the SDK do the unwrap so call sites stay slim. An empty list
1105
+ * means there's nothing pending; the card opts itself out and
1106
+ * returns `null` in that case.
1107
+ *
1108
+ * Note: this method shadows `APIResource.get` (which is `protected`)
1109
+ * — see the note above {@link Persona.get} / {@link Persona.timeline}.
1110
+ */
1111
+ followups(productId: "crm" | "laie" | "flow"): Promise<PersonaFollowupQuestion[]>;
1112
+ }
1113
+
1114
+ /**
1115
+ * Admin-side onboarding resource.
1116
+ *
1117
+ * Mounted on the main client as `ecod.adminOnboarding`. Mirrors the
1118
+ * platform-admin endpoints under `/api/admin/onboarding/*` documented
1119
+ * in `server/src/platform/routes/admin/onboarding.routes.ts`.
1120
+ *
1121
+ * Auth: every method requires the elevated platform `coreApiKey`. The
1122
+ * SDK forwards it as `x-core-api-key`; admin server-side callers should
1123
+ * always construct a client with `coreApiKey` set (see
1124
+ * `admin/src/lib/client/erix-admin.ts`).
1125
+ *
1126
+ * Endpoints covered in this build (task 4.1.1 — list page):
1127
+ *
1128
+ * - GET /api/admin/onboarding/niche-packs list
1129
+ * - GET /api/admin/onboarding/niche-packs/:slug fetch one
1130
+ * - POST /api/admin/onboarding/niche-packs create
1131
+ * - PATCH /api/admin/onboarding/niche-packs/:slug update
1132
+ * - POST /api/admin/onboarding/niche-packs/:slug/publish publish
1133
+ * - POST /api/admin/onboarding/niche-packs/:slug/unpublish back to draft
1134
+ * - POST /api/admin/onboarding/niche-packs/:slug/fork new draft copy
1135
+ * - GET /api/admin/onboarding/configs list configs
1136
+ * - POST /api/admin/onboarding/configs create config
1137
+ * - PATCH /api/admin/onboarding/configs/:productId/:version update
1138
+ * - POST /api/admin/onboarding/configs/:productId/:version/publish publish
1139
+ *
1140
+ * Endpoints for A/B onboarding experiments (task 4.2.2):
1141
+ *
1142
+ * - GET /api/admin/onboarding/experiments list
1143
+ * - GET /api/admin/onboarding/experiments/:slug fetch one
1144
+ * - POST /api/admin/onboarding/experiments create draft
1145
+ * - PATCH /api/admin/onboarding/experiments/:slug partial update
1146
+ * - DELETE /api/admin/onboarding/experiments/:slug hard-delete
1147
+ *
1148
+ * Successive admin tasks (4.1.2–4.1.5) layer richer editor / fork /
1149
+ * publish flows on top of these primitives.
1150
+ */
1151
+
1152
+ /** Niche pack row as returned by the admin list endpoint. */
1153
+ interface NichePackSummary {
1154
+ id: string;
1155
+ slug: string;
1156
+ productId: NichePack["productId"];
1157
+ version: string;
1158
+ status: NichePackStatus;
1159
+ /** ISO 8601 — creation timestamp (server clock). */
1160
+ createdAt: string;
1161
+ /** ISO 8601 — last edit timestamp. */
1162
+ updatedAt: string;
1163
+ /** ISO 8601 — first time `status` flipped to `published`. */
1164
+ publishedAt?: string | null;
1165
+ /** Admin staff id who created this version. */
1166
+ createdBy?: string | null;
1167
+ }
1168
+ /** Filters accepted by `adminOnboarding.listNichePacks`. */
1169
+ interface ListNichePacksParams {
1170
+ /** Restrict to a single product family. */
1171
+ productId?: NichePack["productId"];
1172
+ /** Restrict to one lifecycle status. */
1173
+ status?: NichePackStatus;
1174
+ /**
1175
+ * Free-form fuzzy search. Matched (case-insensitive) against `slug`
1176
+ * and `productId`. Whitespace is trimmed.
1177
+ */
1178
+ search?: string;
1179
+ /** Page size — server clamps to 1..100, default 25. */
1180
+ limit?: number;
1181
+ /** Pagination offset, default 0. */
1182
+ offset?: number;
1183
+ }
1184
+ /** Body accepted by the create-pack endpoint. */
1185
+ interface CreateNichePackPayload {
1186
+ slug: string;
1187
+ productId: NichePack["productId"];
1188
+ /** Defaults to `"1.0.0"` server-side. */
1189
+ version?: string;
1190
+ onboardingConfig?: Record<string, unknown>;
1191
+ workspaceProfile?: Record<string, unknown>;
1192
+ researchConfig?: Record<string, unknown>;
1193
+ }
1194
+ /** Body accepted by the update-pack endpoint. At least one field required. */
1195
+ interface UpdateNichePackPayload {
1196
+ status?: NichePackStatus;
1197
+ onboardingConfig?: Record<string, unknown>;
1198
+ workspaceProfile?: Record<string, unknown>;
1199
+ researchConfig?: Record<string, unknown>;
1200
+ }
1201
+ /** Body accepted by the fork-pack endpoint. */
1202
+ interface ForkNichePackPayload {
1203
+ /** Source version to clone; defaults to the latest row for the slug. */
1204
+ fromVersion?: string;
1205
+ /** Target version for the new draft; defaults to a server-side bump. */
1206
+ version?: string;
1207
+ }
1208
+ /**
1209
+ * Body accepted by `adminOnboarding.smokeTestNichePack` (task 4.1.5).
1210
+ *
1211
+ * Both fields are optional:
1212
+ *
1213
+ * - `fromVersion` pins a specific row for the slug; without it the
1214
+ * server picks the most recent (typically the latest draft).
1215
+ * - `answers` is a `{ stepId: value }` map. When omitted (or missing
1216
+ * keys for some steps) the in-memory runner auto-picks the first
1217
+ * option of `single_choice` / `multi_choice` steps and uses a
1218
+ * placeholder for `text_input`.
1219
+ */
1220
+ interface SmokeTestNichePackPayload {
1221
+ fromVersion?: string;
1222
+ answers?: Record<string, unknown>;
1223
+ }
1224
+ /**
1225
+ * One materializer action the live `workspaceShaper.materialize` call
1226
+ * WOULD perform if the smoke-tested pack were used in a real
1227
+ * onboarding-complete flow. The summary string is what the admin
1228
+ * panel renders verbatim ("Would create pipeline 'Patient Journey'
1229
+ * with 5 stages").
1230
+ */
1231
+ type SmokeTestMaterializerAction = {
1232
+ kind: "pipeline";
1233
+ name: string;
1234
+ stageCount: number;
1235
+ isDefault: boolean;
1236
+ summary: string;
1237
+ } | {
1238
+ kind: "field";
1239
+ key: string;
1240
+ label: string;
1241
+ type: string;
1242
+ required: boolean;
1243
+ summary: string;
1244
+ } | {
1245
+ kind: "automation";
1246
+ name: string;
1247
+ trigger: string;
1248
+ enabled: boolean;
1249
+ summary: string;
1250
+ };
1251
+ /**
1252
+ * Result envelope returned by `adminOnboarding.smokeTestNichePack`.
1253
+ *
1254
+ * Mirrors `SmokeTestResult` in
1255
+ * `server/src/erix/services/onboarding/smokeTestRunner.ts`.
1256
+ */
1257
+ interface SmokeTestResult {
1258
+ /** In-memory `OnboardingSession` produced by the simulator. */
1259
+ session: OnboardingSession;
1260
+ /** Persona shaped from the answers + memory mappings. */
1261
+ persona: AIPersona;
1262
+ /** Workspace profile produced by `workspaceShaper.produce`. */
1263
+ workspaceProfile: WorkspaceProfile;
1264
+ /** Actions the live materializer would perform. */
1265
+ materializerActions: SmokeTestMaterializerAction[];
1266
+ /** Wall-clock duration (millis) of the simulation. */
1267
+ durationMs: number;
1268
+ /** Step ids the engine visited (in order). */
1269
+ visitedStepIds: string[];
1270
+ /** Step ids the engine skipped via branching predicates. */
1271
+ skippedStepIds: string[];
1272
+ /** Whether the runner had to auto-pick at least one answer. */
1273
+ autoPicked: boolean;
1274
+ }
1275
+ /** Standard `{ success, data }` envelope used by every admin endpoint. */
1276
+ interface ApiEnvelope<T> {
1277
+ success: boolean;
1278
+ data: T;
1279
+ }
1280
+ /** Envelope returned by `GET /admin/onboarding/niche-packs`. */
1281
+ interface ListNichePacksResponse extends ApiEnvelope<NichePackSummary[]> {
1282
+ /**
1283
+ * Total niche-pack rows matching the current filter set, ignoring
1284
+ * the page window. Drives the saas-side paginator. Falls back to
1285
+ * `data.length` when the server can't compute a true count.
1286
+ */
1287
+ total: number;
1288
+ }
1289
+ /** Envelope returned by single-pack endpoints (get / create / update / fork). */
1290
+ type NichePackResponse = ApiEnvelope<NichePack & NichePackSummary>;
1291
+ /** Filters accepted by `adminOnboarding.listOnboardingConfigs`. */
1292
+ interface ListOnboardingConfigsParams {
1293
+ productId?: OnboardingConfig["productId"];
1294
+ status?: NichePackStatus;
1295
+ }
1296
+ /** Body accepted by the create-config endpoint. */
1297
+ interface CreateOnboardingConfigPayload {
1298
+ productId: OnboardingConfig["productId"];
1299
+ version?: string;
1300
+ config?: Record<string, unknown>;
1301
+ }
1302
+ /** Body accepted by the update-config endpoint. */
1303
+ interface UpdateOnboardingConfigPayload {
1304
+ status?: NichePackStatus;
1305
+ config?: Record<string, unknown>;
1306
+ }
1307
+ /** Filters accepted by `adminOnboarding.listOnboardingExperiments`. */
1308
+ interface ListOnboardingExperimentsParams {
1309
+ productId?: OnboardingExperiment["productId"];
1310
+ status?: OnboardingExperimentStatus;
1311
+ /** Page size — server clamps to 1..100, default 25. */
1312
+ limit?: number;
1313
+ /** Pagination offset, default 0. */
1314
+ offset?: number;
1315
+ } /** Body accepted by the create-experiment endpoint. */
1316
+ interface CreateOnboardingExperimentPayload {
1317
+ /** Admin-facing handle; unique platform-wide. */
1318
+ slug: string;
1319
+ productId: OnboardingExperiment["productId"];
1320
+ /** Display name shown in the admin panel. */
1321
+ name: string;
1322
+ description?: string;
1323
+ /** Defaults to `"draft"` server-side. */
1324
+ status?: OnboardingExperimentStatus;
1325
+ /**
1326
+ * Variant routing table — may be empty for a freshly created
1327
+ * draft. Variants whose `cohortRange` overlaps are rejected with
1328
+ * `OVERLAPPING_VARIANTS`.
1329
+ */
1330
+ variants?: OnboardingExperimentVariant[];
1331
+ /** Fallback config version when no variant matches the user's bucket. */
1332
+ controlConfigVersion?: string | null;
1333
+ }
1334
+ /** Body accepted by the patch-experiment endpoint. */
1335
+ interface UpdateOnboardingExperimentPayload {
1336
+ name?: string;
1337
+ description?: string | null;
1338
+ status?: OnboardingExperimentStatus;
1339
+ variants?: OnboardingExperimentVariant[];
1340
+ controlConfigVersion?: string | null;
1341
+ }
1342
+ /** Envelope returned by `GET /admin/onboarding/experiments`. */
1343
+ interface ListOnboardingExperimentsResponse extends ApiEnvelope<OnboardingExperiment[]> {
1344
+ /**
1345
+ * Total experiments matching the current filter set, ignoring the
1346
+ * page window. Drives the saas-side paginator. Falls back to
1347
+ * `data.length` when the server can't compute a true count.
1348
+ */
1349
+ total: number;
1350
+ }
1351
+ /** Envelope returned by single-experiment endpoints. */
1352
+ type OnboardingExperimentResponse = ApiEnvelope<OnboardingExperiment>;
1353
+ /**
1354
+ * Filters accepted by every analytics endpoint
1355
+ * (`getOnboardingFunnel`, `getOnboardingTimeOnStep`,
1356
+ * `getOnboardingAbandonment`).
1357
+ *
1358
+ * `productId` constrains by product family, `nicheSubtype` by the
1359
+ * `nichePackSlug` recorded on the originating session row. `fromDate`
1360
+ * / `toDate` are wire-side ISO 8601 strings (the server coerces them
1361
+ * via Zod's `z.coerce.date()`); when omitted, the server defaults to
1362
+ * the trailing 30 days. The server hard-caps the requested window at
1363
+ * 180 days and rejects `fromDate >= toDate` with 400 INVALID_FIELD.
1364
+ */
1365
+ interface OnboardingAnalyticsFiltersParams {
1366
+ productId?: "crm" | "laie" | "flow" | string;
1367
+ nicheSubtype?: string;
1368
+ /** ISO 8601 datetime, e.g. `"2025-01-01T00:00:00.000Z"`. */
1369
+ fromDate?: string;
1370
+ /** ISO 8601 datetime. */
1371
+ toDate?: string;
1372
+ }
1373
+ /** One funnel row — distinct sessions that reached `stepId`. */
1374
+ interface OnboardingFunnelStep {
1375
+ productId: string;
1376
+ nichePackSlug: string | null;
1377
+ stepId: string;
1378
+ /** Distinct sessions that emitted at least one event for this step. */
1379
+ sessions: number;
1380
+ /**
1381
+ * Fraction `[0, 1]` of sessions that reached this step relative to
1382
+ * the first step in the same `(productId, nichePackSlug)` flow.
1383
+ * The first step always has `conversionRate = 1`.
1384
+ */
1385
+ conversionRate: number;
1386
+ }
1387
+ /** Result envelope for `getOnboardingFunnel`. */
1388
+ interface OnboardingFunnelResult {
1389
+ /** Flat list, ordered by `(productId, nichePackSlug, sessions DESC)`. */
1390
+ funnel: OnboardingFunnelStep[];
1391
+ /** Distinct sessions across all rows in the window. */
1392
+ totalSessions: number;
1393
+ }
1394
+ /** One time-on-step row — per `(productId, nichePackSlug, stepId)`. */
1395
+ interface OnboardingTimeOnStepRow {
1396
+ productId: string;
1397
+ nichePackSlug: string | null;
1398
+ stepId: string;
1399
+ /** Median (p50) milliseconds from `stepId` first event → next step. */
1400
+ medianMs: number;
1401
+ /** p95 milliseconds. */
1402
+ p95Ms: number;
1403
+ /** Number of (session, step) pairs that contributed a delta. */
1404
+ sampleSize: number;
1405
+ }
1406
+ /** Result envelope for `getOnboardingTimeOnStep`. */
1407
+ interface OnboardingTimeOnStepResult {
1408
+ rows: OnboardingTimeOnStepRow[];
1409
+ }
1410
+ /** One abandonment bucket — abandoned sessions whose last seen step was `stepId`. */
1411
+ interface OnboardingAbandonmentRow {
1412
+ productId: string;
1413
+ nichePackSlug: string | null;
1414
+ stepId: string;
1415
+ abandonedSessions: number;
1416
+ }
1417
+ /** Result envelope for `getOnboardingAbandonment`. */
1418
+ interface OnboardingAbandonmentResult {
1419
+ rows: OnboardingAbandonmentRow[];
1420
+ totalAbandoned: number;
1421
+ }
1422
+ /** `{ success, data: OnboardingFunnelResult }`. */
1423
+ type OnboardingFunnelResponse = ApiEnvelope<OnboardingFunnelResult>;
1424
+ /** `{ success, data: OnboardingTimeOnStepResult }`. */
1425
+ type OnboardingTimeOnStepResponse = ApiEnvelope<OnboardingTimeOnStepResult>;
1426
+ /** `{ success, data: OnboardingAbandonmentResult }`. */
1427
+ type OnboardingAbandonmentResponse = ApiEnvelope<OnboardingAbandonmentResult>;
1428
+ /**
1429
+ * Admin-side onboarding control plane.
1430
+ *
1431
+ * @example
1432
+ * ```typescript
1433
+ * const { data, total } = await ecod.adminOnboarding.listNichePacks({
1434
+ * productId: "crm",
1435
+ * status: "published",
1436
+ * search: "clinic",
1437
+ * limit: 25,
1438
+ * offset: 0,
1439
+ * });
1440
+ * ```
1441
+ */
1442
+ declare class AdminOnboarding extends APIResource {
1443
+ /**
1444
+ * List niche packs with optional filtering, search, and pagination.
1445
+ * Server response shape: `{ success, data, total }`.
1446
+ */
1447
+ listNichePacks(params?: ListNichePacksParams): Promise<ListNichePacksResponse>;
1448
+ /** Fetch a single niche pack by slug. Without `version` returns the latest. */
1449
+ getNichePack(slug: string, version?: string): Promise<NichePackResponse>;
1450
+ /** Create a new niche pack draft. */
1451
+ createNichePack(payload: CreateNichePackPayload): Promise<NichePackResponse>;
1452
+ /** Patch a draft niche pack. Server enforces "at least one field". */
1453
+ updateNichePack(slug: string, payload: UpdateNichePackPayload, version?: string): Promise<NichePackResponse>;
1454
+ /** Flip a niche pack to `published`. */
1455
+ publishNichePack(slug: string, version?: string): Promise<NichePackResponse>;
1456
+ /** Flip a niche pack back to `draft`. */
1457
+ unpublishNichePack(slug: string, version?: string): Promise<NichePackResponse>;
1458
+ /** Fork an existing niche pack into a new draft version. */
1459
+ forkNichePack(slug: string, payload?: ForkNichePackPayload): Promise<NichePackResponse>;
1460
+ /**
1461
+ * Run an in-memory smoke test against a niche pack (task 4.1.5).
1462
+ *
1463
+ * The server resolves the pack row, walks its `onboardingConfig`
1464
+ * with the supplied (or auto-picked) answers, shapes a persona
1465
+ * + workspace profile, and returns the result without writing
1466
+ * anything. Useful for admins to validate a draft pack before
1467
+ * publishing.
1468
+ *
1469
+ * @param slug - Niche pack slug.
1470
+ * @param payload - Optional `{ fromVersion?, answers? }`. Both
1471
+ * fields are independently optional; `version` (the legacy
1472
+ * query-string equivalent) is also supported as a third
1473
+ * argument for parity with the other slug-keyed methods.
1474
+ * @param version - Optional version override applied as a
1475
+ * `?version=` query param (kept for callers that already
1476
+ * thread version through the URL).
1477
+ */
1478
+ smokeTestNichePack(slug: string, payload?: SmokeTestNichePackPayload, version?: string): Promise<ApiEnvelope<SmokeTestResult>>;
1479
+ listOnboardingConfigs(params?: ListOnboardingConfigsParams): Promise<ApiEnvelope<unknown[]>>;
1480
+ createOnboardingConfig(payload: CreateOnboardingConfigPayload): Promise<ApiEnvelope<unknown>>;
1481
+ updateOnboardingConfig(productId: OnboardingConfig["productId"], version: string, payload: UpdateOnboardingConfigPayload): Promise<ApiEnvelope<unknown>>;
1482
+ publishOnboardingConfig(productId: OnboardingConfig["productId"], version: string): Promise<ApiEnvelope<unknown>>;
1483
+ /**
1484
+ * List onboarding A/B experiments with optional filters and
1485
+ * pagination. Server response shape: `{ success, data, total }`.
1486
+ */
1487
+ listOnboardingExperiments(params?: ListOnboardingExperimentsParams): Promise<ListOnboardingExperimentsResponse>;
1488
+ /** Fetch a single experiment by its slug. 404 when absent. */
1489
+ getOnboardingExperiment(slug: string): Promise<OnboardingExperimentResponse>;
1490
+ /**
1491
+ * Create a draft experiment. The server enforces:
1492
+ *
1493
+ * - `slug` non-empty + globally unique.
1494
+ * - `productId ∈ {crm, laie, flow}`.
1495
+ * - `variants` is an array (may be empty for a draft).
1496
+ * - Each variant's `cohortRange.from < cohortRange.to`, both in
1497
+ * `[0, 100]`.
1498
+ * - Variants don't overlap (returns 400 `OVERLAPPING_VARIANTS`).
1499
+ */
1500
+ createOnboardingExperiment(payload: CreateOnboardingExperimentPayload): Promise<OnboardingExperimentResponse>;
1501
+ /**
1502
+ * Patch an experiment. At least one of `name | description |
1503
+ * status | variants | controlConfigVersion` must be supplied —
1504
+ * the server returns 400 `NO_UPDATES` for an empty body. When
1505
+ * `variants` is supplied the overlap validator re-runs.
1506
+ */
1507
+ updateOnboardingExperiment(slug: string, payload: UpdateOnboardingExperimentPayload): Promise<OnboardingExperimentResponse>;
1508
+ /**
1509
+ * Hard-delete an experiment. Returns the deleted row in `data` for
1510
+ * audit-log convenience. Admin-only — no soft-delete column.
1511
+ */
1512
+ deleteOnboardingExperiment(slug: string): Promise<OnboardingExperimentResponse>;
1513
+ /**
1514
+ * Funnel-by-step counts for `(productId, nichePackSlug)`, plus
1515
+ * conversion rates relative to each flow's first step.
1516
+ *
1517
+ * Server-side filters: `productId`, `nicheSubtype` (the niche pack
1518
+ * slug — UI-friendly alias of the column name), `fromDate`,
1519
+ * `toDate`. Both date bounds default to the trailing 30 days. The
1520
+ * server clamps the window at 180 days and rejects
1521
+ * `fromDate >= toDate` with 400 INVALID_FIELD.
1522
+ *
1523
+ * Powers the admin Sankey + per-step bar chart in Task 4.3.2.
1524
+ */
1525
+ getOnboardingFunnel(params?: OnboardingAnalyticsFiltersParams): Promise<OnboardingFunnelResponse>;
1526
+ /**
1527
+ * Median + p95 milliseconds spent on each step (per
1528
+ * `(productId, nichePackSlug, stepId)`), computed via
1529
+ * `LEAD(created_at) OVER (PARTITION BY session_id …)` on the event
1530
+ * stream. Sample size accompanies each row so the saas can
1531
+ * grey-out under-sampled buckets.
1532
+ */
1533
+ getOnboardingTimeOnStep(params?: OnboardingAnalyticsFiltersParams): Promise<OnboardingTimeOnStepResponse>;
1534
+ /**
1535
+ * Histogram of `stepId` values where sessions went inactive — i.e.
1536
+ * `status = 'abandoned'` OR (active for >24h with no further
1537
+ * events). The server also tallies a `totalAbandoned` aggregate so
1538
+ * the saas can render a percentage column without re-summing.
1539
+ */
1540
+ getOnboardingAbandonment(params?: OnboardingAnalyticsFiltersParams): Promise<OnboardingAbandonmentResponse>;
1541
+ }
1542
+
64
1543
  interface CheckoutProduct {
65
1544
  externalId: string;
66
1545
  name: string;
@@ -628,9 +2107,15 @@ declare class Automations extends APIResource {
628
2107
  }
629
2108
 
630
2109
  /**
631
- * The specific UI control type suggested for a field.
2110
+ * The specific UI control type suggested for a field on a `ResourceManifest`.
2111
+ *
2112
+ * NOTE: this is the UI-hint vocabulary used by `ResourceManifest`. It is
2113
+ * intentionally distinct from the custom-fields `FieldType` (16 storage
2114
+ * types) exported from `./custom-fields`. Use `ManifestFieldType` when
2115
+ * declaring auto-generated form fields, and `FieldType` (custom-fields)
2116
+ * when declaring tenant-owned custom fields stored in `dynamic_fields`.
632
2117
  */
633
- type FieldType = "string" | "number" | "boolean" | "date" | "datetime" | "select" | "multi-select" | "phone" | "email" | "currency" | "json" | "textarea" | "richtext" | "file" | "image";
2118
+ type ManifestFieldType = "string" | "number" | "boolean" | "date" | "datetime" | "select" | "multi-select" | "phone" | "email" | "currency" | "json" | "textarea" | "richtext" | "file" | "image";
634
2119
  /**
635
2120
  * Metadata defining a single field within a resource.
636
2121
  * Used by UI kits to automatically generate forms and validation logic.
@@ -641,7 +2126,7 @@ interface FieldManifest {
641
2126
  /** Human-readable label (localized if possible). */
642
2127
  label: string;
643
2128
  /** Suggested UI type. */
644
- type: FieldType;
2129
+ type: ManifestFieldType;
645
2130
  /** Short hint/placeholder text. */
646
2131
  description?: string;
647
2132
  /** Whether the field is required for creation. */
@@ -689,13 +2174,70 @@ interface ResourceManifest {
689
2174
  }
690
2175
 
691
2176
  /**
692
- * Valid statuses for a CRM Lead.
2177
+ * Org-wide lifecycle status of a CRM Lead. Matches the `erix_leads.status`
2178
+ * column in Postgres and the legacy Mongoose `Lead.status` enum.
2179
+ *
2180
+ * open — active in any pipeline (default for new leads)
2181
+ * won — closed positively (set when stage.isWon = true)
2182
+ * lost — closed negatively (set when stage.isLost = true)
2183
+ * archived — soft-deleted / parked
2184
+ *
2185
+ * NOTE: the Kanban columns visible in the UI ("New", "Contacted",
2186
+ * "Qualified", etc.) are *pipeline stages* (`Lead.stageId` →
2187
+ * `PipelineStage`), NOT statuses. To move a lead between Kanban columns,
2188
+ * call `leads.move(leadId, stageId)`. Use `leads.update(leadId, { status })`
2189
+ * only to flip the lifecycle to one of the four values above.
693
2190
  */
694
- type LeadStatus = "new" | "contacted" | "qualified" | "won" | "lost" | "archived";
2191
+ type LeadStatus = "open" | "won" | "lost" | "archived";
2192
+ /**
2193
+ * Runtime list of every valid LeadStatus value. Use this when validating
2194
+ * input or rendering selectors so the array stays in sync with the type.
2195
+ */
2196
+ declare const LEAD_STATUS_VALUES: readonly ["open", "won", "lost", "archived"];
695
2197
  /**
696
2198
  * Common lead acquisition sources. Additional string values are allowed.
697
2199
  */
698
2200
  type LeadSource = "website" | "whatsapp" | "direct" | "referral" | string;
2201
+ /**
2202
+ * Lead entity as returned by the API. Mongo-shaped (`_id`).
2203
+ *
2204
+ * `pipelineId` and `stageId` may be either an ID string or a populated
2205
+ * sub-document depending on whether the endpoint expanded them.
2206
+ */
2207
+ interface Lead {
2208
+ _id: string;
2209
+ firstName: string;
2210
+ lastName?: string;
2211
+ phone: string;
2212
+ email?: string;
2213
+ company?: string;
2214
+ status: LeadStatus;
2215
+ value?: number;
2216
+ source?: string;
2217
+ assignedTo?: string;
2218
+ tags?: string[];
2219
+ pipelineId?: string | {
2220
+ _id: string;
2221
+ name: string;
2222
+ isDefault?: boolean;
2223
+ };
2224
+ stageId?: string | {
2225
+ _id: string;
2226
+ name: string;
2227
+ color: string;
2228
+ probability?: number;
2229
+ isWon?: boolean;
2230
+ isLost?: boolean;
2231
+ order?: number;
2232
+ };
2233
+ score?: number;
2234
+ lastContactedAt?: string;
2235
+ createdAt: string;
2236
+ updatedAt: string;
2237
+ metadata?: Record<string, unknown>;
2238
+ /** Custom fields dictionary keyed by FieldDefinition.id. */
2239
+ dynamic_fields?: Record<string, unknown>;
2240
+ }
699
2241
  /**
700
2242
  * Parameters for creating a new CRM Lead.
701
2243
  */
@@ -1051,6 +2593,32 @@ declare class Payments extends APIResource {
1051
2593
  }): Promise<T>;
1052
2594
  }
1053
2595
 
2596
+ /**
2597
+ * A single stage inside a CRM pipeline (Mongo-shaped `_id`).
2598
+ */
2599
+ interface PipelineStage {
2600
+ _id: string;
2601
+ name: string;
2602
+ color: string;
2603
+ probability?: number;
2604
+ isWon?: boolean;
2605
+ isLost?: boolean;
2606
+ order: number;
2607
+ }
2608
+ /**
2609
+ * A CRM pipeline returned by the API. Stages are embedded.
2610
+ */
2611
+ interface Pipeline {
2612
+ _id: string;
2613
+ name: string;
2614
+ isDefault: boolean;
2615
+ stages: PipelineStage[];
2616
+ }
2617
+ interface CreatePipelineParams {
2618
+ name: string;
2619
+ isDefault?: boolean;
2620
+ stages?: any[];
2621
+ }
1054
2622
  interface PipelineStageParams {
1055
2623
  name: string;
1056
2624
  color?: string;
@@ -1669,6 +3237,118 @@ declare class EventsResource extends APIResource {
1669
3237
  }): Promise<T>;
1670
3238
  }
1671
3239
 
3240
+ /**
3241
+ * ERIX-Flow — no-code visual workflow orchestration.
3242
+ *
3243
+ * Create, manage, and trigger flow-based automations with a visual canvas.
3244
+ * Mounted at `/api/flow/v1`.
3245
+ *
3246
+ * @example
3247
+ * ```typescript
3248
+ * const workflows = await ecod.flow.list();
3249
+ * const run = await ecod.flow.run(workflowId, { seed: { person: { phone: "+91..." } } });
3250
+ * const status = await ecod.flow.getRun(run.runId);
3251
+ * ```
3252
+ */
3253
+ declare class Flow extends APIResource {
3254
+ /**
3255
+ * List flow definitions for the tenant.
3256
+ */
3257
+ list<T = any>(params?: {
3258
+ page?: number;
3259
+ limit?: number;
3260
+ status?: string;
3261
+ }): Promise<T>;
3262
+ /**
3263
+ * Create a new flow definition.
3264
+ */
3265
+ create<T = any>(payload: {
3266
+ name: string;
3267
+ description?: string;
3268
+ graph: {
3269
+ nodes: unknown[];
3270
+ edges: unknown[];
3271
+ };
3272
+ status?: string;
3273
+ triggerType?: string;
3274
+ triggerConfig?: unknown;
3275
+ }): Promise<T>;
3276
+ /**
3277
+ * Get a single flow definition by ID.
3278
+ */
3279
+ getWorkflow<T = any>(id: string): Promise<T>;
3280
+ /**
3281
+ * Update a flow definition.
3282
+ */
3283
+ update<T = any>(id: string, payload: {
3284
+ name?: string;
3285
+ description?: string;
3286
+ graph?: {
3287
+ nodes: unknown[];
3288
+ edges: unknown[];
3289
+ };
3290
+ status?: string;
3291
+ }): Promise<T>;
3292
+ /**
3293
+ * Trigger a flow run. Returns the runId for tracking.
3294
+ */
3295
+ run<T = any>(workflowId: string, payload?: {
3296
+ triggerType?: string;
3297
+ seed?: {
3298
+ recordId?: string;
3299
+ person?: {
3300
+ fullName?: string;
3301
+ email?: string;
3302
+ phone?: string;
3303
+ };
3304
+ company?: {
3305
+ name?: string;
3306
+ domain?: string;
3307
+ };
3308
+ crm?: {
3309
+ contactRef?: string;
3310
+ stage?: string;
3311
+ };
3312
+ };
3313
+ }): Promise<T>;
3314
+ /**
3315
+ * Get a run's summary + node statuses.
3316
+ */
3317
+ getRun<T = any>(runId: string): Promise<T>;
3318
+ /**
3319
+ * Get run records (review surface, PII-redacted).
3320
+ */
3321
+ getRunRecords<T = any>(runId: string): Promise<T>;
3322
+ /**
3323
+ * Retry a failed run from the failed node.
3324
+ */
3325
+ retry<T = any>(runId: string): Promise<T>;
3326
+ /**
3327
+ * Approve/reject/edit at an approval checkpoint.
3328
+ */
3329
+ approve<T = any>(runId: string, payload: {
3330
+ decision: "approve" | "reject" | "edit";
3331
+ record?: Record<string, unknown>;
3332
+ nodeId?: string;
3333
+ }): Promise<T>;
3334
+ /**
3335
+ * Get run execution logs.
3336
+ */
3337
+ getRunLogs<T = any>(runId: string): Promise<T>;
3338
+ /**
3339
+ * List installable flow templates.
3340
+ */
3341
+ templates<T = any>(params?: {
3342
+ category?: string;
3343
+ }): Promise<T>;
3344
+ /**
3345
+ * Install a template into the tenant's workspace.
3346
+ */
3347
+ installTemplate<T = any>(templateId: string, payload?: {
3348
+ name?: string;
3349
+ }): Promise<T>;
3350
+ }
3351
+
1672
3352
  interface SystemHealth {
1673
3353
  status: string;
1674
3354
  version: string;
@@ -1733,6 +3413,58 @@ declare class Health extends APIResource {
1733
3413
  jobStatus(jobId: string): Promise<JobStatus>;
1734
3414
  }
1735
3415
 
3416
+ /**
3417
+ * Invoices — CRM invoice management.
3418
+ *
3419
+ * Create, send, and track invoices for CRM deals.
3420
+ * Mounted at `/api/saas/invoices`.
3421
+ *
3422
+ * @example
3423
+ * ```typescript
3424
+ * const invoices = await ecod.invoices.list();
3425
+ * const inv = await ecod.invoices.create({ leadId, items, currency: "INR" });
3426
+ * await ecod.invoices.sendViaWhatsApp(inv.id);
3427
+ * ```
3428
+ */
3429
+ declare class Invoices extends APIResource {
3430
+ /**
3431
+ * List invoices for the tenant.
3432
+ */
3433
+ list<T = any>(params?: {
3434
+ page?: number;
3435
+ limit?: number;
3436
+ status?: string;
3437
+ }): Promise<T>;
3438
+ /**
3439
+ * Get a single invoice by ID.
3440
+ */
3441
+ getInvoice<T = any>(id: string): Promise<T>;
3442
+ /**
3443
+ * Create a new invoice.
3444
+ */
3445
+ create<T = any>(payload: Record<string, unknown>): Promise<T>;
3446
+ /**
3447
+ * Update an invoice.
3448
+ */
3449
+ update<T = any>(id: string, payload: Record<string, unknown>): Promise<T>;
3450
+ /**
3451
+ * Delete an invoice.
3452
+ */
3453
+ delete<T = any>(id: string): Promise<T>;
3454
+ /**
3455
+ * Send an invoice via WhatsApp to the linked lead.
3456
+ */
3457
+ sendViaWhatsApp<T = any>(id: string): Promise<T>;
3458
+ /**
3459
+ * Get invoice settings for the tenant.
3460
+ */
3461
+ getSettings<T = any>(): Promise<T>;
3462
+ /**
3463
+ * Update invoice settings.
3464
+ */
3465
+ updateSettings<T = any>(payload: Record<string, unknown>): Promise<T>;
3466
+ }
3467
+
1736
3468
  interface LogPaginationQuery {
1737
3469
  page?: number;
1738
3470
  limit?: number;
@@ -2420,6 +4152,270 @@ declare class Notifications extends APIResource {
2420
4152
  retryAction<T = any>(notificationId: string): Promise<T>;
2421
4153
  }
2422
4154
 
4155
+ /**
4156
+ * Body of `POST /api/onboarding/global` — the four-question signup form.
4157
+ *
4158
+ * Fields land directly on `ecodrix_organizations.*` columns (workspace
4159
+ * name, timezone) and `service_config -> 'global'` (primary product,
4160
+ * adaptive flag). See REQ-001.
4161
+ */
4162
+ interface GlobalOnboardingPayload {
4163
+ /** Workspace / org display name. Persisted to `ecodrix_organizations.name`. */
4164
+ workspaceName: string;
4165
+ /** Personal display name of the signing-up user. */
4166
+ displayName: string;
4167
+ /** IANA timezone, e.g. `"Asia/Kolkata"`. Persisted to `organizations.timezone`. */
4168
+ timezone: string;
4169
+ /**
4170
+ * The product the user wants to start with. `"not_sure"` defers
4171
+ * per-product onboarding to first product open.
4172
+ */
4173
+ primaryProduct: "crm" | "laie" | "flow" | "not_sure";
4174
+ }
4175
+ /**
4176
+ * Body of `POST /api/onboarding/start` — opens a per-product onboarding
4177
+ * session. Triggered by the explicit "Enable {Product}" click on
4178
+ * `/products/<productId>` (REQ-002).
4179
+ */
4180
+ interface StartOnboardingPayload {
4181
+ productId: "crm" | "laie" | "flow";
4182
+ }
4183
+ /**
4184
+ * Body of `POST /api/onboarding/answer` — records one step's answer and
4185
+ * advances the engine. The saas calls this on every step submit (and as
4186
+ * a debounced PATCH for resumability).
4187
+ */
4188
+ interface AnswerOnboardingPayload {
4189
+ sessionId: string;
4190
+ stepId: string;
4191
+ /** Shape depends on the step type — string, string[], boolean, etc. */
4192
+ answer: unknown;
4193
+ }
4194
+ /**
4195
+ * Body of `POST /api/onboarding/complete` — terminates the session,
4196
+ * upserts the persona, and materializes the workspace defaults.
4197
+ */
4198
+ interface CompleteOnboardingPayload {
4199
+ sessionId: string;
4200
+ }
4201
+ /**
4202
+ * Body of `POST /api/onboarding/edit` — post-completion edit of a single
4203
+ * answer from `/settings/onboarding`. Triggers a `persona_recomputed`
4204
+ * event (REQ-010).
4205
+ */
4206
+ interface EditOnboardingPayload {
4207
+ stepId: string;
4208
+ answer: unknown;
4209
+ }
4210
+ /**
4211
+ * Result of `POST /api/onboarding/start`.
4212
+ */
4213
+ interface StartOnboardingResponse {
4214
+ sessionId: string;
4215
+ firstStep: OnboardingStep;
4216
+ }
4217
+ /**
4218
+ * Result of `POST /api/onboarding/answer`.
4219
+ */
4220
+ interface AnswerOnboardingResponse {
4221
+ /** `null` when the engine has reached the final step. */
4222
+ nextStep: OnboardingStep | null;
4223
+ draftPersona: Record<string, unknown>;
4224
+ }
4225
+ /**
4226
+ * Result of `GET /api/onboarding/state`. Returns the active session
4227
+ * (status `"active"`) for `(userId, productId)`, plus the step the
4228
+ * renderer should currently show.
4229
+ */
4230
+ interface OnboardingStateResponse {
4231
+ session: OnboardingSession | null;
4232
+ currentStep: OnboardingStep | null;
4233
+ }
4234
+ /**
4235
+ * Result of `POST /api/onboarding/complete`.
4236
+ */
4237
+ interface CompleteOnboardingResponse {
4238
+ /** Opaque persona document — full type lives in `resources/persona.ts`. */
4239
+ persona: Record<string, unknown>;
4240
+ workspaceProfile: WorkspaceProfile;
4241
+ }
4242
+ /**
4243
+ * Result of `POST /api/onboarding/edit`.
4244
+ */
4245
+ interface EditOnboardingResponse {
4246
+ persona: Record<string, unknown>;
4247
+ /** RFC 6902 JSON Patch describing what changed in the persona. */
4248
+ diff: Array<Record<string, unknown>>;
4249
+ }
4250
+ /**
4251
+ * Adaptive onboarding resource.
4252
+ *
4253
+ * Mounted on the main client as `ecod.onboarding`. Mirrors the seven
4254
+ * tenant-facing routes documented in `design.md` § "API surface".
4255
+ *
4256
+ * @example
4257
+ * ```typescript
4258
+ * await ecod.onboarding.global({
4259
+ * workspaceName: "Acme Health",
4260
+ * displayName: "Alice",
4261
+ * timezone: "Asia/Kolkata",
4262
+ * primaryProduct: "crm",
4263
+ * });
4264
+ *
4265
+ * const { sessionId, firstStep } = await ecod.onboarding.start({ productId: "crm" });
4266
+ * await ecod.onboarding.answer({ sessionId, stepId: firstStep.stepId, answer: "healthcare" });
4267
+ * await ecod.onboarding.complete({ sessionId });
4268
+ * ```
4269
+ */
4270
+ declare class Onboarding extends APIResource {
4271
+ /**
4272
+ * Submit the four-question global onboarding form (REQ-001).
4273
+ * Updates `ecodrix_organizations.*` columns and
4274
+ * `service_config.global.primaryProduct`.
4275
+ */
4276
+ global<T = unknown>(payload: GlobalOnboardingPayload): Promise<T>;
4277
+ /**
4278
+ * Open a per-product onboarding session. Called when the user clicks
4279
+ * "Enable {Product}" on `/products/<productId>` (REQ-002).
4280
+ */
4281
+ start(payload: StartOnboardingPayload): Promise<StartOnboardingResponse>;
4282
+ /**
4283
+ * Record one step's answer and advance the engine. Returns the next
4284
+ * step (or `null` at the end) and the in-progress draft persona.
4285
+ */
4286
+ answer(payload: AnswerOnboardingPayload): Promise<AnswerOnboardingResponse>;
4287
+ /**
4288
+ * Load the active session for the calling user + product. Used on
4289
+ * page mount + every revisit to support resumability (REQ-010).
4290
+ */
4291
+ state(productId: string): Promise<OnboardingStateResponse>;
4292
+ /**
4293
+ * Finalise the session, upsert the persona, and materialize the
4294
+ * niche-tuned workspace defaults.
4295
+ */
4296
+ complete(payload: CompleteOnboardingPayload): Promise<CompleteOnboardingResponse>;
4297
+ /**
4298
+ * Edit a previously-answered step from `/settings/onboarding`. The
4299
+ * server emits a `persona_recomputed` event so the workspace and
4300
+ * persona update in-session (REQ-010).
4301
+ */
4302
+ edit(payload: EditOnboardingPayload): Promise<EditOnboardingResponse>;
4303
+ /**
4304
+ * Preview the workspace profile that would be materialized if the
4305
+ * session completed right now. Used by the `feature_preview` step.
4306
+ */
4307
+ previewWorkspace(sessionId: string): Promise<WorkspaceProfile>;
4308
+ }
4309
+
4310
+ /**
4311
+ * Portal Inbox — team-side unified inbox for portal conversations.
4312
+ *
4313
+ * Lists all projects with active client threads, retrieves messages,
4314
+ * and posts replies. Mounted at `/api/saas/portal-inbox`.
4315
+ *
4316
+ * @example
4317
+ * ```typescript
4318
+ * const threads = await ecod.portalInbox.conversations();
4319
+ * const messages = await ecod.portalInbox.messages(projectId);
4320
+ * await ecod.portalInbox.reply(projectId, { body: "We'll get back to you!" });
4321
+ * ```
4322
+ */
4323
+ declare class PortalInbox extends APIResource {
4324
+ /**
4325
+ * List all projects with active portal conversation threads.
4326
+ * Returns projectId, projectName, lastMessageAt, lastBody, lastSender, unreadCount.
4327
+ */
4328
+ conversations<T = any>(): Promise<T>;
4329
+ /**
4330
+ * Get paginated messages for a project's portal thread.
4331
+ */
4332
+ messages<T = any>(projectId: string): Promise<T>;
4333
+ /**
4334
+ * Post a reply from the team to a project's portal thread.
4335
+ */
4336
+ reply<T = any>(projectId: string, payload: {
4337
+ body: string;
4338
+ attachments?: Array<{
4339
+ name: string;
4340
+ url: string;
4341
+ }>;
4342
+ }): Promise<T>;
4343
+ }
4344
+
4345
+ /**
4346
+ * Project Documents + E-Sign — document management and digital signatures
4347
+ * for CRM projects.
4348
+ *
4349
+ * Mounted at `/api/saas/projects/:projectId/documents`.
4350
+ *
4351
+ * @example
4352
+ * ```typescript
4353
+ * const docs = await ecod.projectDocuments.list(projectId);
4354
+ * await ecod.projectDocuments.upload(projectId, { name: "Contract.pdf", fileKey, mimeType });
4355
+ * await ecod.projectDocuments.toggleSignature(projectId, docId, true);
4356
+ * await ecod.projectDocuments.addSigner(projectId, docId, { role: "client", name: "Alice" });
4357
+ * await ecod.projectDocuments.stampSign(projectId, docId, { signatureImage: base64Png });
4358
+ * ```
4359
+ */
4360
+ declare class ProjectDocuments extends APIResource {
4361
+ /**
4362
+ * List all documents for a project.
4363
+ */
4364
+ list<T = any>(projectId: string): Promise<T>;
4365
+ /**
4366
+ * Register an uploaded document (after uploading to R2 via presigned URL).
4367
+ */
4368
+ upload<T = any>(projectId: string, payload: {
4369
+ name: string;
4370
+ fileKey: string;
4371
+ mimeType: string;
4372
+ sizeBytes?: string;
4373
+ }): Promise<T>;
4374
+ /**
4375
+ * Toggle "Signature Required" on a document.
4376
+ * When enabled, triggers notifications to signers.
4377
+ */
4378
+ toggleSignature<T = any>(projectId: string, docId: string, required: boolean): Promise<T>;
4379
+ /**
4380
+ * Add a signer to a document.
4381
+ */
4382
+ addSigner<T = any>(projectId: string, docId: string, signer: {
4383
+ role: "owner" | "client";
4384
+ name: string;
4385
+ email?: string;
4386
+ userId?: string;
4387
+ }): Promise<T>;
4388
+ /**
4389
+ * Team user signs a document (simple mark — no PDF stamp).
4390
+ */
4391
+ sign<T = any>(projectId: string, docId: string, payload?: {
4392
+ signedFileKey?: string;
4393
+ }): Promise<T>;
4394
+ /**
4395
+ * Team user declines signing a document.
4396
+ */
4397
+ decline<T = any>(projectId: string, docId: string): Promise<T>;
4398
+ /**
4399
+ * Full signing ceremony — stamps a signature image onto the PDF,
4400
+ * computes an audit seal, and re-uploads the signed copy.
4401
+ *
4402
+ * @param signatureImage - Base64-encoded PNG of the drawn/typed signature
4403
+ * @param signerName - Name to stamp on the PDF
4404
+ * @param position - Optional position override { page?, x?, y?, width?, height? }
4405
+ */
4406
+ stampSign<T = any>(projectId: string, docId: string, payload: {
4407
+ signatureImage: string;
4408
+ signerName?: string;
4409
+ position?: {
4410
+ page?: number;
4411
+ x?: number;
4412
+ y?: number;
4413
+ width?: number;
4414
+ height?: number;
4415
+ };
4416
+ }): Promise<T>;
4417
+ }
4418
+
2423
4419
  interface JobStats {
2424
4420
  waiting: number;
2425
4421
  active: number;
@@ -2471,6 +4467,93 @@ declare class Queue extends APIResource {
2471
4467
  deleteJob<T = any>(jobId: string): Promise<T>;
2472
4468
  }
2473
4469
 
4470
+ /** Relay endpoint provenance. */
4471
+ type RelayOrigin = "seed" | "manual" | "auto";
4472
+ /** Relay egress type. */
4473
+ type RelayType = "cloudflare" | "gcp" | "lambda" | "browser" | "direct";
4474
+ /** Public relay record (secrets are NEVER included). */
4475
+ interface RelayRecord {
4476
+ id: string;
4477
+ type: RelayType;
4478
+ url: string | null;
4479
+ region: string | null;
4480
+ priority: number;
4481
+ enabled: boolean;
4482
+ origin: RelayOrigin;
4483
+ providerResourceId: string | null;
4484
+ provisionedAt: string | null;
4485
+ lastHealthyAt: string | null;
4486
+ health?: RelayEndpointHealth | null;
4487
+ }
4488
+ interface RelayEndpointHealth {
4489
+ key: string;
4490
+ type: RelayType;
4491
+ url?: string;
4492
+ region?: string;
4493
+ healthy: boolean;
4494
+ degradedUntil?: number;
4495
+ requestCount: number;
4496
+ errorCount: number;
4497
+ lastUsedAt?: number;
4498
+ }
4499
+ /** Hot-reloadable relay-fabric tuning. */
4500
+ interface RelayTuning {
4501
+ relayTimeoutMs: number;
4502
+ relayCooldownMs: number;
4503
+ pingIntervalMs: number;
4504
+ pingTimeoutMs: number;
4505
+ maxRetries: number;
4506
+ leadEnrichTimeoutMs: number;
4507
+ aiStepTimeoutMs: number;
4508
+ stuckJobTimeoutMs: number;
4509
+ targetHealthyRelays: number;
4510
+ autoProvisionEnabled: boolean;
4511
+ maxAutoRelaysPerType: number;
4512
+ maxAutoRelaysTotal: number;
4513
+ retireAfterDegradedMs: number;
4514
+ }
4515
+ interface RelayCreatePayload {
4516
+ type: RelayType;
4517
+ url?: string;
4518
+ region?: string;
4519
+ priority?: number;
4520
+ /** Key into the platform secret store — never a plaintext secret. */
4521
+ secretRef?: string;
4522
+ }
4523
+ interface RelayUpdatePayload {
4524
+ enabled?: boolean;
4525
+ priority?: number;
4526
+ region?: string;
4527
+ }
4528
+ interface RelayProvisionPayload {
4529
+ provider?: "lambda" | "cloudflare";
4530
+ region?: string;
4531
+ }
4532
+ /**
4533
+ * Dynamic Relay Fabric control plane. Authenticated with the platform CORE API
4534
+ * key (server-side only). Endpoints live under `/api/laie/v1/relays`.
4535
+ */
4536
+ declare class Relays extends APIResource {
4537
+ /** List relays + live health (per-type aggregate + per-endpoint). */
4538
+ list<T = any>(): Promise<T>;
4539
+ /** Add a manual relay. */
4540
+ create<T = any>(payload: RelayCreatePayload): Promise<T>;
4541
+ /** Enable/disable/repriority a relay. */
4542
+ update<T = any>(id: string, payload: RelayUpdatePayload): Promise<T>;
4543
+ /** Remove a manual/auto relay (seed relays are never removable). */
4544
+ delete<T = any>(id: string): Promise<T>;
4545
+ /** Read the hot-reloadable tuning config. */
4546
+ getConfig<T = any>(): Promise<T>;
4547
+ /** Update the tuning config (audited). */
4548
+ updateConfig<T = any>(payload: Partial<RelayTuning>): Promise<T>;
4549
+ /** Force a health ping and return the snapshot. */
4550
+ ping<T = any>(): Promise<T>;
4551
+ /** Manually provision a cloud relay (auto-provisioning must be enabled). */
4552
+ provision<T = any>(payload?: RelayProvisionPayload): Promise<T>;
4553
+ /** Manually retire a relay. */
4554
+ retire<T = any>(id: string): Promise<T>;
4555
+ }
4556
+
2474
4557
  declare class Clients extends APIResource {
2475
4558
  /**
2476
4559
  * List all clients in the ecosystem.
@@ -2591,7 +4674,7 @@ declare class Services extends APIResource {
2591
4674
  constructor(client: any);
2592
4675
  }
2593
4676
 
2594
- declare class EmailConfig extends APIResource {
4677
+ declare class EmailConfig$1 extends APIResource {
2595
4678
  /**
2596
4679
  * Retrieve current email provider configuration and domain verification statuses.
2597
4680
  */
@@ -2646,7 +4729,7 @@ declare class EmailConfig extends APIResource {
2646
4729
  listProviders<T = any>(): Promise<T>;
2647
4730
  }
2648
4731
  declare class Settings extends APIResource {
2649
- email: EmailConfig;
4732
+ email: EmailConfig$1;
2650
4733
  constructor(client: any);
2651
4734
  }
2652
4735
 
@@ -2818,6 +4901,24 @@ declare class Webhooks {
2818
4901
  constructEvent(payload: string | Buffer, signature: string | string[] | undefined, secret: string): Promise<any>;
2819
4902
  }
2820
4903
 
4904
+ /**
4905
+ * Broadcast campaign entity returned by `/api/saas/chat/broadcasts`.
4906
+ *
4907
+ * Tracks the lifecycle of a bulk WhatsApp send and its delivery counters.
4908
+ */
4909
+ interface Broadcast {
4910
+ _id: string;
4911
+ name: string;
4912
+ templateName: string;
4913
+ status: "draft" | "scheduled" | "sending" | "completed" | "failed";
4914
+ recipientCount: number;
4915
+ sentCount: number;
4916
+ deliveredCount: number;
4917
+ readCount: number;
4918
+ createdAt: string;
4919
+ scheduledAt?: string;
4920
+ completedAt?: string;
4921
+ }
2821
4922
  interface CreateBroadcastParams {
2822
4923
  /** Optional name for the broadcast campaign */
2823
4924
  name?: string;
@@ -2899,6 +5000,28 @@ declare class Broadcasts extends APIResource {
2899
5000
  delete<T = any>(id: string): Promise<T>;
2900
5001
  }
2901
5002
 
5003
+ /**
5004
+ * Conversation entity returned by `/api/saas/chat/conversations`.
5005
+ *
5006
+ * Represents a single WhatsApp thread between the workspace and an
5007
+ * external phone number. `status` is the conversation lifecycle (open
5008
+ * vs resolved) — distinct from `LeadStatus` on the linked lead.
5009
+ */
5010
+ interface Conversation {
5011
+ _id: string;
5012
+ phone: string;
5013
+ contactName?: string;
5014
+ lastMessage?: {
5015
+ body: string;
5016
+ timestamp: string;
5017
+ direction: "inbound" | "outbound";
5018
+ };
5019
+ unreadCount: number;
5020
+ status: "open" | "resolved";
5021
+ assignedTo?: string;
5022
+ leadId?: string;
5023
+ updatedAt: string;
5024
+ }
2902
5025
  interface ListParams {
2903
5026
  limit?: number;
2904
5027
  before?: string;
@@ -3000,6 +5123,20 @@ declare class Conversations extends APIResource {
3000
5123
  bulkDelete<T = any>(ids: string[]): Promise<T>;
3001
5124
  }
3002
5125
 
5126
+ /**
5127
+ * A single chat message inside a `Conversation`.
5128
+ */
5129
+ interface Message {
5130
+ _id: string;
5131
+ conversationId: string;
5132
+ body?: string;
5133
+ mediaUrl?: string;
5134
+ mediaType?: "image" | "video" | "audio" | "document";
5135
+ direction: "inbound" | "outbound";
5136
+ status: "sent" | "delivered" | "read" | "failed";
5137
+ timestamp: string;
5138
+ replyToId?: string;
5139
+ }
3003
5140
  /**
3004
5141
  * Parameters for sending a free-form WhatsApp message.
3005
5142
  */
@@ -3207,6 +5344,28 @@ declare class Messages extends APIResource {
3207
5344
  }>;
3208
5345
  }
3209
5346
 
5347
+ /**
5348
+ * Component of a Meta-approved WhatsApp template (HEADER / BODY / FOOTER /
5349
+ * BUTTONS).
5350
+ */
5351
+ interface TemplateComponent {
5352
+ type: "HEADER" | "BODY" | "FOOTER" | "BUTTONS";
5353
+ text?: string;
5354
+ format?: string;
5355
+ }
5356
+ /**
5357
+ * WhatsApp message template entity.
5358
+ *
5359
+ * Status mirrors Meta's approval pipeline: APPROVED / PENDING / REJECTED.
5360
+ */
5361
+ interface Template {
5362
+ _id: string;
5363
+ name: string;
5364
+ category: "MARKETING" | "UTILITY" | "AUTHENTICATION";
5365
+ language: string;
5366
+ status: "APPROVED" | "PENDING" | "REJECTED";
5367
+ components: TemplateComponent[];
5368
+ }
3210
5369
  interface TemplateMapping {
3211
5370
  mappings: any[];
3212
5371
  onEmptyVariable: string;
@@ -3485,10 +5644,30 @@ declare class ECODrIxAPI {
3485
5644
  readonly settings: Settings;
3486
5645
  /** Dynamic Cross-Origin Resource Sharing network policies. */
3487
5646
  readonly cors: Cors;
5647
+ /** LAIE Dynamic Relay Fabric control plane (CORE key, server-side only). */
5648
+ readonly relays: Relays;
3488
5649
  /** ErixCheckout one-click checkout flow management. */
3489
5650
  readonly checkout: Checkout;
3490
5651
  /** ERIX-Connect — tenant connections (channels, credentials, DB residency). */
3491
5652
  readonly connect: Connect;
5653
+ /** Adaptive onboarding — global + per-product config-driven flows. */
5654
+ readonly onboarding: Onboarding;
5655
+ /**
5656
+ * Platform-admin onboarding control plane (niche packs +
5657
+ * versioned onboarding configs). Server-side only — requires
5658
+ * the elevated `coreApiKey`.
5659
+ */
5660
+ readonly adminOnboarding: AdminOnboarding;
5661
+ /** AI persona memory — read, reinforce, confidence score. */
5662
+ readonly persona: Persona;
5663
+ /** ERIX-Flow — no-code visual workflow orchestration. */
5664
+ readonly flow: Flow;
5665
+ /** Portal Inbox — team-side unified inbox for portal conversations. */
5666
+ readonly portalInbox: PortalInbox;
5667
+ /** Project Documents + E-Sign — document management and digital signatures. */
5668
+ readonly projectDocuments: ProjectDocuments;
5669
+ /** Invoices — CRM invoice management. */
5670
+ readonly invoices: Invoices;
3492
5671
  constructor(options: ECODrIxAPIOptions);
3493
5672
  private initSocket;
3494
5673
  /**
@@ -3571,6 +5750,243 @@ declare class ECODrIxAPI {
3571
5750
  request<T = any>(method: Method, path: string, data?: any, params?: any): Promise<T>;
3572
5751
  }
3573
5752
 
5753
+ /**
5754
+ * Common response envelopes returned by the ERIX API.
5755
+ */
5756
+ /**
5757
+ * Standard paginated response shape.
5758
+ *
5759
+ * Used by list endpoints that page results (e.g. leads, conversations,
5760
+ * broadcasts). Frontend consumers can rely on `data` + `pagination` being
5761
+ * present.
5762
+ */
5763
+ interface PaginatedResponse<T> {
5764
+ data: T[];
5765
+ pagination: {
5766
+ page: number;
5767
+ limit: number;
5768
+ total: number;
5769
+ pages: number;
5770
+ };
5771
+ }
5772
+
5773
+ /**
5774
+ * Custom field type system for the ERIX CRM module.
5775
+ *
5776
+ * Defines the FieldType discriminated union, per-type config interfaces,
5777
+ * and the stored value shapes persisted in a lead's `dynamic_fields`
5778
+ * column.
5779
+ *
5780
+ * This is the single contract used by both the backend (server adapters,
5781
+ * validators, formula evaluators) and the saas frontend (designer UI,
5782
+ * editors, renderers). Keep saas + server in sync by sharing this file.
5783
+ */
5784
+ /** The 16 supported custom field types. */
5785
+ type FieldType = "text" | "number" | "currency" | "dropdown" | "date" | "datetime" | "phone" | "email" | "file" | "formula" | "relation" | "rating" | "progress" | "tags" | "url" | "checkbox";
5786
+ interface BaseConfig {
5787
+ required?: boolean;
5788
+ description?: string;
5789
+ }
5790
+ interface TextConfig extends BaseConfig {
5791
+ minLength?: number;
5792
+ maxLength?: number;
5793
+ pattern?: string;
5794
+ }
5795
+ interface NumberConfig extends BaseConfig {
5796
+ min?: number;
5797
+ max?: number;
5798
+ integer?: boolean;
5799
+ }
5800
+ interface CurrencyConfig extends BaseConfig {
5801
+ /** ISO 4217, default INR */
5802
+ defaultCurrency?: string;
5803
+ min?: number;
5804
+ max?: number;
5805
+ }
5806
+ interface DropdownConfig extends BaseConfig {
5807
+ options: {
5808
+ value: string;
5809
+ label: string;
5810
+ color?: string;
5811
+ }[];
5812
+ }
5813
+ /** ISO date */
5814
+ interface DateConfig extends BaseConfig {
5815
+ min?: string;
5816
+ max?: string;
5817
+ }
5818
+ /** ISO datetime */
5819
+ interface DateTimeConfig extends BaseConfig {
5820
+ min?: string;
5821
+ max?: string;
5822
+ }
5823
+ interface PhoneConfig extends BaseConfig {
5824
+ region?: string;
5825
+ }
5826
+ type EmailConfig = BaseConfig;
5827
+ interface FileConfig extends BaseConfig {
5828
+ maxSizeBytes?: number;
5829
+ accept?: string[];
5830
+ }
5831
+ interface FormulaConfig extends BaseConfig {
5832
+ expression: string;
5833
+ resultType: "number" | "text" | "boolean";
5834
+ }
5835
+ interface RelationConfig extends BaseConfig {
5836
+ targetEntity: "lead";
5837
+ displayField?: string;
5838
+ }
5839
+ interface RatingConfig extends BaseConfig {
5840
+ /** default 5 */
5841
+ max?: 5 | 10;
5842
+ }
5843
+ interface ProgressConfig extends BaseConfig {
5844
+ /** default 0..100 */
5845
+ min?: number;
5846
+ max?: number;
5847
+ }
5848
+ interface TagsConfig extends BaseConfig {
5849
+ options?: {
5850
+ value: string;
5851
+ label: string;
5852
+ color?: string;
5853
+ }[];
5854
+ allowFreeform?: boolean;
5855
+ }
5856
+ type UrlConfig = BaseConfig;
5857
+ type CheckboxConfig = BaseConfig;
5858
+ interface FieldDefBase {
5859
+ id: string;
5860
+ orgId: string;
5861
+ name: string;
5862
+ label: string;
5863
+ order: number;
5864
+ isActive: boolean;
5865
+ createdAt: string;
5866
+ updatedAt: string;
5867
+ }
5868
+ type FieldDefinition = (FieldDefBase & {
5869
+ fieldType: "text";
5870
+ config: TextConfig;
5871
+ }) | (FieldDefBase & {
5872
+ fieldType: "number";
5873
+ config: NumberConfig;
5874
+ }) | (FieldDefBase & {
5875
+ fieldType: "currency";
5876
+ config: CurrencyConfig;
5877
+ }) | (FieldDefBase & {
5878
+ fieldType: "dropdown";
5879
+ config: DropdownConfig;
5880
+ }) | (FieldDefBase & {
5881
+ fieldType: "date";
5882
+ config: DateConfig;
5883
+ }) | (FieldDefBase & {
5884
+ fieldType: "datetime";
5885
+ config: DateTimeConfig;
5886
+ }) | (FieldDefBase & {
5887
+ fieldType: "phone";
5888
+ config: PhoneConfig;
5889
+ }) | (FieldDefBase & {
5890
+ fieldType: "email";
5891
+ config: EmailConfig;
5892
+ }) | (FieldDefBase & {
5893
+ fieldType: "file";
5894
+ config: FileConfig;
5895
+ }) | (FieldDefBase & {
5896
+ fieldType: "formula";
5897
+ config: FormulaConfig;
5898
+ }) | (FieldDefBase & {
5899
+ fieldType: "relation";
5900
+ config: RelationConfig;
5901
+ }) | (FieldDefBase & {
5902
+ fieldType: "rating";
5903
+ config: RatingConfig;
5904
+ }) | (FieldDefBase & {
5905
+ fieldType: "progress";
5906
+ config: ProgressConfig;
5907
+ }) | (FieldDefBase & {
5908
+ fieldType: "tags";
5909
+ config: TagsConfig;
5910
+ }) | (FieldDefBase & {
5911
+ fieldType: "url";
5912
+ config: UrlConfig;
5913
+ }) | (FieldDefBase & {
5914
+ fieldType: "checkbox";
5915
+ config: CheckboxConfig;
5916
+ });
5917
+ type FieldConfig = FieldDefinition["config"];
5918
+ type CurrencyValue = {
5919
+ amount: number;
5920
+ currency: string;
5921
+ };
5922
+ type RelationValue = {
5923
+ leadId: string;
5924
+ };
5925
+ type StoredFieldValue = string | number | boolean | null | string[] | CurrencyValue | RelationValue;
5926
+ type NormalizedFieldValue = StoredFieldValue;
5927
+
5928
+ /**
5929
+ * CRM view configuration and filter model.
5930
+ *
5931
+ * Defines the layout, column, filter, sort, and color-rule shapes that
5932
+ * make up a saved CrmView, plus the adapter-level CustomFieldFilter
5933
+ * passed into leads.list for backend-specific query translation.
5934
+ */
5935
+
5936
+ type LayoutType = "table" | "kanban" | "cards";
5937
+ interface ColumnConfig {
5938
+ /** default field name or custom field id */
5939
+ fieldKey: string;
5940
+ visible: boolean;
5941
+ /** px */
5942
+ width: number;
5943
+ order: number;
5944
+ }
5945
+ type FilterOperator = "contains" | "equals" | "startsWith" | "isEmpty" | "gt" | "lt" | "between" | "before" | "after" | "isAnyOf" | "isNoneOf";
5946
+ interface FilterRule {
5947
+ fieldKey: string;
5948
+ operator: FilterOperator;
5949
+ value?: unknown;
5950
+ }
5951
+ interface SortRule {
5952
+ fieldKey: string;
5953
+ dir: "asc" | "desc";
5954
+ }
5955
+ interface ColorRule {
5956
+ fieldKey: string;
5957
+ operator: FilterOperator;
5958
+ value?: unknown;
5959
+ color: string;
5960
+ target: "row" | "cell";
5961
+ }
5962
+ interface ViewConfig {
5963
+ columns: ColumnConfig[];
5964
+ sort: SortRule[];
5965
+ filters: FilterRule[];
5966
+ /** stage | source | assignedTo | <dropdown/tags field id> */
5967
+ groupBy?: string;
5968
+ colorRules: ColorRule[];
5969
+ }
5970
+ interface CrmView {
5971
+ id: string;
5972
+ orgId: string;
5973
+ name: string;
5974
+ layoutType: LayoutType;
5975
+ config: ViewConfig;
5976
+ isDefault: boolean;
5977
+ isShared: boolean;
5978
+ createdBy: string;
5979
+ createdAt: string;
5980
+ updatedAt: string;
5981
+ }
5982
+ /** Adapter-level custom-field filter passed into leads.list. */
5983
+ interface CustomFieldFilter {
5984
+ fieldId: string;
5985
+ fieldType: FieldType;
5986
+ operator: FilterOperator;
5987
+ value?: unknown;
5988
+ }
5989
+
3574
5990
  /**
3575
5991
  * HTTP status code to exception type mapping
3576
5992
  * Maps specific HTTP status codes to appropriate typed exceptions
@@ -3698,4 +6114,4 @@ declare function getRetryConfig(error: any): {
3698
6114
  readonly reason: "Generic API errors may be transient and worth retrying";
3699
6115
  };
3700
6116
 
3701
- export { APIError, type ApplyCouponPayload, AuthenticationError, type CheckoutAmountBreakdown, type CheckoutOrderResponse, type CheckoutProduct, type CheckoutSession, type CreateOrderPayload, type CreateSessionPayload, ECODrIxAPI as ECODrIx, ECODrIxAPI, type ECODrIxAPIOptions, ERROR_RETRY_CONFIG, EcodrixError, type FieldManifest, type FieldType, HTTP_STATUS_ERROR_MAP, RateLimitError, type ResourceManifest, type VerifyPayload, ECODrIxAPI as default, getErrorDetails, getRetryConfig, handleAPIError, isAPIError };
6117
+ export { type AIPersona, APIError, AdminOnboarding, type AnswerFollowupBody, type AnswerOnboardingPayload, type AnswerOnboardingResponse, type ApplyCouponPayload, AuthenticationError, type BehaviorRules, type Broadcast, type CheckboxConfig, type CheckoutAmountBreakdown, type CheckoutOrderResponse, type CheckoutProduct, type CheckoutSession, type ColorRule, type ColumnConfig, type CompleteOnboardingPayload, type CompleteOnboardingResponse, type ConfidenceResponse, type Conversation, type ListParams as ConversationListParams, type Correction, type CreateBroadcastParams, type CreateLeadParams, type CreateNichePackPayload, type CreateOnboardingConfigPayload, type CreateOnboardingExperimentPayload, type CreateOrderPayload, type CreatePipelineParams, type CreateSessionPayload, type CrmView, type CurrencyConfig, type CurrencyValue, type CustomFieldFilter, type DateConfig, type DateTimeConfig, type DropdownConfig, ECODrIxAPI as ECODrIx, ECODrIxAPI, type ECODrIxAPIOptions, ERROR_RETRY_CONFIG, EcodrixError, type EditOnboardingPayload, type EditOnboardingResponse, type EmailConfig, type FieldConfig, type FieldDefinition, type FieldManifest, type FieldType, type FileConfig, type FilterOperator, type FilterRule, Flow, type ForkNichePackPayload, type FormulaConfig, type GlobalOnboardingPayload, HTTP_STATUS_ERROR_MAP, type ICP, Invoices, LEAD_STATUS_VALUES, type LayoutType, type Lead, type LeadPattern, type LeadSource, type LeadStatus, type LearningMemory, type ListLeadsParams, type ListNichePacksParams, type ListNichePacksResponse, type ListOnboardingConfigsParams, type ListOnboardingExperimentsParams, type ListOnboardingExperimentsResponse, type ManifestFieldType, type Message, type NichePack, type NichePackResearchConfig, type NichePackResponse, type NichePackStatus, type NichePackSummary, type NormalizedFieldValue, type NumberConfig, Onboarding, type OnboardingAbandonmentResponse, type OnboardingAbandonmentResult, type OnboardingAbandonmentRow, type OnboardingAnalyticsFiltersParams, type OnboardingCohortRange, type OnboardingConfig, type OnboardingExperiment, type OnboardingExperimentResponse, type OnboardingExperimentStatus, type OnboardingExperimentVariant, type OnboardingFunnelResponse, type OnboardingFunnelResult, type OnboardingFunnelStep, type OnboardingMemoryMapping, type OnboardingOption, type OnboardingSession, type OnboardingSessionStatus, type OnboardingStateResponse, type OnboardingStep, type OnboardingStepType, type OnboardingTimeOnStepResponse, type OnboardingTimeOnStepResult, type OnboardingTimeOnStepRow, type OutreachEdit, type PaginatedResponse, Persona, type PersonaFollowupQuestion, type PersonaFollowupsResponse, type PersonaIdentity, type PersonaTimelineEntry, type PersonaTimelineOptions, type PersonaTimelineResponse, type PhoneConfig, type Pipeline, type PipelineStage, type PipelineStageParams, PortalInbox, type ProgressConfig, ProjectDocuments, RateLimitError, type RatingConfig, type ReinforceResponse, type ReinforcementEvent, type RelationConfig, type RelationValue, type RelayCreatePayload, type RelayEndpointHealth, type RelayOrigin, type RelayProvisionPayload, type RelayRecord, type RelayTuning, type RelayType, type RelayUpdatePayload, Relays, type ResourceManifest, type SendMessageParams, type ServiceConfig, type ServiceConfigGlobal, type ServiceConfigPerProduct, type SmokeTestMaterializerAction, type SmokeTestNichePackPayload, type SmokeTestResult, type SortRule, type StartOnboardingPayload, type StartOnboardingResponse, type StoredFieldValue, type SuggestionEvent, type TagsConfig, type Template, type TemplateComponent, type TemplateMapping, type TextConfig, type UpdateNichePackPayload, type UpdateOnboardingConfigPayload, type UpdateOnboardingExperimentPayload, type UpsertLeadParams, type UrlConfig, type VerifyPayload, type ViewConfig, type Vocabulary, type WorkspaceDashboardLayout, type WorkspaceDefaultAutomation, type WorkspaceDefaultField, type WorkspaceDefaultPipeline, type WorkspaceModuleConfig, type WorkspaceProfile, ECODrIxAPI as default, getErrorDetails, getRetryConfig, handleAPIError, isAPIError };