@ecodrix/erix-api 1.0.4 → 1.0.6

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.
package/README.md CHANGED
@@ -6,10 +6,11 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=for-the-badge)](https://opensource.org/licenses/MIT)
7
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue?style=for-the-badge&logo=typescript)](https://www.typescriptlang.org/)
8
8
  [![OpenAPI](https://img.shields.io/badge/OpenAPI-3.0-green?style=for-the-badge&logo=openapiinitiative)](./schema/openapi.yaml)
9
+ [![Node.js](https://img.shields.io/badge/Node.js-%3E%3D18-brightgreen?style=for-the-badge&logo=nodedotjs)](https://nodejs.org/)
9
10
 
10
11
  **The official, isomorphic SDK for the [ECODrIx](https://ecodrix.com) platform.**
11
12
 
12
- Manage WhatsApp conversations, CRM leads, file storage, and Google Meet appointments — all from a single, type-safe library.
13
+ Manage WhatsApp conversations, CRM leads, pipelines, automations, marketing campaigns, file storage, and Google Meet — all from a single, type-safe library.
13
14
 
14
15
  </div>
15
16
 
@@ -22,12 +23,30 @@ Manage WhatsApp conversations, CRM leads, file storage, and Google Meet appointm
22
23
  - [Configuration](#configuration)
23
24
  - [Resources](#resources)
24
25
  - [WhatsApp](#whatsapp)
25
- - [CRM — Leads](#crm--leads)
26
+ - [Messages](#ecod-whatsapp-messages)
27
+ - [Conversations](#ecod-whatsapp-conversations)
28
+ - [Templates](#ecod-whatsapp-templates)
29
+ - [Broadcasts](#ecod-whatsapp-broadcasts)
30
+ - [CRM](#crm)
31
+ - [Leads](#ecod-crm-leads)
32
+ - [Pipelines & Stages](#ecod-crm-pipelines)
33
+ - [Automations](#ecod-crm-automations)
34
+ - [Sequences](#ecod-crm-sequences)
35
+ - [Activities & Timeline](#ecod-crm-activities)
36
+ - [Analytics](#ecod-crm-analytics)
37
+ - [Scoring](#ecod-crm-scoring)
38
+ - [Payments](#ecod-crm-payments)
39
+ - [Automation Dashboard](#ecod-crm-automationdashboard)
40
+ - [Events & Workflows](#events--workflows)
41
+ - [Marketing](#marketing)
26
42
  - [Meetings](#meetings)
27
- - [Media (Storage)](#media-storage)
43
+ - [Storage](#storage)
44
+ - [Media](#media)
28
45
  - [Email](#email)
29
- - [Events & Workflows](#events--workflows)
30
46
  - [Notifications & Logs](#notifications--logs)
47
+ - [Queue Management](#queue-management)
48
+ - [Health & Diagnostics](#health--diagnostics)
49
+ - [Webhooks](#webhooks)
31
50
  - [Enterprise Capabilities](#enterprise-capabilities)
32
51
  - [Auto-Paginating Iterators](#auto-paginating-iterators)
33
52
  - [Bulk Data Chunking](#bulk-data-chunking)
@@ -48,9 +67,6 @@ pnpm add @ecodrix/erix-api
48
67
 
49
68
  # npm
50
69
  npm install @ecodrix/erix-api
51
-
52
- # yarn
53
- yarn add @ecodrix/erix-api
54
70
  ```
55
71
 
56
72
  > **Requires**: Node.js >= 18
@@ -63,8 +79,8 @@ yarn add @ecodrix/erix-api
63
79
  import { Ecodrix } from "@ecodrix/erix-api";
64
80
 
65
81
  const ecod = new Ecodrix({
66
- apiKey: "your_api_key",
67
- clientCode: "YOUR_CLIENT_CODE", // Your tenant ID
82
+ apiKey: process.env.ECOD_API_KEY!,
83
+ clientCode: process.env.ECOD_CLIENT_CODE,
68
84
  });
69
85
 
70
86
  // Send a WhatsApp message
@@ -75,12 +91,17 @@ await ecod.whatsapp.messages.send({
75
91
 
76
92
  // Create a CRM lead
77
93
  const lead = await ecod.crm.leads.create({
78
- firstName: "Jhon",
94
+ firstName: "Priya",
79
95
  phone: "+919876543210",
80
96
  source: "website",
81
97
  });
82
98
 
83
- console.log(lead.data.id);
99
+ // Fire an automation trigger
100
+ await ecod.events.trigger({
101
+ trigger: "trial_started",
102
+ phone: "+919876543210",
103
+ createLeadIfMissing: true,
104
+ });
84
105
  ```
85
106
 
86
107
  ---
@@ -92,7 +113,7 @@ Pass an options object to `new Ecodrix(options)`:
92
113
  | Option | Type | Required | Default | Description |
93
114
  | ------------ | -------- | ----------- | ------------------------- | ---------------------------------------------- |
94
115
  | `apiKey` | `string` | ✅ Yes | — | Your ECOD Platform API key |
95
- | `clientCode` | `string` | Recommended | — | Your tenant ID (scopes all requests) |
116
+ | `clientCode` | `string` | Recommended | — | Your tenant ID scopes all requests |
96
117
  | `baseUrl` | `string` | No | `https://api.ecodrix.com` | Override the API base URL (e.g. for local dev) |
97
118
  | `socketUrl` | `string` | No | Same as `baseUrl` | Override the Socket.io server URL |
98
119
 
@@ -108,22 +129,12 @@ const ecod = new Ecodrix({
108
129
 
109
130
  ## Resources
110
131
 
111
- Every resource follows the same predictable CRUD interface:
112
-
113
- | Method | Description |
114
- | --------------------- | ---------------------------------- |
115
- | `.create(params)` | Create a new record |
116
- | `.list(params?)` | List records with optional filters |
117
- | `.retrieve(id)` | Get a single record by ID |
118
- | `.update(id, params)` | Update a record |
119
- | `.delete(id)` | Delete or cancel a record |
120
-
121
- ---
122
-
123
132
  ### WhatsApp
124
133
 
125
134
  Access via `ecod.whatsapp`.
126
135
 
136
+ ---
137
+
127
138
  #### `ecod.whatsapp.messages`
128
139
 
129
140
  **Send a text message**
@@ -146,355 +157,651 @@ await ecod.whatsapp.messages.send({
146
157
  });
147
158
  ```
148
159
 
149
- **Send a pre-approved template message**
160
+ **Send a template message via queue**
150
161
 
151
162
  ```typescript
152
163
  await ecod.whatsapp.messages.sendTemplate({
153
164
  to: "+919876543210",
154
165
  templateName: "appointment_reminder",
155
166
  language: "en_US",
156
- variables: ["Dhanesh", "Tomorrow 10AM"],
167
+ variables: ["Priya", "Tomorrow 10AM"],
157
168
  });
158
169
  ```
159
170
 
160
- **Send a Meta-approved direct template (Bypass queues)**
161
-
162
- Use this specifically to dispatch high-priority utility templates containing dynamic variable maps.
171
+ **Mark messages as read**
163
172
 
164
173
  ```typescript
165
- const { messageId } = await ecod.whatsapp.sendTemplate({
166
- phone: "+919876543210",
167
- templateName: "payment_confirmation",
168
- variables: {
169
- name: "Dhanesh",
170
- amount: "₹1,500",
171
- },
172
- });
174
+ await ecod.whatsapp.messages.markRead("message_id");
173
175
  ```
174
176
 
175
- **Mark a conversation as read**
176
-
177
- ```typescript
178
- await ecod.whatsapp.messages.markRead("conversation_id");
179
- ```
177
+ ---
180
178
 
181
179
  #### `ecod.whatsapp.conversations`
182
180
 
183
181
  ```typescript
184
182
  // List conversations (cursor-based pagination)
185
- const { data } = await ecod.whatsapp.conversations.list({ limit: 20 });
183
+ const { data } = await ecod.whatsapp.conversations.list({
184
+ limit: 20,
185
+ status: "open",
186
+ after: "cursor_token",
187
+ });
186
188
 
187
189
  // Get a specific conversation
188
190
  const conv = await ecod.whatsapp.conversations.retrieve("conversation_id");
189
191
 
190
- // Archive a conversation
192
+ // Get messages in a conversation
193
+ const { data: msgs } = await ecod.whatsapp.conversations.messages("conversation_id", {
194
+ limit: 50,
195
+ });
196
+
197
+ // Create a conversation explicitly
198
+ await ecod.whatsapp.conversations.create({ phone: "+919876543210", name: "Priya" });
199
+
200
+ // Link a conversation to a CRM lead
201
+ await ecod.whatsapp.conversations.linkLead("conversation_id", "lead_id");
202
+
203
+ // Mark a conversation as read (clears unread badge)
204
+ await ecod.whatsapp.conversations.markRead("conversation_id");
205
+
206
+ // Delete / archive a conversation
191
207
  await ecod.whatsapp.conversations.delete("conversation_id");
208
+
209
+ // Bulk delete conversations
210
+ await ecod.whatsapp.conversations.bulkDelete(["conv_1", "conv_2"]);
192
211
  ```
193
212
 
194
213
  ---
195
214
 
196
- ### CRM — Leads
215
+ #### `ecod.whatsapp.templates`
197
216
 
198
- Access via `ecod.crm.leads`.
217
+ ```typescript
218
+ // List pre-approved Meta templates
219
+ const { data } = await ecod.whatsapp.templates.list();
199
220
 
200
- **Create a Lead**
221
+ // Sync templates from the Meta Business Account
222
+ await ecod.whatsapp.templates.sync();
223
+ ```
224
+
225
+ ---
226
+
227
+ #### `ecod.whatsapp.broadcasts`
228
+
229
+ Send a personalised WhatsApp template message to multiple recipients at once.
201
230
 
202
231
  ```typescript
203
- const { data: lead } = await ecod.crm.leads.create({
204
- firstName: "Dhanesh",
205
- lastName: "Kumar",
232
+ await ecod.whatsapp.broadcasts.create({
233
+ name: "April Promo",
234
+ templateName: "discount_offer",
235
+ templateLanguage: "en_US",
236
+ recipients: [
237
+ { phone: "+919876543210", variables: ["Priya", "20%"] },
238
+ { phone: "+919123456789", variables: ["Ravi", "30%"] },
239
+ ],
240
+ });
241
+
242
+ // List past broadcasts
243
+ const { data } = await ecod.whatsapp.broadcasts.list({ status: "completed" });
244
+ ```
245
+
246
+ ---
247
+
248
+ #### `ecod.whatsapp.sendTemplate` — Direct Template (Bypass Queue)
249
+
250
+ Use this for high-priority utility templates that must deliver immediately outside the automation queue.
251
+
252
+ ```typescript
253
+ const result = await ecod.whatsapp.sendTemplate({
206
254
  phone: "+919876543210",
207
- email: "dhanesh@example.com",
208
- source: "website", // 'website' | 'whatsapp' | 'direct' | ...
209
- metadata: {
210
- utmSource: "google",
255
+ templateName: "payment_confirmation",
256
+ variables: {
257
+ name: "Priya",
258
+ amount: "₹1,500",
211
259
  },
212
260
  });
261
+ // result.messageId → WA message ID
262
+ ```
263
+
264
+ ---
265
+
266
+ ### CRM
267
+
268
+ Access via `ecod.crm`.
269
+
270
+ ---
271
+
272
+ #### `ecod.crm.leads`
273
+
274
+ **Create**
275
+
276
+ ```typescript
277
+ const { data: lead } = await ecod.crm.leads.create({
278
+ firstName: "Priya",
279
+ lastName: "Sharma",
280
+ phone: "+919876543210",
281
+ email: "priya@example.com",
282
+ source: "website",
283
+ pipelineId: "pipeline_id",
284
+ stageId: "stage_id",
285
+ metadata: { utmSource: "google" },
286
+ });
287
+ ```
288
+
289
+ **Upsert by phone** — Creates if absent, updates if exists
290
+
291
+ ```typescript
292
+ await ecod.crm.leads.upsert({
293
+ leadData: { phone: "+919876543210", firstName: "Priya" },
294
+ trigger: "webinar_joined",
295
+ });
213
296
  ```
214
297
 
215
- **List Leads (with filters)**
298
+ **List with filters**
216
299
 
217
300
  ```typescript
218
- const { data: leads } = await ecod.crm.leads.list({
219
- status: "new", // filter by status
301
+ const { data } = await ecod.crm.leads.list({
302
+ status: "new",
220
303
  source: "whatsapp",
304
+ pipelineId: "pipeline_id",
221
305
  page: 1,
222
306
  limit: 25,
223
307
  });
224
308
  ```
225
309
 
226
- _(See [Enterprise Capabilities](#enterprise-capabilities) for efficient auto-pagination or bulk-creation tricks like `listAutoPaging` and `createMany`)._
227
-
228
- **Retrieve a Lead by ID**
310
+ **Retrieve**
229
311
 
230
312
  ```typescript
231
313
  const { data: lead } = await ecod.crm.leads.retrieve("lead_id");
314
+ const { data } = await ecod.crm.leads.retrieveByPhone("+919876543210");
315
+ const { data } = await ecod.crm.leads.retrieveByRef("orderId", "ORD-123");
232
316
  ```
233
317
 
234
- **Update a Lead**
318
+ **Update**
235
319
 
236
320
  ```typescript
237
- await ecod.crm.leads.update("lead_id", {
238
- email: "new@email.com",
239
- metadata: { score: 95 },
321
+ await ecod.crm.leads.update("lead_id", { email: "new@email.com" });
322
+ await ecod.crm.leads.updateMetadata("lead_id", {
323
+ refs: { orderId: "ORD-456" },
324
+ extra: { plan: "pro" },
240
325
  });
241
326
  ```
242
327
 
243
- **Delete / Archive a Lead**
328
+ **Move & Convert**
329
+
330
+ ```typescript
331
+ await ecod.crm.leads.move("lead_id", "target_stage_id");
332
+ await ecod.crm.leads.convert("lead_id", "won", "Signed contract");
333
+ ```
334
+
335
+ **Tags**
244
336
 
245
337
  ```typescript
338
+ await ecod.crm.leads.tags("lead_id", { add: ["vip", "hot"], remove: ["cold"] });
339
+ ```
340
+
341
+ **Score**
342
+
343
+ ```typescript
344
+ await ecod.crm.leads.recalculateScore("lead_id");
345
+ ```
346
+
347
+ **Bulk**
348
+
349
+ ```typescript
350
+ await ecod.crm.leads.import(leadArray); // Bulk import
246
351
  await ecod.crm.leads.delete("lead_id");
352
+ await ecod.crm.leads.bulkDelete(["id_1", "id_2"]);
247
353
  ```
248
354
 
249
355
  ---
250
356
 
251
- ### Meetings
357
+ #### `ecod.crm.pipelines`
252
358
 
253
- Access via `ecod.meet`. Backed by **Google Meet**.
359
+ ```typescript
360
+ // CRUD
361
+ const { data } = await ecod.crm.pipelines.list();
362
+ await ecod.crm.pipelines.create({ name: "Sales", isDefault: true });
363
+ await ecod.crm.pipelines.retrieve("pipeline_id");
364
+ await ecod.crm.pipelines.update("pipeline_id", { name: "Deals" });
365
+ await ecod.crm.pipelines.delete("pipeline_id");
366
+
367
+ // Advanced
368
+ await ecod.crm.pipelines.setDefault("pipeline_id");
369
+ await ecod.crm.pipelines.duplicate("pipeline_id", "Sales Copy");
370
+ await ecod.crm.pipelines.archive("pipeline_id");
371
+ const { data: board } = await ecod.crm.pipelines.board("pipeline_id");
372
+ const { data: forecast } = await ecod.crm.pipelines.forecast("pipeline_id");
373
+
374
+ // Stage management
375
+ await ecod.crm.pipelines.addStage("pipeline_id", { name: "Negotiation", color: "#f59e0b" });
376
+ await ecod.crm.pipelines.reorderStages("pipeline_id", ["stage_1", "stage_3", "stage_2"]);
377
+ await ecod.crm.pipelines.updateStage("stage_id", { probability: 80 });
378
+ await ecod.crm.pipelines.deleteStage("stage_id", "fallback_stage_id");
379
+ ```
254
380
 
255
- **Schedule a Meeting**
381
+ ---
382
+
383
+ #### `ecod.crm.automations`
384
+
385
+ ```typescript
386
+ // CRUD
387
+ const { data } = await ecod.crm.automations.list();
388
+ await ecod.crm.automations.create({ name: "Follow-up", trigger: "lead_created", nodes: [], edges: [] });
389
+ await ecod.crm.automations.update("rule_id", { isActive: false });
390
+ await ecod.crm.automations.toggle("rule_id");
391
+ await ecod.crm.automations.deleteRule("rule_id");
392
+ await ecod.crm.automations.bulkDelete(["rule_1", "rule_2"]);
393
+
394
+ // Testing & events
395
+ await ecod.crm.automations.test("rule_id", "lead_id");
396
+ const { data: events } = await ecod.crm.automations.getAvailableEvents();
397
+
398
+ // Enrollment management
399
+ const { data } = await ecod.crm.automations.enrollments("rule_id", { status: "active", page: 1 });
400
+ await ecod.crm.automations.getEnrollment("enrollment_id");
401
+ await ecod.crm.automations.pauseEnrollment("rule_id", "enrollment_id");
402
+ await ecod.crm.automations.resumeEnrollment("rule_id", "enrollment_id");
403
+
404
+ // Run inspection
405
+ const { data: runs } = await ecod.crm.automations.runs("rule_id");
406
+ await ecod.crm.automations.getRun("run_id");
407
+ await ecod.crm.automations.resumeRun("run_id");
408
+ await ecod.crm.automations.abortRun("run_id");
409
+
410
+ // Unlock a wait_event node from external tool (e.g. Zapier, payment gateway callback)
411
+ await ecod.crm.automations.webhookEvent("rule_id", "payment_received", {
412
+ amount: 1500,
413
+ currency: "INR",
414
+ });
415
+ ```
416
+
417
+ ---
418
+
419
+ #### `ecod.crm.sequences`
420
+
421
+ Manually enroll / unenroll leads in drip automation sequences.
256
422
 
257
423
  ```typescript
258
- const { data: meeting } = await ecod.meet.create({
424
+ await ecod.crm.sequences.enroll({
259
425
  leadId: "lead_id",
260
- participantName: "Dhanesh Kumar",
261
- participantPhone: "+919876543210",
262
- startTime: "2026-04-10T10:00:00.000Z",
263
- endTime: "2026-04-10T10:30:00.000Z",
426
+ ruleId: "rule_id",
427
+ variables: { discount: "20%" },
264
428
  });
265
-
266
- console.log(meeting.meetLink); // https://meet.google.com/abc-defg-hij
429
+ await ecod.crm.sequences.unenroll("enrollment_id");
430
+ const { data } = await ecod.crm.sequences.listForLead("lead_id");
267
431
  ```
268
432
 
269
- **List Meetings**
433
+ ---
434
+
435
+ #### `ecod.crm.activities`
270
436
 
271
437
  ```typescript
272
- const { data: meetings } = await ecod.meet.list({ status: "scheduled" });
438
+ // Lead timeline (all CRM events in order)
439
+ const { data } = await ecod.crm.activities.timeline("lead_id", { page: 1, limit: 50 });
440
+
441
+ // Filtered activity list
442
+ const { data } = await ecod.crm.activities.list("lead_id", { type: "call" });
443
+
444
+ // Log a call outcome
445
+ await ecod.crm.activities.logCall("lead_id", {
446
+ outcome: "answered",
447
+ duration: 120,
448
+ notes: "Interested in Pro plan",
449
+ });
450
+
451
+ // Log a generic activity
452
+ await ecod.crm.activities.log({
453
+ leadId: "lead_id",
454
+ type: "email_opened",
455
+ title: "Campaign Email Opened",
456
+ });
457
+
458
+ // --- Notes ---
459
+ const { data: notes } = await ecod.crm.activities.notes.list("lead_id");
460
+ await ecod.crm.activities.notes.create("lead_id", { content: "Call scheduled for Monday" });
461
+ await ecod.crm.activities.notes.update("note_id", "Updated note text");
462
+ await ecod.crm.activities.notes.pin("note_id"); // pin to top
463
+ await ecod.crm.activities.notes.pin("note_id", false); // unpin
273
464
  ```
274
465
 
275
- **Retrieve a Meeting**
466
+ ---
467
+
468
+ #### `ecod.crm.analytics`
276
469
 
277
470
  ```typescript
278
- const { data } = await ecod.meet.retrieve("meeting_id");
471
+ const { data: overview } = await ecod.crm.analytics.overview("pipeline_id");
472
+ const { data: conversion } = await ecod.crm.analytics.conversionRate("pipeline_id");
473
+ const { data: velocity } = await ecod.crm.analytics.dealVelocity("pipeline_id");
474
+ const { data: stageTime } = await ecod.crm.analytics.stageTime("pipeline_id");
279
475
  ```
280
476
 
281
- **Reschedule a Meeting**
477
+ ---
478
+
479
+ #### `ecod.crm.scoring`
282
480
 
283
481
  ```typescript
284
- await ecod.meet.update("meeting_id", {
285
- startTime: "2026-04-11T11:00:00.000Z",
286
- endTime: "2026-04-11T11:30:00.000Z",
287
- });
482
+ const { data } = await ecod.crm.scoring.getConfig();
483
+ await ecod.crm.scoring.updateConfig({ rules: [...] });
288
484
  ```
289
485
 
290
- **Cancel a Meeting**
486
+ ---
487
+
488
+ #### `ecod.crm.payments`
291
489
 
292
490
  ```typescript
293
- await ecod.meet.delete("meeting_id");
491
+ const { data } = await ecod.crm.payments.list("lead_id");
294
492
  ```
295
493
 
296
494
  ---
297
495
 
298
- ### Storage
496
+ #### `ecod.crm.automationDashboard`
299
497
 
300
- Access via `ecod.storage`. Powered by **Cloudflare R2**.
498
+ ```typescript
499
+ const { data: stats } = await ecod.crm.automationDashboard.stats();
500
+ const { data: logs } = await ecod.crm.automationDashboard.logs({ limit: 10, status: "failed" });
501
+ await ecod.crm.automationDashboard.retryFailedEvent("log_id");
502
+ ```
301
503
 
302
- **Upload a File (Elite Helper)**
504
+ ---
303
505
 
304
- This single call handles the entire R2 presigned-URL orchestration for you:
506
+ ### Events & Workflows
305
507
 
306
- 1. Requests a presigned PUT URL from the backend.
307
- 2. Uploads the file directly to R2 (no proxy overhead).
308
- 3. Confirms the upload with the backend.
508
+ Access via `ecod.events`. Connect your external applications to the CRM automation engine.
309
509
 
310
- ```typescript
311
- // Node.js: from a Buffer
312
- import { readFileSync } from "fs";
510
+ **List all available triggers**
313
511
 
314
- const fileBuffer = readFileSync("./contract.pdf");
315
- const { data } = await ecod.storage.upload(fileBuffer, {
316
- folder: "customer_documents",
317
- filename: "contract.pdf",
318
- contentType: "application/pdf",
319
- });
512
+ ```typescript
513
+ const { data: triggers } = await ecod.events.list();
514
+ ```
320
515
 
321
- console.log(data.url); // https://cdn.ecodrix.com/customer_documents/contract.pdf
516
+ **Register a custom event**
322
517
 
323
- // Browser: from an <input> file
324
- const file = document.getElementById("file-input").files[0];
325
- const { data } = await ecod.storage.upload(file, {
326
- folder: "avatars",
327
- filename: file.name,
328
- contentType: file.type,
329
- });
330
- filename: "dhanesh.png",
331
- contentType: "image/png"
518
+ ```typescript
519
+ await ecod.events.assign({
520
+ name: "cart_abandoned",
521
+ displayName: "Shopping Cart Abandoned",
522
+ pipelineId: "pipeline_123",
332
523
  });
333
524
 
334
- console.log(data.url);
335
- // -> https://cdn.ecodrix.com/avatars/dhanesh.png
525
+ // Deactivate
526
+ await ecod.events.unassign("cart_abandoned");
527
+ await ecod.events.unassignBulk(["event_a", "event_b"]);
336
528
  ```
337
529
 
338
- **Get a Presigned URL for Private Assets**
530
+ **Fire an event / trigger workflows**
339
531
 
340
- ```javascript
341
- const { data } = await ecod.media.getDownloadUrl("confidential/contract.pdf");
532
+ ```typescript
533
+ await ecod.events.trigger({
534
+ trigger: "cart_abandoned",
535
+ phone: "+919876543210",
536
+ variables: { items: "T-Shirt, Mug", total: "₹850" },
537
+ createLeadIfMissing: true,
538
+ delayMinutes: 30,
539
+ // Optional: auto-book a Google Meet during the workflow
540
+ requiresMeet: true,
541
+ meetConfig: {
542
+ summary: "Follow-up call",
543
+ duration: 30,
544
+ timezone: "Asia/Kolkata",
545
+ },
546
+ });
342
547
  ```
343
548
 
344
- **Monitor Quota & Usage**
549
+ **Custom event definitions (CRM-managed)**
345
550
 
346
- ```javascript
347
- const { data: usage } = await ecod.media.getUsage();
348
- console.log(`${usage.usedMB} MB used of ${usage.limitMB} MB`);
551
+ ```typescript
552
+ const { data } = await ecod.events.listCustomEvents();
553
+ await ecod.events.createCustomEvent({ name: "webinar_attended", displayName: "Webinar Attended" });
554
+ await ecod.events.deleteCustomEvent("event_id");
555
+ await ecod.events.emit({ eventName: "webinar_attended", leadId: "lead_id" });
349
556
  ```
350
557
 
351
558
  ---
352
559
 
353
- ### Email
560
+ ### Marketing
561
+
562
+ Access via `ecod.marketing`.
354
563
 
355
- Access via `ecod.email`.
564
+ **Email campaigns**
356
565
 
357
- **Send an Email Campaign**
566
+ ```typescript
567
+ await ecod.marketing.emails.sendCampaign({
568
+ recipients: ["user@example.com"],
569
+ subject: "Summer Sale!",
570
+ html: "<h1>Get 20% off!</h1>",
571
+ });
572
+ await ecod.marketing.emails.sendTest("dev@example.com");
573
+ ```
358
574
 
359
- Dispatch an HTML email campaign to a given array of recipients. Powered by Ecodrix or AWS SES depending on tenant configuration.
575
+ **Campaigns (Email + SMS)**
360
576
 
361
577
  ```typescript
362
- const { success } = await ecod.email.sendEmailCampaign({
363
- subject: "Summer Subscription Discount!",
364
- recipients: ["user@example.com", "lead@example.com"],
365
- html: "<h1>Get 20% off all plans today!</h1>",
578
+ await ecod.marketing.campaigns.create({ name: "Q2 Push", type: "email" });
579
+ await ecod.marketing.campaigns.list({ status: "active" });
580
+ await ecod.marketing.campaigns.retrieve("campaign_id");
581
+ await ecod.marketing.campaigns.update("campaign_id", { subject: "New subject" });
582
+ await ecod.marketing.campaigns.send("campaign_id", { scheduledAt: "2026-05-01T09:00:00Z" });
583
+ await ecod.marketing.campaigns.stats("campaign_id");
584
+ await ecod.marketing.campaigns.delete("campaign_id");
585
+ ```
586
+
587
+ **WhatsApp marketing (CRM-integrated template dispatch)**
588
+
589
+ Unlike the direct `ecod.whatsapp.sendTemplate`, this endpoint resolves CRM field variables automatically.
590
+
591
+ ```typescript
592
+ await ecod.marketing.whatsapp.sendTemplate({
593
+ phone: "+919876543210",
594
+ templateName: "seasonal_offer",
595
+ variables: { discount: "40%" },
366
596
  });
367
597
  ```
368
598
 
369
- **Send a Test Email**
599
+ ---
370
600
 
371
- Send a system verification email to validate SMTP configuration for your tenant.
601
+ ### Meetings
602
+
603
+ Access via `ecod.meet`. Backed by **Google Meet**.
372
604
 
373
605
  ```typescript
374
- const { success } = await ecod.email.sendTestEmail("admin@example.com");
606
+ const { data: meeting } = await ecod.meet.create({
607
+ leadId: "lead_id",
608
+ participantName: "Priya Sharma",
609
+ participantPhone: "+919876543210",
610
+ startTime: "2026-04-10T10:00:00.000Z",
611
+ endTime: "2026-04-10T10:30:00.000Z",
612
+ });
613
+ console.log(meeting.meetLink); // → https://meet.google.com/abc-defg-hij
614
+
615
+ const { data: meetings } = await ecod.meet.list({ status: "scheduled" });
616
+ const { data } = await ecod.meet.retrieve("meeting_id");
617
+ await ecod.meet.update("meeting_id", { startTime: "2026-04-11T11:00:00.000Z" });
618
+ await ecod.meet.delete("meeting_id");
375
619
  ```
376
620
 
377
621
  ---
378
622
 
379
- ### Events & Workflows
623
+ ### Storage
380
624
 
381
- Access via `ecod.events`. Programmatically integrate the CRM's automation engine with your external apps.
625
+ Access via `ecod.storage`. Powered by **Cloudflare R2**.
382
626
 
383
- **Retrieve Global Triggers**
627
+ This handles the full presigned-URL orchestration transparently:
384
628
 
385
- Fetch all active automation triggers belonging to the current tenant.
629
+ 1. Requests a presigned PUT URL from the backend.
630
+ 2. Uploads directly to R2 (no proxy overhead).
631
+ 3. Confirms the upload.
386
632
 
387
633
  ```typescript
388
- const { data: triggers } = await ecod.events.list();
634
+ // Node.js: from a Buffer
635
+ import { readFileSync } from "fs";
636
+
637
+ const fileBuffer = readFileSync("./contract.pdf");
638
+ const { data } = await ecod.storage.upload(fileBuffer, {
639
+ folder: "customer_documents",
640
+ filename: "contract.pdf",
641
+ contentType: "application/pdf",
642
+ });
643
+ console.log(data.url); // → https://cdn.ecodrix.com/customer_documents/contract.pdf
644
+
645
+ // Browser: from an <input> element
646
+ const file = document.getElementById("file-input").files[0];
647
+ const { data } = await ecod.storage.upload(file, {
648
+ folder: "avatars",
649
+ filename: file.name,
650
+ contentType: file.type,
651
+ });
389
652
  ```
390
653
 
391
- **Register a Custom Event Definition**
654
+ ---
655
+
656
+ ### Media
392
657
 
393
- Register a new entry point that you want to show up in the CRM Automation Builder.
658
+ Access via `ecod.media`. Manage files stored in R2.
394
659
 
395
660
  ```typescript
396
- await ecod.events.assign({
397
- name: "cart_abandoned",
398
- displayName: "Shopping Cart Abandoned",
399
- pipelineId: "pipeline_123" /* Auto map leads to this pipeline */,
400
- });
661
+ // Get a presigned download URL for a private file
662
+ const { data } = await ecod.media.getDownloadUrl("confidential/contract.pdf");
663
+
664
+ // Monitor storage quota
665
+ const { data: usage } = await ecod.media.getUsage();
666
+ console.log(`${usage.usedMB} MB of ${usage.limitMB} MB used`);
667
+
668
+ // List files in a folder
669
+ await ecod.media.list({ folder: "invoices" });
670
+
671
+ // Delete a file
672
+ await ecod.media.delete("invoices/old_invoice.pdf");
401
673
  ```
402
674
 
403
- **Emit a Payload / Trigger Workflow**
675
+ ---
676
+
677
+ ### Email
404
678
 
405
- Inject generic data into the `EventBus` to fire customized automation rules.
679
+ Access via `ecod.email`. Backed by SMTP or AWS SES depending on tenant configuration.
406
680
 
407
681
  ```typescript
408
- await ecod.events.trigger({
409
- trigger: "cart_abandoned",
410
- phone: "+919876543210", // Primary matching key
411
- variables: { items: "T-Shirt, Mug", total: "₹850" },
412
- createLeadIfMissing: true, // Upsert lead if it's a new customer
682
+ await ecod.email.sendEmailCampaign({
683
+ subject: "Summer Discount!",
684
+ recipients: ["user@example.com"],
685
+ html: "<h1>Get 20% off all plans!</h1>",
413
686
  });
687
+
688
+ // Verify SMTP configuration
689
+ await ecod.email.sendTestEmail("admin@example.com");
414
690
  ```
415
691
 
416
692
  ---
417
693
 
418
694
  ### Notifications & Logs
419
695
 
420
- Access via `ecod.notifications`. View automation and webhook audit logs.
421
-
422
- **List Automation Execution Logs**
696
+ Access via `ecod.notifications`.
423
697
 
424
698
  ```typescript
699
+ // Automation execution logs
425
700
  const { data: logs } = await ecod.notifications.listLogs({
426
701
  trigger: "lead_created",
427
- status: "failed", // filter to only failed automations
702
+ status: "failed",
428
703
  startDate: "2026-04-01",
429
704
  endDate: "2026-04-30",
430
- limit: 50,
431
705
  });
432
- ```
433
-
434
- **Retrieve a Specific Log**
435
-
436
- ```typescript
437
- const { data: log } = await ecod.notifications.retrieveLog("log_id");
438
- ```
439
706
 
440
- **Get Automation Stats Summary**
707
+ await ecod.notifications.retrieveLog("log_id");
441
708
 
442
- ```typescript
709
+ // Aggregate stats
443
710
  const { data: stats } = await ecod.notifications.getStats({
444
711
  startDate: "2026-04-01",
445
712
  endDate: "2026-04-30",
446
713
  });
714
+
715
+ // Provider webhook callback logs
716
+ const { data } = await ecod.notifications.listCallbacks({ limit: 20 });
717
+ ```
718
+
719
+ ---
720
+
721
+ ### Queue Management
722
+
723
+ Access via `ecod.queue`. Inspect and manage background automation jobs.
724
+
725
+ ```typescript
726
+ // View failed jobs
727
+ const { data: failed } = await ecod.queue.listFailed();
728
+
729
+ // Get queue health by status
730
+ const stats = await ecod.queue.getStats();
731
+ // stats → { waiting: 12, active: 3, completed: 482, failed: 7, delayed: 0 }
732
+
733
+ // Retry a failed job
734
+ await ecod.queue.retryJob("job_id");
735
+
736
+ // Remove a job permanently
737
+ await ecod.queue.deleteJob("job_id");
447
738
  ```
448
739
 
449
- **List Provider Callback Logs (Webhooks)**
740
+ ---
741
+
742
+ ### Health & Diagnostics
743
+
744
+ Access via `ecod.health`.
450
745
 
451
746
  ```typescript
452
- const { data: callbacks } = await ecod.notifications.listCallbacks({
453
- limit: 20,
454
- });
747
+ // Global platform status
748
+ const health = await ecod.health.system();
749
+ // { status: "ok", version: "...", env: "production", uptime: 3600, db: "connected", queueDepth: 5 }
750
+
751
+ // Tenant-scoped service readiness
752
+ const clientHealth = await ecod.health.clientHealth();
753
+ // { clientCode: "...", services: { whatsapp: "connected", email: "configured", googleMeet: "configured" }, ... }
754
+
755
+ // Background job status lookup
756
+ const job = await ecod.health.jobStatus("job_id");
455
757
  ```
456
758
 
457
759
  ---
458
760
 
459
- ## Enterprise Capabilities
761
+ ### Webhooks
460
762
 
461
- The SDK is equipped with state-of-the-art native patterns for robust execution, zero-downtime performance, and unparalleled developer experience. All network requests automatically utilize an exponential-backoff retry engine to gracefully handle temporary network errors.
763
+ Access via `ecod.webhooks`.
764
+
765
+ > ⚠️ **Node.js only** — Requires `node:crypto`. Not available in browser bundles.
766
+
767
+ ---
768
+
769
+ ## Enterprise Capabilities
462
770
 
463
771
  ### Auto-Paginating Iterators
464
772
 
465
- To rapidly ingest records without manually iterating pages, use standard Node.js `for await` loops. The SDK controls memory buffers and page fetching dynamically.
773
+ Stream all records without manual page management:
466
774
 
467
775
  ```typescript
468
- // Look how beautiful this developer experience is:
469
- for await (const lead of ecod.crm.leads.listAutoPaging({ status: "won" })) {
776
+ // With optional type parameter for full type safety
777
+ for await (const lead of ecod.crm.leads.listAutoPaging<Lead>({ status: "won" })) {
470
778
  await syncToMyDatabase(lead);
471
779
  }
472
780
  ```
473
781
 
474
782
  ### Bulk Data Chunking
475
783
 
476
- Need to insert 5,000 leads at once but worried about network congestion or rate limits? `createMany` automatically chunks arrays into concurrent streams.
784
+ Insert thousands of leads without hitting rate limits. `createMany` automatically chunks and concurrently streams batches:
477
785
 
478
786
  ```typescript
479
- // The users array chunked into safe parallel batched requests
480
787
  const results = await ecod.crm.leads.createMany(massiveArrayOfLeads, 100);
481
- console.log(`Successfully ingested ${results.length} leads.`);
788
+ console.log(`Ingested ${results.length} leads.`);
482
789
  ```
483
790
 
484
791
  ### Idempotency Keys
485
792
 
486
- Because the SDK provides an automatic network retry engine (`axios-retry`), temporary blips could duplicate events. Passing an `idempotencyKey` strictly tells the backend: "Only execute this once, even if I accidentally retry the same payload."
793
+ Prevent duplicate requests caused by automatic retries:
487
794
 
488
795
  ```typescript
489
796
  await ecod.email.sendEmailCampaign(
490
- { subject: "Promo", recipients: ["user@example.com"] },
491
- { idempotencyKey: "promo_campaign_uuid_123_abc" },
797
+ { subject: "Promo", recipients: ["user@example.com"], html: "..." },
798
+ { idempotencyKey: "promo_campaign_uuid_2026_q2" },
492
799
  );
493
800
  ```
494
801
 
495
802
  ### Webhook Signature Verification
496
803
 
497
- Verify cryptographic webhook payloads issued by the ECODrIx platform reliably in Node environments, mitigating spoofing attacks seamlessly.
804
+ Cryptographically verify that webhook payloads originated from ECODrIx:
498
805
 
499
806
  ```typescript
500
807
  app.post(
@@ -505,9 +812,10 @@ app.post(
505
812
  const event = await ecod.webhooks.constructEvent(
506
813
  req.body.toString(),
507
814
  req.headers["x-ecodrix-signature"],
508
- "whsec_your_secret_key",
815
+ "whsec_your_webhook_secret",
509
816
  );
510
- console.log("Verified event received:", event);
817
+ console.log("Verified event:", event);
818
+ res.json({ received: true });
511
819
  } catch (err) {
512
820
  return res.status(400).send(`Invalid signature: ${err.message}`);
513
821
  }
@@ -517,11 +825,10 @@ app.post(
517
825
 
518
826
  ### Raw Execution Engine
519
827
 
520
- Need to execute an experimental, undocumented, or beta endpoint but still want full authentication mapping, global headers, and intelligent retries?
828
+ Access any API endpoint — including experimental or custom ones with full authentication and retry benefits:
521
829
 
522
830
  ```typescript
523
- // Bypass strictly typed resources with full network resiliency
524
- const customData = await ecod.request("POST", "/api/saas/beta-feature-route", {
831
+ const { data } = await ecod.request("POST", "/api/saas/beta-feature", {
525
832
  experimentalFlag: true,
526
833
  });
527
834
  ```
@@ -530,46 +837,41 @@ const customData = await ecod.request("POST", "/api/saas/beta-feature-route", {
530
837
 
531
838
  ## Real-time Events
532
839
 
533
- The SDK maintains a persistent **Socket.io** connection. Subscribe to live platform events using `ecod.on(event, handler)`.
534
-
535
- ```typescript
536
- const ecod = new Ecodrix({ apiKey: "...", clientCode: "..." });
537
-
538
- // New incoming WhatsApp message
539
- ecod.on("whatsapp.message_received", (data) => {
540
- console.log(`New message from ${data.from}: ${data.body}`);
541
- });
542
-
543
- // Lead stage changed in CRM
544
- ecod.on("crm.lead_updated", (data) => {
545
- console.log(`Lead ${data.leadId} moved to stage: ${data.stageName}`);
546
- });
547
-
548
- // Automation or event failed — for alert pipelines
549
- ecod.on("automation.failed", (data) => {
550
- console.error(`Automation failed: ${data.trigger} ${data.reason}`);
551
- });
552
-
553
- // Storage upload completed
554
- ecod.on("storage.upload_confirmed", (data) => {
555
- console.log(`File available at: ${data.url}`);
556
- });
840
+ The SDK maintains a persistent Socket.io connection, scoped to your `clientCode` tenant. Use `ecod.on()` to subscribe:
841
+
842
+ ```typescript
843
+ ecod
844
+ .on("whatsapp.message_received", (msg) => {
845
+ console.log(`New message from ${msg.from}: ${msg.body}`);
846
+ })
847
+ .on("crm.lead_created", ({ leadId }) => {
848
+ console.log(`New lead: ${leadId}`);
849
+ })
850
+ .on("crm.lead_updated", ({ leadId, stageName }) => {
851
+ console.log(`${leadId} moved to ${stageName}`);
852
+ })
853
+ .on("meet.scheduled", ({ meetLink }) => {
854
+ console.log(`Meeting booked: ${meetLink}`);
855
+ })
856
+ .on("automation.failed", ({ trigger, reason }) => {
857
+ alertTeam(`Automation ${trigger} failed: ${reason}`);
858
+ });
557
859
 
558
- // Disconnect when done (e.g. server shutdown)
559
- ecod.disconnect();
860
+ // Graceful shutdown
861
+ process.on("SIGTERM", () => ecod.disconnect());
560
862
  ```
561
863
 
562
- ### Standard Event Names
864
+ ### Standard Event Reference
563
865
 
564
- | Event | Payload | Description |
565
- | --------------------------- | -------------------------------- | --------------------------- |
566
- | `whatsapp.message_received` | `{ from, body, conversationId }` | Inbound WhatsApp message |
567
- | `whatsapp.message_sent` | `{ to, messageId }` | Outbound message delivered |
568
- | `crm.lead_created` | `{ leadId, phone }` | New CRM lead created |
569
- | `crm.lead_updated` | `{ leadId, stageName }` | Lead stage or field changed |
570
- | `meet.scheduled` | `{ meetingId, meetLink }` | New Google Meet booked |
571
- | `storage.upload_confirmed` | `{ key, url, sizeBytes }` | File upload confirmed |
572
- | `automation.failed` | `{ trigger, reason, leadId }` | Automation execution failed |
866
+ | Event | Payload | Description |
867
+ | --------------------------- | -------------------------------- | ------------------------------ |
868
+ | `whatsapp.message_received` | `{ from, body, conversationId }` | Inbound WhatsApp message |
869
+ | `whatsapp.message_sent` | `{ to, messageId }` | Outbound message delivered |
870
+ | `crm.lead_created` | `{ leadId, phone }` | New CRM lead created |
871
+ | `crm.lead_updated` | `{ leadId, stageName }` | Lead stage or fields updated |
872
+ | `meet.scheduled` | `{ meetingId, meetLink }` | Google Meet appointment booked |
873
+ | `storage.upload_confirmed` | `{ key, url, sizeBytes }` | File upload confirmed |
874
+ | `automation.failed` | `{ trigger, reason, leadId }` | Automation execution failed |
573
875
 
574
876
  ---
575
877
 
@@ -578,12 +880,7 @@ ecod.disconnect();
578
880
  All methods throw typed errors you can catch and inspect:
579
881
 
580
882
  ```typescript
581
- import {
582
- Ecodrix,
583
- APIError,
584
- AuthenticationError,
585
- RateLimitError,
586
- } from "@ecodrix/erix-api";
883
+ import { APIError, AuthenticationError, RateLimitError, Ecodrix } from "@ecodrix/erix-api";
587
884
 
588
885
  try {
589
886
  const { data } = await ecod.crm.leads.retrieve("non_existent_id");
@@ -592,31 +889,30 @@ try {
592
889
  // 401 — invalid API key or client code
593
890
  console.error("Check your credentials.");
594
891
  } else if (err instanceof RateLimitError) {
595
- // 429 — slow down requests
596
- console.warn("Rate limit hit. Retrying after delay...");
892
+ // 429 — too many requests (SDK auto-retries up to 3×)
893
+ console.warn("Rate limit hit.");
597
894
  } else if (err instanceof APIError) {
598
- // Other HTTP errors from the API
599
895
  console.error(`API Error [${err.status}]: ${err.message}`);
600
896
  } else {
601
- throw err; // re-throw unknown errors
897
+ throw err;
602
898
  }
603
899
  }
604
900
  ```
605
901
 
606
902
  ### Error Classes
607
903
 
608
- | Class | Status | Code | Description |
609
- | --------------------- | ------ | --------------------- | ------------------------------------------ |
610
- | `EcodrixError` | — | — | Base error class |
611
- | `APIError` | varies | varies | Generic API error with `status` and `code` |
612
- | `AuthenticationError` | 401 | `AUTH_FAILED` | Invalid API key or client code |
613
- | `RateLimitError` | 429 | `RATE_LIMIT_EXCEEDED` | Too many requests |
904
+ | Class | HTTP Status | Description |
905
+ | --------------------- | ----------- | ------------------------------------------ |
906
+ | `EcodrixError` | — | Base error class |
907
+ | `APIError` | varies | Generic API error with `.status` and `.code` |
908
+ | `AuthenticationError` | 401 | Invalid API key or client code |
909
+ | `RateLimitError` | 429 | Too many requests |
614
910
 
615
911
  ---
616
912
 
617
913
  ## Browser / CDN Usage
618
914
 
619
- For usage without a bundler (plain HTML, marketing pages):
915
+ For usage without a bundler:
620
916
 
621
917
  ```html
622
918
  <!-- Via CDN (jsDelivr) -->
@@ -634,14 +930,17 @@ For usage without a bundler (plain HTML, marketing pages):
634
930
  </script>
635
931
  ```
636
932
 
933
+ > ⚠️ **Note**: `ecod.webhooks.constructEvent` and `ecod.storage.upload` (from `Buffer`) are Node.js only and will not function in browser environments.
934
+
637
935
  ---
638
936
 
639
937
  ## Contributing
640
938
 
641
939
  1. Fork the repository.
642
940
  2. Create a feature branch: `git checkout -b feat/my-feature`
643
- 3. Make changes, then run `pnpm check` to validate.
644
- 4. Submit a Pull Request.
941
+ 3. Make changes, then run `pnpm check` to validate formatting and lint.
942
+ 4. Run `pnpm build` to verify the output compiles cleanly.
943
+ 5. Submit a Pull Request.
645
944
 
646
945
  ---
647
946