@ecodrix/erix-api 1.0.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.
- package/LICENSE +21 -0
- package/README.md +643 -0
- package/dist/index.d.ts +1096 -0
- package/dist/ts/browser/index.global.js +52 -0
- package/dist/ts/browser/index.global.js.map +1 -0
- package/dist/ts/cjs/index.cjs +2 -0
- package/dist/ts/cjs/index.cjs.map +1 -0
- package/dist/ts/cjs/index.d.cts +1096 -0
- package/dist/ts/esm/index.d.ts +1096 -0
- package/dist/ts/esm/index.js +2 -0
- package/dist/ts/esm/index.js.map +1 -0
- package/package.json +69 -0
- package/schema/openapi.yaml +199 -0
- package/src/core.ts +262 -0
- package/src/error.ts +72 -0
- package/src/index.ts +18 -0
- package/src/resource.ts +73 -0
- package/src/resources/crm/index.ts +10 -0
- package/src/resources/crm/leads.ts +186 -0
- package/src/resources/email.ts +51 -0
- package/src/resources/events.ts +121 -0
- package/src/resources/media.ts +174 -0
- package/src/resources/meet.ts +153 -0
- package/src/resources/notifications.ts +123 -0
- package/src/resources/webhooks.ts +85 -0
- package/src/resources/whatsapp/conversations.ts +43 -0
- package/src/resources/whatsapp/index.ts +49 -0
- package/src/resources/whatsapp/messages.ts +138 -0
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import type { AxiosInstance } from "axios";
|
|
2
|
+
import { APIResource } from "../resource";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Event definition representing an entry point capable of triggering automations.
|
|
6
|
+
*/
|
|
7
|
+
export interface EventDefinition {
|
|
8
|
+
name: string;
|
|
9
|
+
displayName: string;
|
|
10
|
+
description?: string;
|
|
11
|
+
pipelineId?: string;
|
|
12
|
+
stageId?: string;
|
|
13
|
+
defaultSource?: string;
|
|
14
|
+
[key: string]: any;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Payload to register/assign a new custom event definition.
|
|
19
|
+
*/
|
|
20
|
+
export interface AssignEventPayload {
|
|
21
|
+
/** The internal machine-readable name of the event (e.g. "webinar_joined") */
|
|
22
|
+
name: string;
|
|
23
|
+
/** Human-readable display name */
|
|
24
|
+
displayName: string;
|
|
25
|
+
/** Event description */
|
|
26
|
+
description?: string;
|
|
27
|
+
/** ID of the pipeline to associate with matching leads */
|
|
28
|
+
pipelineId?: string;
|
|
29
|
+
/** ID of the stage within the pipeline */
|
|
30
|
+
stageId?: string;
|
|
31
|
+
/** Default source tag for leads created via this event */
|
|
32
|
+
defaultSource?: string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Payload to programmatically trigger an event/workflow.
|
|
37
|
+
*/
|
|
38
|
+
export interface TriggerPayload {
|
|
39
|
+
/** The name of the event to fire (e.g., "webinar_joined") */
|
|
40
|
+
trigger: string;
|
|
41
|
+
/** Phone number of the lead (E.164 format) */
|
|
42
|
+
phone: string;
|
|
43
|
+
/** Email of the lead */
|
|
44
|
+
email?: string;
|
|
45
|
+
/** Key-value pairs for string templates */
|
|
46
|
+
variables?: Record<string, string>;
|
|
47
|
+
/** Deep payload data mapping to the event */
|
|
48
|
+
data?: any;
|
|
49
|
+
/** URL to receive an acknowledgment callback when processing completes */
|
|
50
|
+
callbackUrl?: string;
|
|
51
|
+
/** Secret metadata passed back to the callback URL */
|
|
52
|
+
callbackMetadata?: any;
|
|
53
|
+
/** Auto-create lead if phone does not exist in CRM */
|
|
54
|
+
createLeadIfMissing?: boolean;
|
|
55
|
+
/** Pre-fill data if creating a new lead */
|
|
56
|
+
leadData?: {
|
|
57
|
+
firstName?: string;
|
|
58
|
+
lastName?: string;
|
|
59
|
+
source?: string;
|
|
60
|
+
};
|
|
61
|
+
/** Delay execution of matched workflows (in seconds) */
|
|
62
|
+
delaySeconds?: number;
|
|
63
|
+
/** Delay execution of matched workflows (in minutes) */
|
|
64
|
+
delayMinutes?: number;
|
|
65
|
+
/** Explicit ISO timestamp to trigger the workflow run */
|
|
66
|
+
runAt?: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Response returned when triggering an event.
|
|
71
|
+
*/
|
|
72
|
+
export interface TriggerResponse {
|
|
73
|
+
success: boolean;
|
|
74
|
+
data?: {
|
|
75
|
+
eventLogId: string;
|
|
76
|
+
trigger: string;
|
|
77
|
+
leadId: string;
|
|
78
|
+
rulesMatched: number;
|
|
79
|
+
};
|
|
80
|
+
message?: string;
|
|
81
|
+
code?: string;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export class EventsResource extends APIResource {
|
|
85
|
+
/**
|
|
86
|
+
* Retrieve all valid events (system + custom) that can be used as Automation Rule triggers.
|
|
87
|
+
*/
|
|
88
|
+
async list(): Promise<{ success: boolean; data: EventDefinition[] }> {
|
|
89
|
+
return this.get<{ success: boolean; data: EventDefinition[] }>("/api/saas/events");
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Register a new custom event entry point into the CRM automation engine.
|
|
94
|
+
* Useful when introducing new granular triggers.
|
|
95
|
+
*/
|
|
96
|
+
async assign(payload: AssignEventPayload): Promise<{ success: boolean; data: EventDefinition }> {
|
|
97
|
+
return this.post<{ success: boolean; data: EventDefinition }>("/api/saas/events/assign", payload);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Deactivate a custom event assignment by name.
|
|
102
|
+
*/
|
|
103
|
+
async unassign(name: string): Promise<{ success: boolean; data: any }> {
|
|
104
|
+
return this.post<{ success: boolean; data: any }>("/api/saas/events/unassign", { name });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Deactivate multiple custom event assignments simultaneously.
|
|
109
|
+
*/
|
|
110
|
+
async unassignBulk(names: string[]): Promise<{ success: boolean; message: string }> {
|
|
111
|
+
return this.post<{ success: boolean; message: string }>("/api/saas/events/unassign/bulk", { names });
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Programmatically fire an event into the CRM automation engine.
|
|
116
|
+
* Emits to the internal EventBus to match with active Workflow Automation Rules.
|
|
117
|
+
*/
|
|
118
|
+
async trigger(payload: TriggerPayload): Promise<TriggerResponse> {
|
|
119
|
+
return this.post<TriggerResponse>("/api/saas/workflows/trigger", payload);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import axios from "axios";
|
|
2
|
+
import { APIResource } from "../resource";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for the `upload()` elite helper.
|
|
6
|
+
*/
|
|
7
|
+
export interface UploadOptions {
|
|
8
|
+
/**
|
|
9
|
+
* The destination folder key in R2.
|
|
10
|
+
* @example "customer_documents" | "avatars" | "invoices"
|
|
11
|
+
*/
|
|
12
|
+
folder: string;
|
|
13
|
+
/**
|
|
14
|
+
* The desired filename, including extension.
|
|
15
|
+
* @example "contract.pdf" | "profile.jpg"
|
|
16
|
+
*/
|
|
17
|
+
filename: string;
|
|
18
|
+
/**
|
|
19
|
+
* The MIME type of the file.
|
|
20
|
+
* @example "application/pdf" | "image/jpeg" | "video/mp4"
|
|
21
|
+
*/
|
|
22
|
+
contentType: string;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Cloudflare R2-backed media resource.
|
|
27
|
+
*
|
|
28
|
+
* Access via `ecod.media`.
|
|
29
|
+
*
|
|
30
|
+
* The `upload()` method is the recommended approach. It acts as a client-side
|
|
31
|
+
* orchestrator: it fetches a presigned URL from the backend, uploads directly
|
|
32
|
+
* to R2 (bypassing the API server), then confirms the upload — all in one call.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const { data } = await ecod.media.upload(fileBuffer, {
|
|
37
|
+
* folder: "invoices",
|
|
38
|
+
* filename: "invoice-001.pdf",
|
|
39
|
+
* contentType: "application/pdf",
|
|
40
|
+
* });
|
|
41
|
+
* console.log(data.url); // → https://cdn.ecodrix.com/invoices/invoice-001.pdf
|
|
42
|
+
* ```
|
|
43
|
+
*/
|
|
44
|
+
export class MediaResource extends APIResource {
|
|
45
|
+
/**
|
|
46
|
+
* Get current storage usage metrics for the tenant.
|
|
47
|
+
*
|
|
48
|
+
* @returns `{ usedMB, limitMB, fileCount }`
|
|
49
|
+
*
|
|
50
|
+
* @example
|
|
51
|
+
* ```typescript
|
|
52
|
+
* const { data } = await ecod.media.getUsage();
|
|
53
|
+
* console.log(`${data.usedMB} / ${data.limitMB} MB used`);
|
|
54
|
+
* ```
|
|
55
|
+
*/
|
|
56
|
+
async getUsage() {
|
|
57
|
+
return this.get("/api/saas/storage/usage");
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Create a new top-level folder in the tenant's R2 bucket.
|
|
62
|
+
*
|
|
63
|
+
* @param name - Folder name (alphanumeric, hyphens, underscores).
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```typescript
|
|
67
|
+
* await ecod.media.createFolder("patient_records");
|
|
68
|
+
* ```
|
|
69
|
+
*/
|
|
70
|
+
async createFolder(name: string) {
|
|
71
|
+
return this.post("/api/saas/storage/folders", { name });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* List files within a specific folder, with optional date filtering.
|
|
76
|
+
*
|
|
77
|
+
* @param folder - The folder key to list.
|
|
78
|
+
* @param params - Optional `year` and `month` filters (e.g. `{ year: "2026", month: "04" }`).
|
|
79
|
+
* @returns Array of file metadata objects.
|
|
80
|
+
*
|
|
81
|
+
* @example
|
|
82
|
+
* ```typescript
|
|
83
|
+
* const { data: files } = await ecod.media.list("invoices", { year: "2026" });
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
async list(folder: string, params?: { year?: string; month?: string }) {
|
|
87
|
+
return this.get(`/api/saas/storage/files/${folder}`, { params } as any);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Delete a file from R2 by its storage key.
|
|
92
|
+
*
|
|
93
|
+
* @param key - The full storage key of the file (e.g. `"invoices/contract.pdf"`).
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* ```typescript
|
|
97
|
+
* await ecod.media.delete("invoices/old-contract.pdf");
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
async delete(key: string) {
|
|
101
|
+
return this.deleteRequest("/api/saas/storage/files", { params: { key } });
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Request a temporary presigned download URL for a private file.
|
|
106
|
+
*
|
|
107
|
+
* @param key - The full storage key of the file.
|
|
108
|
+
* @returns `{ url }` — a time-limited download URL.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const { data } = await ecod.media.getDownloadUrl("invoices/contract.pdf");
|
|
113
|
+
* // → { url: "https://cdn.ecodrix.com/...?token=..." }
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
async getDownloadUrl(key: string) {
|
|
117
|
+
return this.post("/api/saas/storage/download-url", { key });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* **Elite Upload Helper** — uploads a file to Cloudflare R2 in a single call.
|
|
122
|
+
*
|
|
123
|
+
* This orchestrates 3 steps transparently:
|
|
124
|
+
* 1. Fetch a presigned `PUT` URL from the ECODrIx backend.
|
|
125
|
+
* 2. Upload the file _directly_ to R2 (no API proxy, maximum throughput).
|
|
126
|
+
* 3. Confirm the upload with the backend so it is indexed and tracked.
|
|
127
|
+
*
|
|
128
|
+
* Works with Node.js `Buffer`, browser `File`/`Blob`, or any stream-like
|
|
129
|
+
* object that Axios can PUT.
|
|
130
|
+
*
|
|
131
|
+
* @param file - The file data to upload.
|
|
132
|
+
* @param options - Folder, filename, and content type.
|
|
133
|
+
* @returns Confirmed file metadata including `key` and `url`.
|
|
134
|
+
*
|
|
135
|
+
* @example Node.js
|
|
136
|
+
* ```typescript
|
|
137
|
+
* import { readFileSync } from "fs";
|
|
138
|
+
* const buf = readFileSync("./invoice.pdf");
|
|
139
|
+
* const { data } = await ecod.media.upload(buf, {
|
|
140
|
+
* folder: "invoices",
|
|
141
|
+
* filename: "invoice-001.pdf",
|
|
142
|
+
* contentType: "application/pdf",
|
|
143
|
+
* });
|
|
144
|
+
* ```
|
|
145
|
+
*
|
|
146
|
+
* @example Browser
|
|
147
|
+
* ```typescript
|
|
148
|
+
* const file = inputElement.files[0];
|
|
149
|
+
* const { data } = await ecod.media.upload(file, {
|
|
150
|
+
* folder: "avatars",
|
|
151
|
+
* filename: file.name,
|
|
152
|
+
* contentType: file.type,
|
|
153
|
+
* });
|
|
154
|
+
* ```
|
|
155
|
+
*/
|
|
156
|
+
async upload(file: any, options: UploadOptions): Promise<any> {
|
|
157
|
+
// Step 1: Get presigned URL
|
|
158
|
+
const { data: presignedData } = await this.client.post(
|
|
159
|
+
"/api/saas/storage/upload-url",
|
|
160
|
+
options,
|
|
161
|
+
);
|
|
162
|
+
const { uploadUrl, key } = presignedData;
|
|
163
|
+
|
|
164
|
+
// Step 2: Upload directly to R2 (bypasses the API server for performance)
|
|
165
|
+
await axios.put(uploadUrl, file, {
|
|
166
|
+
headers: { "Content-Type": options.contentType },
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const sizeBytes = file.size || file.byteLength || 0;
|
|
170
|
+
|
|
171
|
+
// Step 3: Confirm with backend
|
|
172
|
+
return this.post("/api/saas/storage/confirm-upload", { key, sizeBytes });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { APIResource } from "../resource";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Parameters for scheduling a new Google Meet appointment.
|
|
5
|
+
*/
|
|
6
|
+
export interface CreateMeetingParams {
|
|
7
|
+
/** The CRM Lead ID this meeting belongs to. */
|
|
8
|
+
leadId: string;
|
|
9
|
+
/** Full name of the external participant. */
|
|
10
|
+
participantName: string;
|
|
11
|
+
/** Phone number of the participant in E.164 format. */
|
|
12
|
+
participantPhone: string;
|
|
13
|
+
/**
|
|
14
|
+
* Meeting start time.
|
|
15
|
+
* @example new Date("2026-04-10T10:00:00.000Z")
|
|
16
|
+
*/
|
|
17
|
+
startTime: Date | string;
|
|
18
|
+
/**
|
|
19
|
+
* Meeting end time.
|
|
20
|
+
* @example new Date("2026-04-10T10:30:00.000Z")
|
|
21
|
+
*/
|
|
22
|
+
endTime: Date | string;
|
|
23
|
+
/** Arbitrary metadata to attach to the meeting record. */
|
|
24
|
+
metadata?: Record<string, any>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Parameters for updating an existing meeting.
|
|
29
|
+
*/
|
|
30
|
+
export interface UpdateMeetingParams {
|
|
31
|
+
/**
|
|
32
|
+
* Update the meeting status.
|
|
33
|
+
* @example "scheduled" | "completed" | "cancelled"
|
|
34
|
+
*/
|
|
35
|
+
status?: string;
|
|
36
|
+
/** Update the payment status for paid consultations. */
|
|
37
|
+
paymentStatus?: string;
|
|
38
|
+
/** New start time for rescheduling. */
|
|
39
|
+
startTime?: Date | string;
|
|
40
|
+
/** New end time for rescheduling. */
|
|
41
|
+
endTime?: Date | string;
|
|
42
|
+
/** Duration in minutes. */
|
|
43
|
+
duration?: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Google Meet appointment scheduling resource.
|
|
48
|
+
*
|
|
49
|
+
* Access via `ecod.meet`.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* const { data } = await ecod.meet.create({
|
|
54
|
+
* leadId: "64abc...",
|
|
55
|
+
* participantName: "Alice",
|
|
56
|
+
* participantPhone: "+919876543210",
|
|
57
|
+
* startTime: "2026-04-10T10:00:00Z",
|
|
58
|
+
* endTime: "2026-04-10T10:30:00Z",
|
|
59
|
+
* });
|
|
60
|
+
* console.log(data.meetLink); // → https://meet.google.com/abc-defg-hij
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
export class Meetings extends APIResource {
|
|
64
|
+
/**
|
|
65
|
+
* Schedule a new Google Meet with a lead.
|
|
66
|
+
*
|
|
67
|
+
* The backend automatically creates a Google Calendar event and generates
|
|
68
|
+
* a Meet link, then sends confirmation notifications via WhatsApp.
|
|
69
|
+
*
|
|
70
|
+
* @param data - Meeting details.
|
|
71
|
+
* @returns The created Meeting document including `meetLink`.
|
|
72
|
+
*
|
|
73
|
+
* @example
|
|
74
|
+
* ```typescript
|
|
75
|
+
* const { data } = await ecod.meet.create({
|
|
76
|
+
* leadId: "64abc...",
|
|
77
|
+
* participantName: "Alice",
|
|
78
|
+
* participantPhone: "+91...",
|
|
79
|
+
* startTime: "2026-04-10T10:00:00Z",
|
|
80
|
+
* endTime: "2026-04-10T10:30:00Z",
|
|
81
|
+
* });
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
async create(data: CreateMeetingParams): Promise<any> {
|
|
85
|
+
return this.post("/api/saas/meet", data);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* List all meetings for the tenant, with optional filters.
|
|
90
|
+
*
|
|
91
|
+
* @param params - `leadId` to filter by lead; `status` to filter by state.
|
|
92
|
+
* @returns Array of Meeting documents.
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const { data } = await ecod.meet.list({ status: "scheduled" });
|
|
97
|
+
* ```
|
|
98
|
+
*/
|
|
99
|
+
async list(params?: { leadId?: string; status?: string }): Promise<any> {
|
|
100
|
+
return this.get("/api/saas/meet", { params } as any);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Retrieve the full details for a specific meeting.
|
|
105
|
+
*
|
|
106
|
+
* @param meetingId - The unique meeting ID.
|
|
107
|
+
* @returns The Meeting document.
|
|
108
|
+
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```typescript
|
|
111
|
+
* const { data } = await ecod.meet.retrieve("meeting_id");
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
async retrieve(meetingId: string): Promise<any> {
|
|
115
|
+
return this.get(`/api/saas/meet/${meetingId}`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Update or reschedule an existing meeting.
|
|
120
|
+
*
|
|
121
|
+
* Partial updates are supported — only supply the fields you wish to change.
|
|
122
|
+
*
|
|
123
|
+
* @param meetingId - The meeting to update.
|
|
124
|
+
* @param data - Fields to update.
|
|
125
|
+
* @returns The updated Meeting document.
|
|
126
|
+
*
|
|
127
|
+
* @example
|
|
128
|
+
* ```typescript
|
|
129
|
+
* // Reschedule
|
|
130
|
+
* await ecod.meet.update("meeting_id", {
|
|
131
|
+
* startTime: "2026-04-11T11:00:00Z",
|
|
132
|
+
* endTime: "2026-04-11T11:30:00Z",
|
|
133
|
+
* });
|
|
134
|
+
* ```
|
|
135
|
+
*/
|
|
136
|
+
async update(meetingId: string, data: UpdateMeetingParams): Promise<any> {
|
|
137
|
+
return this.client.patch(`/api/saas/meet/${meetingId}`, data).then((res) => res.data);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Cancel a meeting. This sets the meeting status to `"cancelled"`.
|
|
142
|
+
*
|
|
143
|
+
* @param meetingId - The meeting to cancel.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* await ecod.meet.delete("meeting_id");
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
async delete(meetingId: string): Promise<any> {
|
|
151
|
+
return this.update(meetingId, { status: "cancelled" });
|
|
152
|
+
}
|
|
153
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { APIResource } from "../resource";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Filters for querying event and automation execution logs.
|
|
5
|
+
*/
|
|
6
|
+
export interface LogFilter {
|
|
7
|
+
/** Page number for pagination (1-indexed). */
|
|
8
|
+
page?: number;
|
|
9
|
+
/** Maximum number of records to return per page. */
|
|
10
|
+
limit?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Filter by automation trigger name.
|
|
13
|
+
* @example "lead_created" | "appointment_booked"
|
|
14
|
+
*/
|
|
15
|
+
trigger?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Filter by execution status.
|
|
18
|
+
* @example "success" | "failed" | "pending"
|
|
19
|
+
*/
|
|
20
|
+
status?: string;
|
|
21
|
+
/** Filter logs associated with a specific phone number. */
|
|
22
|
+
phone?: string;
|
|
23
|
+
/** Start of the date range (inclusive). ISO 8601 or Date object. */
|
|
24
|
+
startDate?: string | Date;
|
|
25
|
+
/** End of the date range (inclusive). ISO 8601 or Date object. */
|
|
26
|
+
endDate?: string | Date;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Automation event log and provider callback resource.
|
|
31
|
+
*
|
|
32
|
+
* Access via `ecod.notifications`.
|
|
33
|
+
*
|
|
34
|
+
* This resource is **read-only**. It exposes platform audit trails — useful
|
|
35
|
+
* for debugging automation failures, monitoring webhook callbacks, and
|
|
36
|
+
* building internal ops dashboards.
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```typescript
|
|
40
|
+
* // Find failed automations in April
|
|
41
|
+
* const { data } = await ecod.notifications.listLogs({
|
|
42
|
+
* status: "failed",
|
|
43
|
+
* startDate: "2026-04-01",
|
|
44
|
+
* endDate: "2026-04-30",
|
|
45
|
+
* });
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export class Notifications extends APIResource {
|
|
49
|
+
/**
|
|
50
|
+
* List automation execution logs with optional filtering.
|
|
51
|
+
*
|
|
52
|
+
* Each log entry represents one automation run triggered by a platform event.
|
|
53
|
+
* Filter by `status: "failed"` to build a failure alerting pipeline.
|
|
54
|
+
*
|
|
55
|
+
* @param params - Optional filter criteria.
|
|
56
|
+
* @returns Paginated list of event log objects.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```typescript
|
|
60
|
+
* const { data: failed } = await ecod.notifications.listLogs({
|
|
61
|
+
* status: "failed",
|
|
62
|
+
* trigger: "lead_created",
|
|
63
|
+
* limit: 50,
|
|
64
|
+
* });
|
|
65
|
+
* ```
|
|
66
|
+
*/
|
|
67
|
+
async listLogs(params?: LogFilter) {
|
|
68
|
+
return this.get("/api/saas/events/logs", { params } as any);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Retrieve the full details for a single automation event log entry.
|
|
73
|
+
*
|
|
74
|
+
* @param logId - The unique log ID.
|
|
75
|
+
* @returns A single event log document including the payload and error trace.
|
|
76
|
+
*
|
|
77
|
+
* @example
|
|
78
|
+
* ```typescript
|
|
79
|
+
* const { data } = await ecod.notifications.retrieveLog("log_64abc...");
|
|
80
|
+
* console.log(data.error); // → "Provider timeout after 30s"
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
async retrieveLog(logId: string) {
|
|
84
|
+
return this.get(`/api/saas/events/logs/${logId}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Get a summary statistics object for automation event execution.
|
|
89
|
+
*
|
|
90
|
+
* @param params - Optional date range for the summary window.
|
|
91
|
+
* @returns `{ total, success, failed, pending }` counts.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* const { data: stats } = await ecod.notifications.getStats({
|
|
96
|
+
* startDate: "2026-04-01",
|
|
97
|
+
* endDate: "2026-04-30",
|
|
98
|
+
* });
|
|
99
|
+
* console.log(`Failed: ${stats.failed} / ${stats.total}`);
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
async getStats(params?: { startDate?: string; endDate?: string }) {
|
|
103
|
+
return this.get("/api/saas/events/stats", { params } as any);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* List external provider webhook callback logs.
|
|
108
|
+
*
|
|
109
|
+
* These are inbound HTTP callbacks from third-party providers
|
|
110
|
+
* (payment gateways, email services, etc.) stored for audit purposes.
|
|
111
|
+
*
|
|
112
|
+
* @param params - Pagination and date filter options.
|
|
113
|
+
* @returns Paginated list of callback log records.
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* const { data } = await ecod.notifications.listCallbacks({ limit: 20 });
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
async listCallbacks(params?: Omit<LogFilter, "trigger" | "phone">) {
|
|
121
|
+
return this.get("/api/saas/callbacks/logs", { params } as any);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { APIError } from "../error";
|
|
2
|
+
|
|
3
|
+
export class WebhookSignatureError extends APIError {
|
|
4
|
+
constructor(message: string) {
|
|
5
|
+
super(message, 400, "invalid_signature");
|
|
6
|
+
this.name = "WebhookSignatureError";
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Validates and constructs webhook events sent by the ECODrIx platform.
|
|
12
|
+
* Ensures the payload has not been tampered with.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { ecod } from "./services/ecodrix";
|
|
17
|
+
*
|
|
18
|
+
* app.post("/api/webhooks", async (req, res) => {
|
|
19
|
+
* try {
|
|
20
|
+
* const event = await ecod.webhooks.constructEvent(
|
|
21
|
+
* req.rawBody,
|
|
22
|
+
* req.headers["x-ecodrix-signature"],
|
|
23
|
+
* process.env.ECOD_WEBHOOK_SECRET
|
|
24
|
+
* );
|
|
25
|
+
* console.log("Verified event:", event.type);
|
|
26
|
+
* res.send({ received: true });
|
|
27
|
+
* } catch (err) {
|
|
28
|
+
* res.status(400).send(`Webhook Error: ${err.message}`);
|
|
29
|
+
* }
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export class Webhooks {
|
|
34
|
+
/**
|
|
35
|
+
* Cryptographically validates a webhook payload.
|
|
36
|
+
* Note: This method dynamically imports Node's `crypto` module, meaning it will only execute gracefully in a Server environment.
|
|
37
|
+
*
|
|
38
|
+
* @param payload - The raw request body as a string or Buffer.
|
|
39
|
+
* @param signature - The `x-ecodrix-signature` header value.
|
|
40
|
+
* @param secret - The webhook signing secret for your tenant.
|
|
41
|
+
* @returns The parsed JSON object of the event if the signature is valid.
|
|
42
|
+
* @throws WebhookSignatureError if the validation fails.
|
|
43
|
+
*/
|
|
44
|
+
public async constructEvent(
|
|
45
|
+
payload: string | Buffer,
|
|
46
|
+
signature: string | string[] | undefined,
|
|
47
|
+
secret: string
|
|
48
|
+
): Promise<any> {
|
|
49
|
+
if (!signature) {
|
|
50
|
+
throw new WebhookSignatureError("No webhook signature provided");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
let sig = Array.isArray(signature) ? signature[0] : signature;
|
|
54
|
+
if (sig.startsWith("sha256=")) {
|
|
55
|
+
sig = sig.slice(7);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// Dynamic import ensures Isomorphic bundles (like Vite UI) don't crash on import,
|
|
60
|
+
// and only throw if someone incorrectly tries to verify a webhook in the browser.
|
|
61
|
+
const crypto = await import("node:crypto");
|
|
62
|
+
|
|
63
|
+
const hmac = crypto.createHmac("sha256", secret);
|
|
64
|
+
const digest = hmac.update(payload).digest("hex");
|
|
65
|
+
|
|
66
|
+
const isValid = crypto.timingSafeEqual(
|
|
67
|
+
Buffer.from(digest),
|
|
68
|
+
Buffer.from(sig)
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
if (!isValid) {
|
|
72
|
+
throw new WebhookSignatureError("Invalid webhook signature provided");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return JSON.parse(payload.toString("utf8"));
|
|
76
|
+
} catch (error: any) {
|
|
77
|
+
if (error instanceof WebhookSignatureError) {
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
throw new WebhookSignatureError(
|
|
81
|
+
`Webhook payload parsing failed: ${error.message}`
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { APIResource } from "../../resource";
|
|
2
|
+
|
|
3
|
+
export interface ListParams {
|
|
4
|
+
limit?: number;
|
|
5
|
+
before?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export class Conversations extends APIResource {
|
|
9
|
+
/**
|
|
10
|
+
* List conversations for the tenant.
|
|
11
|
+
*/
|
|
12
|
+
async list(params?: ListParams): Promise<any> {
|
|
13
|
+
return this.get("/api/saas/chat/conversations", { params } as any);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Retrieve details of a specific conversation.
|
|
18
|
+
*/
|
|
19
|
+
async retrieve(conversationId: string): Promise<any> {
|
|
20
|
+
return this.get(`/api/saas/chat/conversations/${conversationId}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Get messages for a specific conversation.
|
|
25
|
+
*/
|
|
26
|
+
async messages(conversationId: string, params?: ListParams): Promise<any> {
|
|
27
|
+
return this.get(`/api/saas/chat/conversations/${conversationId}/messages`, { params } as any);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Link a conversation to a lead.
|
|
32
|
+
*/
|
|
33
|
+
async linkLead(conversationId: string, leadId: string) {
|
|
34
|
+
return this.post(`/api/saas/chat/conversations/${conversationId}/link-lead`, { leadId });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Delete a conversation.
|
|
39
|
+
*/
|
|
40
|
+
async delete(conversationId: string): Promise<any> {
|
|
41
|
+
return this.deleteRequest(`/api/saas/chat/conversations/${conversationId}`);
|
|
42
|
+
}
|
|
43
|
+
}
|