@ecodrix/erix-api 3.1.1 → 3.2.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/README.md +468 -815
- package/dist/cli.js +5 -5
- package/dist/ts/cjs/index.cjs +1 -1
- package/dist/ts/cjs/visitor.cjs +1 -0
- package/dist/ts/esm/chunk-XPXDFZWU.js +1 -0
- package/dist/ts/esm/error-DuNpKP1d.d.ts +158 -0
- package/dist/ts/esm/index.d.ts +387 -220
- package/dist/ts/esm/index.js +1 -1
- package/dist/ts/esm/visitor.d.ts +147 -0
- package/dist/ts/esm/visitor.js +1 -0
- package/package.json +7 -2
package/README.md
CHANGED
|
@@ -1,1006 +1,659 @@
|
|
|
1
1
|
# @ecodrix/erix-api
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Table of Contents
|
|
20
|
-
|
|
21
|
-
- [Installation](#installation)
|
|
22
|
-
- [Quick Start](#quick-start)
|
|
23
|
-
- [Configuration](#configuration)
|
|
24
|
-
- [Resources](#resources)
|
|
25
|
-
- [WhatsApp](#whatsapp)
|
|
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)
|
|
42
|
-
- [Meetings](#meetings)
|
|
43
|
-
- [Storage](#storage)
|
|
44
|
-
- [Media](#media)
|
|
45
|
-
- [Email](#email)
|
|
46
|
-
- [Notifications & Logs](#notifications--logs)
|
|
47
|
-
- [Queue Management](#queue-management)
|
|
48
|
-
- [Health & Diagnostics](#health--diagnostics)
|
|
49
|
-
- [Webhooks](#webhooks)
|
|
50
|
-
- [Enterprise Capabilities](#enterprise-capabilities)
|
|
51
|
-
- [Auto-Paginating Iterators](#auto-paginating-iterators)
|
|
52
|
-
- [Bulk Data Chunking](#bulk-data-chunking)
|
|
53
|
-
- [Idempotency Keys](#idempotency-keys)
|
|
54
|
-
- [Webhook Signature Verification](#webhook-signature-verification)
|
|
55
|
-
- [Raw Execution Engine](#raw-execution-engine)
|
|
56
|
-
- [Real-time Events](#real-time-events)
|
|
57
|
-
- [Error Handling](#error-handling)
|
|
58
|
-
- [Browser / CDN Usage](#browser--cdn-usage)
|
|
59
|
-
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
## Installation
|
|
3
|
+
[](https://www.npmjs.com/package/@ecodrix/erix-api)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
The official TypeScript SDK for the [ECODrIx](https://ecodrix.com) platform.
|
|
7
|
+
Use it from your own backend to work with your workspace: CRM leads and
|
|
8
|
+
pipelines, Flow automations, the unified inbox (WhatsApp, Instagram, Facebook,
|
|
9
|
+
Telegram, email, webchat), invoices and payment links, file storage, and live
|
|
10
|
+
events. Every call is scoped to the workspace your key belongs to.
|
|
11
|
+
|
|
12
|
+
- Developer guide: https://api.ecodrix.com/docs
|
|
13
|
+
- API reference: https://api.ecodrix.com/reference
|
|
14
|
+
|
|
15
|
+
## Install
|
|
63
16
|
|
|
64
17
|
```bash
|
|
65
|
-
# pnpm (recommended)
|
|
66
18
|
pnpm add @ecodrix/erix-api
|
|
67
|
-
|
|
68
|
-
# npm
|
|
69
|
-
npm install @ecodrix/erix-api
|
|
19
|
+
# or: npm install @ecodrix/erix-api
|
|
70
20
|
```
|
|
71
21
|
|
|
72
|
-
|
|
22
|
+
- **Node.js 18+** (uses the global `fetch` and `Blob`). Ships ESM and CommonJS
|
|
23
|
+
builds with bundled type definitions.
|
|
24
|
+
- **Browsers**: supported through a bundler (Vite, Next.js, webpack) in
|
|
25
|
+
[proxy mode](#browser-use-proxy-mode) only, plus the keyless
|
|
26
|
+
[website chat visitor client](#website-chat-visitor-client). There is no
|
|
27
|
+
CDN build.
|
|
73
28
|
|
|
74
|
-
|
|
29
|
+
## Quick start
|
|
75
30
|
|
|
76
|
-
|
|
31
|
+
Run this on your server. Keep the key in a server-only environment variable.
|
|
77
32
|
|
|
78
|
-
```
|
|
33
|
+
```ts
|
|
79
34
|
import { ECODrIxAPI } from "@ecodrix/erix-api";
|
|
80
35
|
|
|
81
36
|
const ecod = new ECODrIxAPI({
|
|
82
37
|
apiKey: process.env.ECOD_API_KEY!,
|
|
83
|
-
clientCode: "
|
|
84
|
-
// Optional — pick the API version. Defaults to "v1".
|
|
85
|
-
version: "v1",
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
// The SDK surface mirrors the server's domain ownership 1:1:
|
|
89
|
-
// ecod.platform.* control plane (auth-scoped tenant/admin)
|
|
90
|
-
// ecod.product.erix.* ERIX product (CRM, chat, commerce, events)
|
|
91
|
-
// ecod.infra.connect.* channel transport (email, whatsapp, meet)
|
|
92
|
-
// ecod.infra.storage.* file storage
|
|
93
|
-
|
|
94
|
-
// Send a WhatsApp/chat message
|
|
95
|
-
await ecod.product.erix.chat.messages.send({
|
|
96
|
-
to: "+919876543210",
|
|
97
|
-
text: "Hello from ECODrIx!",
|
|
38
|
+
clientCode: "ERIX_CLNT_XXXXXX", // Settings → Developer in the dashboard
|
|
98
39
|
});
|
|
99
40
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
phone: "+
|
|
41
|
+
const { data: lead } = await ecod.product.erix.leads.create({
|
|
42
|
+
firstName: "Test",
|
|
43
|
+
lastName: "Customer",
|
|
44
|
+
phone: "+91XXXXXXXXXX",
|
|
104
45
|
source: "website",
|
|
105
46
|
});
|
|
106
47
|
|
|
107
|
-
// Fire an automation trigger
|
|
108
48
|
await ecod.product.erix.events.trigger({
|
|
109
|
-
trigger: "
|
|
110
|
-
phone: "+
|
|
111
|
-
createLeadIfMissing: true,
|
|
112
|
-
});
|
|
113
|
-
```
|
|
114
|
-
|
|
115
|
-
> **Note:** host projects never pass a URL. The domain is managed by the SDK;
|
|
116
|
-
> you only choose an API `version`. Moving the whole SDK to `v2` later is a
|
|
117
|
-
> single flag — `new ECODrIxAPI({ ..., version: "v2" })` — because every
|
|
118
|
-
> endpoint is issued relative to `<host>/<version>/api`.
|
|
119
|
-
|
|
120
|
-
---
|
|
121
|
-
|
|
122
|
-
## Configuration
|
|
123
|
-
|
|
124
|
-
Pass an options object to `new Ecodrix(options)`:
|
|
125
|
-
|
|
126
|
-
| Option | Type | Required | Default | Description |
|
|
127
|
-
| ------------ | ---------------------- | ----------- | ------- | ------------------------------------------------------------------ |
|
|
128
|
-
| `apiKey` | `string` | ✅ Yes | — | Your ECOD Platform API key |
|
|
129
|
-
| `clientCode` | `string` | Recommended | — | Your tenant ID — scopes all requests |
|
|
130
|
-
| `version` | `"v1" \| "v2" \| "v3"` | No | `"v1"` | API route version. The single knob to target a new server version. |
|
|
131
|
-
| `coreApiKey` | `string` | No | — | Elevated key for platform-admin surfaces (`ecod.platform.admin.*`) |
|
|
132
|
-
|
|
133
|
-
```typescript
|
|
134
|
-
const ecod = new ECODrIxAPI({
|
|
135
|
-
apiKey: process.env.ECOD_API_KEY!,
|
|
136
|
-
clientCode: "ERIX_CLNT_JHBJHF",
|
|
137
|
-
version: "v1",
|
|
138
|
-
});
|
|
139
|
-
```
|
|
140
|
-
|
|
141
|
-
---
|
|
142
|
-
|
|
143
|
-
## Resources
|
|
144
|
-
|
|
145
|
-
### WhatsApp
|
|
146
|
-
|
|
147
|
-
Access via `ecod.whatsapp`.
|
|
148
|
-
|
|
149
|
-
---
|
|
150
|
-
|
|
151
|
-
#### `ecod.whatsapp.messages`
|
|
152
|
-
|
|
153
|
-
**Send a text message**
|
|
154
|
-
|
|
155
|
-
```typescript
|
|
156
|
-
await ecod.whatsapp.messages.send({
|
|
157
|
-
to: "+919876543210",
|
|
158
|
-
text: "Your appointment is confirmed!",
|
|
159
|
-
});
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
**Send a media message (image, video, document, audio)**
|
|
163
|
-
|
|
164
|
-
```typescript
|
|
165
|
-
await ecod.whatsapp.messages.send({
|
|
166
|
-
to: "+919876543210",
|
|
167
|
-
mediaUrl: "https://cdn.ecodrix.com/invoice.pdf",
|
|
168
|
-
mediaType: "document",
|
|
169
|
-
filename: "invoice.pdf",
|
|
49
|
+
trigger: "enquiry_received",
|
|
50
|
+
phone: "+91XXXXXXXXXX",
|
|
170
51
|
});
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
**Send a template message via queue**
|
|
174
52
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
to: "+919876543210",
|
|
178
|
-
templateName: "appointment_reminder",
|
|
179
|
-
language: "en_US",
|
|
180
|
-
variables: ["Priya", "Tomorrow 10AM"],
|
|
181
|
-
});
|
|
53
|
+
// The client keeps a realtime socket open. Close it when your process stops.
|
|
54
|
+
process.on("SIGTERM", () => ecod.disconnect());
|
|
182
55
|
```
|
|
183
56
|
|
|
184
|
-
|
|
57
|
+
| Option | Type | Default | Notes |
|
|
58
|
+
| ------------ | ---------------------------- | -------- | -------------------------------------------------------- |
|
|
59
|
+
| `apiKey` | `string` | none | Workspace key or a [scoped key](#scoped-keys). Server only. |
|
|
60
|
+
| `clientCode` | `string` | none | Your workspace code, e.g. `ERIX_CLNT_XXXXXX`. |
|
|
61
|
+
| `version` | `"v1"` | `"v1"` | API route version. |
|
|
62
|
+
| `timeout` | `number` (ms) | `30000` | Per request. `0` disables. |
|
|
63
|
+
| `proxy` | `{ url, ticket? }` | none | Browser mode. Replaces `apiKey`. |
|
|
185
64
|
|
|
186
|
-
|
|
187
|
-
await ecod.whatsapp.messages.markRead("message_id");
|
|
188
|
-
```
|
|
65
|
+
## How the client is laid out
|
|
189
66
|
|
|
190
|
-
|
|
67
|
+
The client follows the API's route tree:
|
|
191
68
|
|
|
192
|
-
|
|
69
|
+
| Namespace | What it covers |
|
|
70
|
+
| ---------------------------- | ------------------------------------------------------------------- |
|
|
71
|
+
| `ecod.product.erix.*` | CRM (`leads`, `pipelines`, `activities`, `analytics`), `events` (Flow triggers), `chat` (inbox), `invoices`, `payments`, `ai` |
|
|
72
|
+
| `ecod.infra.connect.*` | Channels: `whatsapp.templates`, `email`, `meet`, `connections` |
|
|
73
|
+
| `ecod.infra.storage.*` | Files: `media`, `files`, `folders` |
|
|
74
|
+
| `ecod.platform.*` | Workspace: `knowledge` (AI knowledge base), `users.notifications` |
|
|
75
|
+
| `ecod.webhooks` | Webhook signature verification |
|
|
76
|
+
| `ecod.on()` / `ecod.off()` | Realtime events |
|
|
77
|
+
| `ecod.request()` | Authenticated call to any endpoint without a typed method yet |
|
|
193
78
|
|
|
194
|
-
|
|
195
|
-
// List conversations (cursor-based pagination)
|
|
196
|
-
const { data } = await ecod.whatsapp.conversations.list({
|
|
197
|
-
limit: 20,
|
|
198
|
-
status: "open",
|
|
199
|
-
after: "cursor_token",
|
|
200
|
-
});
|
|
79
|
+
## Browser use (proxy mode)
|
|
201
80
|
|
|
202
|
-
|
|
203
|
-
|
|
81
|
+
Never put a workspace key in the browser. That includes `NEXT_PUBLIC_*`,
|
|
82
|
+
`VITE_*` or any other variable that ends up in a client bundle. Anyone who
|
|
83
|
+
opens dev tools can read it, and a workspace key has full access.
|
|
204
84
|
|
|
205
|
-
|
|
206
|
-
const { data: msgs } = await ecod.whatsapp.conversations.messages(
|
|
207
|
-
"conversation_id",
|
|
208
|
-
{
|
|
209
|
-
limit: 50,
|
|
210
|
-
},
|
|
211
|
-
);
|
|
85
|
+
In the browser, run the SDK in proxy mode:
|
|
212
86
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
});
|
|
87
|
+
1. Your server exposes `/api/erix/*`. It checks your signed-in user, adds the
|
|
88
|
+
key, and forwards the request to ECODrIx.
|
|
89
|
+
2. For realtime, your server mints a 5-minute socket ticket. The browser gets
|
|
90
|
+
the ticket, never the key.
|
|
218
91
|
|
|
219
|
-
|
|
220
|
-
await ecod.whatsapp.conversations.linkLead("conversation_id", "lead_id");
|
|
92
|
+
Proxy route (Next.js App Router, `app/api/erix/[...path]/route.ts`):
|
|
221
93
|
|
|
222
|
-
|
|
223
|
-
|
|
94
|
+
```ts
|
|
95
|
+
const API = "https://api.ecodrix.com";
|
|
96
|
+
const PASS_HEADERS = ["content-type", "accept", "idempotency-key"];
|
|
97
|
+
const DROP_HEADERS = ["content-encoding", "content-length", "transfer-encoding", "connection"];
|
|
224
98
|
|
|
225
|
-
|
|
226
|
-
await
|
|
99
|
+
async function proxy(req: Request, ctx: { params: Promise<{ path: string[] }> }) {
|
|
100
|
+
const user = await requireUser(req); // your own session check
|
|
101
|
+
if (!user) {
|
|
102
|
+
return Response.json({ success: false, error: "Unauthorized" }, { status: 401 });
|
|
103
|
+
}
|
|
227
104
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
105
|
+
const { path } = await ctx.params;
|
|
106
|
+
const rel = `/${path.map(encodeURIComponent).join("/")}`;
|
|
107
|
+
if (!/^\/v\d+\/api\//.test(rel)) {
|
|
108
|
+
return Response.json({ success: false, error: "Not found" }, { status: 404 });
|
|
109
|
+
}
|
|
231
110
|
|
|
232
|
-
|
|
111
|
+
const headers = new Headers({
|
|
112
|
+
"x-api-key": process.env.ECOD_API_KEY!,
|
|
113
|
+
"x-client-code": "ERIX_CLNT_XXXXXX",
|
|
114
|
+
});
|
|
115
|
+
for (const name of PASS_HEADERS) {
|
|
116
|
+
const value = req.headers.get(name);
|
|
117
|
+
if (value) headers.set(name, value);
|
|
118
|
+
}
|
|
233
119
|
|
|
234
|
-
|
|
120
|
+
const hasBody = req.method !== "GET" && req.method !== "HEAD";
|
|
121
|
+
const upstream = await fetch(`${API}${rel}${new URL(req.url).search}`, {
|
|
122
|
+
method: req.method,
|
|
123
|
+
headers,
|
|
124
|
+
body: hasBody ? await req.arrayBuffer() : undefined,
|
|
125
|
+
redirect: "manual",
|
|
126
|
+
});
|
|
235
127
|
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
128
|
+
const out = new Headers(upstream.headers);
|
|
129
|
+
for (const name of DROP_HEADERS) out.delete(name);
|
|
130
|
+
return new Response(upstream.body, { status: upstream.status, headers: out });
|
|
131
|
+
}
|
|
239
132
|
|
|
240
|
-
|
|
241
|
-
await ecod.whatsapp.templates.sync();
|
|
133
|
+
export { proxy as GET, proxy as POST, proxy as PUT, proxy as PATCH, proxy as DELETE };
|
|
242
134
|
```
|
|
243
135
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
#### `ecod.whatsapp.broadcasts`
|
|
136
|
+
Ticket route (`app/api/erix/ticket/route.ts`):
|
|
247
137
|
|
|
248
|
-
|
|
138
|
+
```ts
|
|
139
|
+
export async function POST(req: Request) {
|
|
140
|
+
const user = await requireUser(req); // your own session check
|
|
141
|
+
if (!user) return new Response("Unauthorized", { status: 401 });
|
|
249
142
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
143
|
+
const res = await fetch("https://api.ecodrix.com/v1/api/platform/realtime/ticket", {
|
|
144
|
+
method: "POST",
|
|
145
|
+
headers: {
|
|
146
|
+
"x-api-key": process.env.ECOD_API_KEY!,
|
|
147
|
+
"x-client-code": "ERIX_CLNT_XXXXXX",
|
|
148
|
+
},
|
|
149
|
+
cache: "no-store",
|
|
150
|
+
});
|
|
151
|
+
const body = (await res.json().catch(() => null)) as { data?: { ticket?: string } } | null;
|
|
152
|
+
const ticket = body?.data?.ticket;
|
|
153
|
+
if (!res.ok || !ticket) return new Response("Ticket unavailable", { status: 502 });
|
|
260
154
|
|
|
261
|
-
|
|
262
|
-
|
|
155
|
+
return new Response(ticket, {
|
|
156
|
+
headers: { "content-type": "text/plain", "cache-control": "no-store" },
|
|
157
|
+
});
|
|
158
|
+
}
|
|
263
159
|
```
|
|
264
160
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
#### `ecod.whatsapp.sendTemplate` — Direct Template (Bypass Queue)
|
|
161
|
+
Browser client:
|
|
268
162
|
|
|
269
|
-
|
|
163
|
+
```ts
|
|
164
|
+
import { ECODrIxAPI } from "@ecodrix/erix-api";
|
|
270
165
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
166
|
+
export const ecod = new ECODrIxAPI({
|
|
167
|
+
proxy: {
|
|
168
|
+
url: "/api/erix",
|
|
169
|
+
ticket: () =>
|
|
170
|
+
fetch("/api/erix/ticket", { method: "POST" }).then((r) =>
|
|
171
|
+
r.ok ? r.text() : Promise.reject(new Error("No realtime ticket")),
|
|
172
|
+
),
|
|
278
173
|
},
|
|
279
174
|
});
|
|
280
|
-
// result.messageId → WA message ID
|
|
281
175
|
```
|
|
282
176
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
### CRM
|
|
286
|
-
|
|
287
|
-
Access via `ecod.crm`.
|
|
288
|
-
|
|
289
|
-
---
|
|
177
|
+
The SDK fetches a fresh ticket on every reconnect. Without `ticket`, REST
|
|
178
|
+
calls work and no socket opens.
|
|
290
179
|
|
|
291
|
-
|
|
180
|
+
Anyone signed in to your app can now do whatever the workspace key can.
|
|
181
|
+
If your users shouldn't have all of that, allow-list the paths they need in the
|
|
182
|
+
proxy route.
|
|
292
183
|
|
|
293
|
-
|
|
184
|
+
## Website chat visitor client
|
|
294
185
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
email: "priya@example.com",
|
|
301
|
-
source: "website",
|
|
302
|
-
pipelineId: "pipeline_id",
|
|
303
|
-
stageId: "stage_id",
|
|
304
|
-
metadata: { utmSource: "google" },
|
|
305
|
-
});
|
|
306
|
-
```
|
|
186
|
+
`@ecodrix/erix-api/visitor` is a separate entry for an anonymous website
|
|
187
|
+
visitor chatting with a workspace. It takes no key and reaches only that
|
|
188
|
+
visitor's own thread. It uses `fetch` and loads `socket.io-client` only when
|
|
189
|
+
you go live, so a chat widget doesn't ship the full SDK. The ERIX chat widget
|
|
190
|
+
runs on it.
|
|
307
191
|
|
|
308
|
-
|
|
192
|
+
```ts
|
|
193
|
+
import { WebchatVisitor, WebchatVisitorError } from "@ecodrix/erix-api/visitor";
|
|
309
194
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
195
|
+
const chat = new WebchatVisitor({
|
|
196
|
+
clientCode: "ERIX_CLNT_XXXXXX",
|
|
197
|
+
// The visitor's only secret: random, 20–80 characters of A-Z a-z 0-9 _ -.
|
|
198
|
+
// Keep it (e.g. in localStorage) to continue the same thread next visit.
|
|
199
|
+
sessionId,
|
|
200
|
+
// The site the chat runs on. It must be on the workspace's allowed domains.
|
|
201
|
+
parent: "https://www.example.in",
|
|
314
202
|
});
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
**List with filters**
|
|
318
203
|
|
|
319
|
-
|
|
320
|
-
const { data } = await ecod.crm.leads.list({
|
|
321
|
-
status: "new",
|
|
322
|
-
source: "whatsapp",
|
|
323
|
-
pipelineId: "pipeline_id",
|
|
324
|
-
page: 1,
|
|
325
|
-
limit: 25,
|
|
326
|
-
});
|
|
327
|
-
```
|
|
204
|
+
const { data: messages, widget, consentNotice } = await chat.messages();
|
|
328
205
|
|
|
329
|
-
|
|
206
|
+
try {
|
|
207
|
+
await chat.send("Do you open on Sunday?", {
|
|
208
|
+
messageId: crypto.randomUUID(), // resend with the same id: stored once
|
|
209
|
+
page: { path: location.pathname, title: document.title },
|
|
210
|
+
});
|
|
211
|
+
} catch (err) {
|
|
212
|
+
if (err instanceof WebchatVisitorError) {
|
|
213
|
+
console.warn(err.code, err.status, err.field, err.message);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
330
216
|
|
|
331
|
-
|
|
332
|
-
const { data: lead } = await ecod.crm.leads.retrieve("lead_id");
|
|
333
|
-
const { data } = await ecod.crm.leads.retrieveByPhone("+919876543210");
|
|
334
|
-
const { data } = await ecod.crm.leads.retrieveByRef("orderId", "ORD-123");
|
|
217
|
+
await chat.contact({ name: "Ravi", phone: "98765 43210" });
|
|
335
218
|
```
|
|
336
219
|
|
|
337
|
-
|
|
220
|
+
Live replies and typing (`/visitor` socket):
|
|
338
221
|
|
|
339
|
-
```
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
222
|
+
```ts
|
|
223
|
+
chat.connect({
|
|
224
|
+
onMessage: (message) => upsert(message), // `from`: visitor | ai | team | system
|
|
225
|
+
onTyping: ({ on, waiting }) => showTyping(on, waiting), // waiting: the team will reply
|
|
226
|
+
onError: () => startPolling(), // live is down: poll messages()
|
|
227
|
+
onReconnect: () => {
|
|
228
|
+
stopPolling();
|
|
229
|
+
chat.messages().then(redraw); // nothing missed is replayed
|
|
230
|
+
},
|
|
344
231
|
});
|
|
345
|
-
```
|
|
346
|
-
|
|
347
|
-
**Move & Convert**
|
|
348
|
-
|
|
349
|
-
```typescript
|
|
350
|
-
await ecod.crm.leads.move("lead_id", "target_stage_id");
|
|
351
|
-
await ecod.crm.leads.convert("lead_id", "won", "Signed contract");
|
|
352
|
-
```
|
|
353
232
|
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
```typescript
|
|
357
|
-
await ecod.crm.leads.tags("lead_id", { add: ["vip", "hot"], remove: ["cold"] });
|
|
233
|
+
chat.disconnect();
|
|
358
234
|
```
|
|
359
235
|
|
|
360
|
-
|
|
236
|
+
| `err.code` | Meaning |
|
|
237
|
+
| -------------------- | ----------------------------------------------------------------- |
|
|
238
|
+
| `VALIDATION_ERROR` | Bad input. `err.field` names it, `err.message` is visitor copy |
|
|
239
|
+
| `ORIGIN_NOT_ALLOWED` | The site isn't on the workspace's allowed domains |
|
|
240
|
+
| `NOT_FOUND` | Unknown client code |
|
|
241
|
+
| `RATE_LIMITED` | Too many messages or connects. Wait a minute |
|
|
242
|
+
| `MALICIOUS_INPUT` | The text was refused as a script or injection |
|
|
243
|
+
| `INTERNAL_ERROR` | Server error. Try again |
|
|
244
|
+
| `NETWORK_ERROR` | No answer (offline, 30 s timeout). Try again |
|
|
361
245
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
246
|
+
The live socket only accepts the ERIX widget frame's origin. A chat UI on your
|
|
247
|
+
own page works over HTTP when that site is the workspace's website (browser
|
|
248
|
+
CORS), and polls `messages()` for replies.
|
|
365
249
|
|
|
366
|
-
|
|
250
|
+
## Scoped keys
|
|
367
251
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
```
|
|
252
|
+
For an integration that does one job (a booking form that fires a Flow event,
|
|
253
|
+
say), create a scoped key under **Settings → Developer → Scoped API keys**.
|
|
254
|
+
Scoped keys can be revoked, can expire and have usage tracking. Each one
|
|
255
|
+
carries a single grant:
|
|
373
256
|
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
const { data } = await ecod.crm.pipelines.list();
|
|
381
|
-
await ecod.crm.pipelines.create({ name: "Sales", isDefault: true });
|
|
382
|
-
await ecod.crm.pipelines.retrieve("pipeline_id");
|
|
383
|
-
await ecod.crm.pipelines.update("pipeline_id", { name: "Deals" });
|
|
384
|
-
await ecod.crm.pipelines.delete("pipeline_id");
|
|
385
|
-
|
|
386
|
-
// Advanced
|
|
387
|
-
await ecod.crm.pipelines.setDefault("pipeline_id");
|
|
388
|
-
await ecod.crm.pipelines.duplicate("pipeline_id", "Sales Copy");
|
|
389
|
-
await ecod.crm.pipelines.archive("pipeline_id");
|
|
390
|
-
const { data: board } = await ecod.crm.pipelines.board("pipeline_id");
|
|
391
|
-
const { data: forecast } = await ecod.crm.pipelines.forecast("pipeline_id");
|
|
392
|
-
|
|
393
|
-
// Stage management
|
|
394
|
-
await ecod.crm.pipelines.addStage("pipeline_id", {
|
|
395
|
-
name: "Negotiation",
|
|
396
|
-
color: "#f59e0b",
|
|
397
|
-
});
|
|
398
|
-
await ecod.crm.pipelines.reorderStages("pipeline_id", [
|
|
399
|
-
"stage_1",
|
|
400
|
-
"stage_3",
|
|
401
|
-
"stage_2",
|
|
402
|
-
]);
|
|
403
|
-
await ecod.crm.pipelines.updateStage("stage_id", { probability: 80 });
|
|
404
|
-
await ecod.crm.pipelines.deleteStage("stage_id", "fallback_stage_id");
|
|
405
|
-
```
|
|
257
|
+
| Purpose | Grant | Works for |
|
|
258
|
+
| ---------------- | ------------------ | --------------------------------------------- |
|
|
259
|
+
| Trigger events | `flow:events` | `product.erix.events.trigger()` |
|
|
260
|
+
| Send WhatsApp | `connect:whatsapp` | `chat.messages.send({ to })`, `sendTemplate()`|
|
|
261
|
+
| Send email | `connect:email` | `POST /infra/connect/send/email` |
|
|
262
|
+
| Send Telegram | `connect:telegram` | `POST /infra/connect/send/telegram` |
|
|
406
263
|
|
|
407
|
-
|
|
264
|
+
Keys look like `erix_<scope>_<secret>`. Pass one as `apiKey`. The server
|
|
265
|
+
enforces its grant, so any call outside the grant fails with
|
|
266
|
+
`AuthenticationError` (HTTP 403). Over plain HTTP you can also send it as
|
|
267
|
+
`Authorization: Bearer erix_<scope>_…`.
|
|
408
268
|
|
|
409
|
-
|
|
269
|
+
```ts
|
|
270
|
+
import { ECODrIxAPI } from "@ecodrix/erix-api";
|
|
410
271
|
|
|
411
|
-
|
|
412
|
-
//
|
|
413
|
-
|
|
414
|
-
await ecod.crm.automations.create({
|
|
415
|
-
name: "Follow-up",
|
|
416
|
-
trigger: "lead_created",
|
|
417
|
-
nodes: [],
|
|
418
|
-
edges: [],
|
|
272
|
+
const events = new ECODrIxAPI({
|
|
273
|
+
apiKey: process.env.ECOD_EVENTS_KEY!, // erix_flow_… with flow:events
|
|
274
|
+
clientCode: "ERIX_CLNT_XXXXXX",
|
|
419
275
|
});
|
|
420
|
-
await ecod.crm.automations.update("rule_id", { isActive: false });
|
|
421
|
-
await ecod.crm.automations.toggle("rule_id");
|
|
422
|
-
await ecod.crm.automations.deleteRule("rule_id");
|
|
423
|
-
await ecod.crm.automations.bulkDelete(["rule_1", "rule_2"]);
|
|
424
|
-
|
|
425
|
-
// Testing & events
|
|
426
|
-
await ecod.crm.automations.test("rule_id", "lead_id");
|
|
427
|
-
const { data: events } = await ecod.crm.automations.getAvailableEvents();
|
|
428
|
-
|
|
429
|
-
// Enrollment management
|
|
430
|
-
const { data } = await ecod.crm.automations.enrollments("rule_id", {
|
|
431
|
-
status: "active",
|
|
432
|
-
page: 1,
|
|
433
|
-
});
|
|
434
|
-
await ecod.crm.automations.getEnrollment("enrollment_id");
|
|
435
|
-
await ecod.crm.automations.pauseEnrollment("rule_id", "enrollment_id");
|
|
436
|
-
await ecod.crm.automations.resumeEnrollment("rule_id", "enrollment_id");
|
|
437
|
-
|
|
438
|
-
// Run inspection
|
|
439
|
-
const { data: runs } = await ecod.crm.automations.runs("rule_id");
|
|
440
|
-
await ecod.crm.automations.getRun("run_id");
|
|
441
|
-
await ecod.crm.automations.resumeRun("run_id");
|
|
442
|
-
await ecod.crm.automations.abortRun("run_id");
|
|
443
|
-
|
|
444
|
-
// Unlock a wait_event node from external tool (e.g. Zapier, payment gateway callback)
|
|
445
|
-
await ecod.crm.automations.webhookEvent("rule_id", "payment_received", {
|
|
446
|
-
amount: 1500,
|
|
447
|
-
currency: "INR",
|
|
448
|
-
});
|
|
449
|
-
```
|
|
450
|
-
|
|
451
|
-
---
|
|
452
|
-
|
|
453
|
-
#### `ecod.crm.sequences`
|
|
454
|
-
|
|
455
|
-
Manually enroll / unenroll leads in drip automation sequences.
|
|
456
276
|
|
|
457
|
-
|
|
458
|
-
await ecod.crm.sequences.enroll({
|
|
459
|
-
leadId: "lead_id",
|
|
460
|
-
ruleId: "rule_id",
|
|
461
|
-
variables: { discount: "20%" },
|
|
462
|
-
});
|
|
463
|
-
await ecod.crm.sequences.unenroll("enrollment_id");
|
|
464
|
-
const { data } = await ecod.crm.sequences.listForLead("lead_id");
|
|
277
|
+
await events.product.erix.events.trigger({ trigger: "booking_created", phone: "+91XXXXXXXXXX" });
|
|
465
278
|
```
|
|
466
279
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
#### `ecod.crm.activities`
|
|
470
|
-
|
|
471
|
-
```typescript
|
|
472
|
-
// Lead timeline (all CRM events in order)
|
|
473
|
-
const { data } = await ecod.crm.activities.timeline("lead_id", {
|
|
474
|
-
page: 1,
|
|
475
|
-
limit: 50,
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
// Filtered activity list
|
|
479
|
-
const { data } = await ecod.crm.activities.list("lead_id", { type: "call" });
|
|
280
|
+
## CRM: leads and pipelines
|
|
480
281
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
282
|
+
```ts
|
|
283
|
+
// Create or update by phone. `refs` are your own IDs, kept on the lead.
|
|
284
|
+
await ecod.product.erix.leads.upsert({
|
|
285
|
+
leadData: { phone: "+91XXXXXXXXXX", firstName: "Test", source: "website" },
|
|
286
|
+
refs: { orderId: "ORD-1042" },
|
|
486
287
|
});
|
|
487
288
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
title: "Campaign Email Opened",
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
// --- Notes ---
|
|
496
|
-
const { data: notes } = await ecod.crm.activities.notes.list("lead_id");
|
|
497
|
-
await ecod.crm.activities.notes.create("lead_id", {
|
|
498
|
-
content: "Call scheduled for Monday",
|
|
289
|
+
const { data: leads, pagination } = await ecod.product.erix.leads.list({
|
|
290
|
+
status: "open", // open | won | lost | archived
|
|
291
|
+
search: "test",
|
|
292
|
+
limit: 25,
|
|
499
293
|
});
|
|
500
|
-
await ecod.crm.activities.notes.update("note_id", "Updated note text");
|
|
501
|
-
await ecod.crm.activities.notes.pin("note_id"); // pin to top
|
|
502
|
-
await ecod.crm.activities.notes.pin("note_id", false); // unpin
|
|
503
|
-
```
|
|
504
294
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
const { data: overview } = await ecod.crm.analytics.overview("pipeline_id");
|
|
511
|
-
const { data: conversion } =
|
|
512
|
-
await ecod.crm.analytics.conversionRate("pipeline_id");
|
|
513
|
-
const { data: velocity } = await ecod.crm.analytics.dealVelocity("pipeline_id");
|
|
514
|
-
const { data: stageTime } = await ecod.crm.analytics.stageTime("pipeline_id");
|
|
295
|
+
await ecod.product.erix.leads.move("LEAD_ID", "STAGE_ID"); // pipeline column
|
|
296
|
+
await ecod.product.erix.leads.setFollowUp("LEAD_ID", "2026-10-20T15:00:00+05:30", "Share the quote");
|
|
297
|
+
await ecod.product.erix.leads.tags("LEAD_ID", { add: ["hot"], remove: ["cold"] });
|
|
298
|
+
await ecod.product.erix.leads.convert("LEAD_ID", "won", "Advance received");
|
|
299
|
+
await ecod.product.erix.leads.delete("LEAD_ID"); // archives; history stays
|
|
515
300
|
```
|
|
516
301
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
302
|
+
Other lookups: `retrieve(id)`, `retrieveByPhone(phone)`,
|
|
303
|
+
`retrieveByRef(key, value)`, `update(id, patch)`, `notes(id)`,
|
|
304
|
+
`createNote(id, { content })`, `activities(id)`, `profile(id)`,
|
|
305
|
+
`import(leads)` and `merge(primaryId, secondaryId)`.
|
|
520
306
|
|
|
521
|
-
|
|
522
|
-
const { data } = await ecod.crm.scoring.getConfig();
|
|
523
|
-
await ecod.crm.scoring.updateConfig({ rules: [...] });
|
|
524
|
-
```
|
|
525
|
-
|
|
526
|
-
---
|
|
307
|
+
Data protection (DPDP). Only owners and admins can do this:
|
|
527
308
|
|
|
528
|
-
|
|
309
|
+
```ts
|
|
310
|
+
const { data: everything } = await ecod.product.erix.leads.export("LEAD_ID"); // (3.2.0)
|
|
529
311
|
|
|
530
|
-
|
|
531
|
-
|
|
312
|
+
// Irreversible. Wipes personal data across CRM, inbox and automations.
|
|
313
|
+
// Invoices are kept for GST records.
|
|
314
|
+
const { data: result } = await ecod.product.erix.leads.erase("LEAD_ID"); // (3.2.0)
|
|
315
|
+
console.log(result.invoicesKept);
|
|
532
316
|
```
|
|
533
317
|
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
#### `ecod.crm.automationDashboard`
|
|
318
|
+
Pipelines:
|
|
537
319
|
|
|
538
|
-
```
|
|
539
|
-
const { data:
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
status: "failed",
|
|
320
|
+
```ts
|
|
321
|
+
const { data: pipeline } = await ecod.product.erix.pipelines.create({
|
|
322
|
+
name: "Admissions",
|
|
323
|
+
stages: ["Enquiry", "Visit booked", "Fee paid", "Admitted"],
|
|
543
324
|
});
|
|
544
|
-
await ecod.crm.automationDashboard.retryFailedEvent("log_id");
|
|
545
|
-
```
|
|
546
|
-
|
|
547
|
-
---
|
|
548
|
-
|
|
549
|
-
### Events & Workflows
|
|
550
325
|
|
|
551
|
-
|
|
326
|
+
const { data: pipelines } = await ecod.product.erix.pipelines.list();
|
|
327
|
+
const { data: board } = await ecod.product.erix.pipelines.board("PIPELINE_ID");
|
|
552
328
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const { data: triggers } = await ecod.events.list();
|
|
329
|
+
await ecod.product.erix.pipelines.addStage("PIPELINE_ID", { name: "Follow-up", color: "#f59e0b" });
|
|
330
|
+
// Moves the stage's leads to another stage of the same pipeline, then deletes it.
|
|
331
|
+
await ecod.product.erix.pipelines.deleteStage("STAGE_ID", "TARGET_STAGE_ID");
|
|
557
332
|
```
|
|
558
333
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
```typescript
|
|
562
|
-
await ecod.events.assign({
|
|
563
|
-
name: "cart_abandoned",
|
|
564
|
-
displayName: "Shopping Cart Abandoned",
|
|
565
|
-
pipelineId: "pipeline_123",
|
|
566
|
-
});
|
|
567
|
-
|
|
568
|
-
// Deactivate
|
|
569
|
-
await ecod.events.unassign("cart_abandoned");
|
|
570
|
-
await ecod.events.unassignBulk(["event_a", "event_b"]);
|
|
571
|
-
```
|
|
334
|
+
## Flow: events and custom triggers
|
|
572
335
|
|
|
573
|
-
|
|
336
|
+
`events.trigger()` fires an event. Every active flow with a matching trigger
|
|
337
|
+
starts a run for that contact. You need `phone` or `email`.
|
|
574
338
|
|
|
575
|
-
```
|
|
576
|
-
await ecod.events.trigger({
|
|
577
|
-
trigger: "
|
|
578
|
-
phone: "+
|
|
579
|
-
variables: {
|
|
339
|
+
```ts
|
|
340
|
+
const res = await ecod.product.erix.events.trigger({
|
|
341
|
+
trigger: "appointment_booked",
|
|
342
|
+
phone: "+91XXXXXXXXXX",
|
|
343
|
+
variables: { slot: "Mon 11:00", branch: "Indore" },
|
|
580
344
|
createLeadIfMissing: true,
|
|
581
|
-
|
|
582
|
-
//
|
|
583
|
-
|
|
584
|
-
meetConfig: {
|
|
585
|
-
summary: "Follow-up call",
|
|
586
|
-
duration: 30,
|
|
587
|
-
timezone: "Asia/Kolkata",
|
|
588
|
-
},
|
|
345
|
+
leadData: { firstName: "Test", source: "website" },
|
|
346
|
+
delayMinutes: 30, // or delaySeconds, or runAt (ISO with offset). Use one.
|
|
347
|
+
idempotencyKey: "booking-8812", // a retry with the same key won't start flows twice
|
|
589
348
|
});
|
|
349
|
+
console.log(res.data?.idempotencyKey, res.data?.leadId);
|
|
590
350
|
```
|
|
591
351
|
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
```typescript
|
|
595
|
-
const { data } = await ecod.events.listCustomEvents();
|
|
596
|
-
await ecod.events.createCustomEvent({
|
|
597
|
-
name: "webinar_attended",
|
|
598
|
-
displayName: "Webinar Attended",
|
|
599
|
-
});
|
|
600
|
-
await ecod.events.deleteCustomEvent("event_id");
|
|
601
|
-
await ecod.events.emit({ eventName: "webinar_attended", leadId: "lead_id" });
|
|
602
|
-
```
|
|
603
|
-
|
|
604
|
-
---
|
|
605
|
-
|
|
606
|
-
### Marketing
|
|
607
|
-
|
|
608
|
-
Access via `ecod.marketing`.
|
|
352
|
+
`createLeadIfMissing`, the delays and `runAt` are released per workspace.
|
|
353
|
+
Without the release the server ignores them and says so in the response.
|
|
609
354
|
|
|
610
|
-
|
|
355
|
+
Define your own triggers so flows can start on them:
|
|
611
356
|
|
|
612
|
-
```
|
|
613
|
-
await ecod.
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
html: "<h1>Get 20% off!</h1>",
|
|
357
|
+
```ts
|
|
358
|
+
await ecod.product.erix.events.createCustomEvent({
|
|
359
|
+
name: "appointment_confirm", // lowercase letters, digits, _ . -
|
|
360
|
+
displayName: "Appointment confirmed",
|
|
617
361
|
});
|
|
618
|
-
await ecod.marketing.emails.sendTest("dev@example.com");
|
|
619
|
-
```
|
|
620
|
-
|
|
621
|
-
**Campaigns (Email + SMS)**
|
|
622
362
|
|
|
623
|
-
|
|
624
|
-
await ecod.
|
|
625
|
-
await ecod.marketing.campaigns.list({ status: "active" });
|
|
626
|
-
await ecod.marketing.campaigns.retrieve("campaign_id");
|
|
627
|
-
await ecod.marketing.campaigns.update("campaign_id", {
|
|
628
|
-
subject: "New subject",
|
|
629
|
-
});
|
|
630
|
-
await ecod.marketing.campaigns.send("campaign_id", {
|
|
631
|
-
scheduledAt: "2026-05-01T09:00:00Z",
|
|
632
|
-
});
|
|
633
|
-
await ecod.marketing.campaigns.stats("campaign_id");
|
|
634
|
-
await ecod.marketing.campaigns.delete("campaign_id");
|
|
363
|
+
const { data } = await ecod.product.erix.events.listCustomEvents(); // data.triggers
|
|
364
|
+
await ecod.product.erix.events.deleteCustomEvent("TRIGGER_ID");
|
|
635
365
|
```
|
|
636
366
|
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
Unlike the direct `ecod.whatsapp.sendTemplate`, this endpoint resolves CRM field variables automatically.
|
|
367
|
+
Some runs stop at an approval step and wait for a person. You can list those
|
|
368
|
+
and decide on them:
|
|
640
369
|
|
|
641
|
-
```
|
|
642
|
-
await ecod.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
}
|
|
370
|
+
```ts
|
|
371
|
+
const { data } = await ecod.product.erix.ai.approvals(); // { drafts, runs }
|
|
372
|
+
for (const run of data.runs) {
|
|
373
|
+
// Pass run.nodeId (3.2.0) so a stale view can't decide a different step.
|
|
374
|
+
await ecod.product.erix.ai.decideRun(run.id, "approve", run.nodeId);
|
|
375
|
+
}
|
|
647
376
|
```
|
|
648
377
|
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
### Meetings
|
|
378
|
+
## Channels: inbox and direct send
|
|
652
379
|
|
|
653
|
-
|
|
380
|
+
All channels share one inbox. Any thread can be answered with
|
|
381
|
+
`chat.messages.send({ conversationId })`.
|
|
654
382
|
|
|
655
|
-
```
|
|
656
|
-
const { data:
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
participantPhone: "+919876543210",
|
|
660
|
-
startTime: "2026-04-10T10:00:00.000Z",
|
|
661
|
-
endTime: "2026-04-10T10:30:00.000Z",
|
|
383
|
+
```ts
|
|
384
|
+
const { data: threads } = await ecod.product.erix.chat.conversations.list({
|
|
385
|
+
channel: "whatsapp",
|
|
386
|
+
limit: 20,
|
|
662
387
|
});
|
|
663
|
-
console.log(meeting.meetLink); // → https://meet.google.com/abc-defg-hij
|
|
664
|
-
|
|
665
|
-
const { data: meetings } = await ecod.meet.list({ status: "scheduled" });
|
|
666
|
-
const { data } = await ecod.meet.retrieve("meeting_id");
|
|
667
|
-
await ecod.meet.update("meeting_id", { startTime: "2026-04-11T11:00:00.000Z" });
|
|
668
|
-
await ecod.meet.delete("meeting_id");
|
|
669
|
-
```
|
|
670
|
-
|
|
671
|
-
---
|
|
672
388
|
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
This handles the full presigned-URL orchestration transparently:
|
|
678
|
-
|
|
679
|
-
1. Requests a presigned PUT URL from the backend.
|
|
680
|
-
2. Uploads directly to R2 (no proxy overhead).
|
|
681
|
-
3. Confirms the upload.
|
|
682
|
-
|
|
683
|
-
```typescript
|
|
684
|
-
// Node.js: from a Buffer
|
|
685
|
-
import { readFileSync } from "fs";
|
|
389
|
+
const { data: messages, hasMore } = await ecod.product.erix.chat.conversations.messages(
|
|
390
|
+
"CONVERSATION_ID",
|
|
391
|
+
{ limit: 50 }, // page back with { before: oldestMessage.createdAt }
|
|
392
|
+
);
|
|
686
393
|
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
filename: "contract.pdf",
|
|
691
|
-
contentType: "application/pdf",
|
|
394
|
+
await ecod.product.erix.chat.messages.send({
|
|
395
|
+
conversationId: "CONVERSATION_ID",
|
|
396
|
+
text: "Your order is packed and ships today.",
|
|
692
397
|
});
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
//
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
filename: file.name,
|
|
700
|
-
contentType: file.type,
|
|
398
|
+
await ecod.product.erix.chat.conversations.markRead("CONVERSATION_ID");
|
|
399
|
+
|
|
400
|
+
// Open (or find) a thread for a number and link it to a lead.
|
|
401
|
+
const { data: thread } = await ecod.product.erix.chat.conversations.create({
|
|
402
|
+
phone: "+91XXXXXXXXXX",
|
|
403
|
+
channel: "whatsapp",
|
|
701
404
|
});
|
|
405
|
+
await ecod.product.erix.chat.conversations.linkLead(thread.id, "LEAD_ID");
|
|
702
406
|
```
|
|
703
407
|
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
### Media
|
|
707
|
-
|
|
708
|
-
Access via `ecod.media`. Manage files stored in R2.
|
|
709
|
-
|
|
710
|
-
```typescript
|
|
711
|
-
// Get a presigned download URL for a private file
|
|
712
|
-
const { data } = await ecod.media.getDownloadUrl("confidential/contract.pdf");
|
|
713
|
-
|
|
714
|
-
// Monitor storage quota
|
|
715
|
-
const { data: usage } = await ecod.media.getUsage();
|
|
716
|
-
console.log(`${usage.usedMB} MB of ${usage.limitMB} MB used`);
|
|
408
|
+
Send a file. Upload it to your storage first, then attach it:
|
|
717
409
|
|
|
718
|
-
|
|
719
|
-
|
|
410
|
+
```ts
|
|
411
|
+
const input = document.querySelector<HTMLInputElement>("#attachment")!;
|
|
412
|
+
const media = await ecod.product.erix.chat.messages.upload(input.files![0]);
|
|
720
413
|
|
|
721
|
-
|
|
722
|
-
|
|
414
|
+
await ecod.product.erix.chat.messages.send({
|
|
415
|
+
conversationId: "CONVERSATION_ID",
|
|
416
|
+
media,
|
|
417
|
+
text: "Invoice attached",
|
|
418
|
+
});
|
|
723
419
|
```
|
|
724
420
|
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
### Email
|
|
421
|
+
Direct WhatsApp send, without an inbox thread ID:
|
|
728
422
|
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
423
|
+
```ts
|
|
424
|
+
// An approved template. Use this to start a conversation.
|
|
425
|
+
await ecod.product.erix.chat.messages.sendTemplate({
|
|
426
|
+
to: "+91XXXXXXXXXX",
|
|
427
|
+
templateName: "order_update",
|
|
428
|
+
language: "en",
|
|
429
|
+
variables: ["ORD-1042", "₹1,499"],
|
|
736
430
|
});
|
|
737
431
|
|
|
738
|
-
//
|
|
739
|
-
await ecod.
|
|
432
|
+
// Free text. Only works inside WhatsApp's 24-hour customer service window.
|
|
433
|
+
await ecod.product.erix.chat.messages.send({ to: "+91XXXXXXXXXX", text: "Thanks, we got your enquiry." });
|
|
740
434
|
```
|
|
741
435
|
|
|
742
|
-
|
|
436
|
+
Manage templates with `ecod.infra.connect.whatsapp.templates`: `list()`,
|
|
437
|
+
`sync()` and `retrieve(name)`.
|
|
743
438
|
|
|
744
|
-
|
|
439
|
+
## Invoices and payment links
|
|
745
440
|
|
|
746
|
-
|
|
441
|
+
Amounts are in rupees. This invoice comes to ₹25,000 + 18% GST = ₹29,500:
|
|
747
442
|
|
|
748
|
-
```
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
443
|
+
```ts
|
|
444
|
+
const { data: invoice } = await ecod.product.erix.invoices.create({
|
|
445
|
+
clientName: "Test Customer",
|
|
446
|
+
clientPhone: "+91XXXXXXXXXX",
|
|
447
|
+
leadId: "LEAD_ID",
|
|
448
|
+
items: [{ description: "Website design", quantity: 1, rate: 25000, hsnSac: "998314" }],
|
|
449
|
+
taxRate: 18,
|
|
450
|
+
dueDate: "2026-11-15",
|
|
755
451
|
});
|
|
756
452
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
const { data: stats } = await ecod.notifications.getStats({
|
|
761
|
-
startDate: "2026-04-01",
|
|
762
|
-
endDate: "2026-04-30",
|
|
763
|
-
});
|
|
453
|
+
// A link on your connected gateway (Razorpay or Stripe). Calling again returns the same link.
|
|
454
|
+
const { data: link } = await ecod.product.erix.invoices.paymentLink(invoice.id); // (3.2.0)
|
|
455
|
+
console.log(link.paymentLinkUrl);
|
|
764
456
|
|
|
765
|
-
//
|
|
766
|
-
|
|
457
|
+
await ecod.product.erix.invoices.sendWhatsApp(invoice.id); // (3.2.0) with the payment link
|
|
458
|
+
await ecod.product.erix.invoices.sendEmail(invoice.id); // (3.2.0) PDF + payment link
|
|
767
459
|
```
|
|
768
460
|
|
|
769
|
-
|
|
461
|
+
Invoices can't be deleted, only voided (`invoices.void(id)`), so GST records
|
|
462
|
+
stay intact. Other calls: `list({ status })`, `getInvoice(id)` and
|
|
463
|
+
`update(id, patch)`. Accounting exports live under
|
|
464
|
+
`product.erix.invoiceExport`: `csv()`, `tally()` and `zoho()`.
|
|
770
465
|
|
|
771
|
-
|
|
466
|
+
A one-off payment link that isn't tied to an invoice, or a payment you took
|
|
467
|
+
offline:
|
|
772
468
|
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
// Get queue health by status
|
|
780
|
-
const stats = await ecod.queue.getStats();
|
|
781
|
-
// stats → { waiting: 12, active: 3, completed: 482, failed: 7, delayed: 0 }
|
|
782
|
-
|
|
783
|
-
// Retry a failed job
|
|
784
|
-
await ecod.queue.retryJob("job_id");
|
|
469
|
+
```ts
|
|
470
|
+
await ecod.infra.connect.connections.createPaymentLink({
|
|
471
|
+
amount: 1499, // ₹1,499
|
|
472
|
+
description: "Consultation fee",
|
|
473
|
+
customer: { phone: "+91XXXXXXXXXX" },
|
|
474
|
+
});
|
|
785
475
|
|
|
786
|
-
//
|
|
787
|
-
await ecod.
|
|
476
|
+
// Cash, UPI or bank transfer. With orderId, a retry is counted once.
|
|
477
|
+
await ecod.product.erix.payments.capture({ leadId: "LEAD_ID", amount: 5000, orderId: "UPI-2026-0042" });
|
|
788
478
|
```
|
|
789
479
|
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
### Health & Diagnostics
|
|
480
|
+
## Storage uploads
|
|
793
481
|
|
|
794
|
-
|
|
482
|
+
`media.upload()` gets a presigned URL, uploads straight to storage and then
|
|
483
|
+
confirms the upload, all in one call. The file bytes never pass through the
|
|
484
|
+
API, and your key never goes to the storage host.
|
|
795
485
|
|
|
796
|
-
```
|
|
797
|
-
|
|
798
|
-
const health = await ecod.health.system();
|
|
799
|
-
// { status: "ok", version: "...", env: "production", uptime: 3600, db: "connected", queueDepth: 5 }
|
|
486
|
+
```ts
|
|
487
|
+
import { readFile } from "node:fs/promises";
|
|
800
488
|
|
|
801
|
-
|
|
802
|
-
const
|
|
803
|
-
|
|
489
|
+
const buffer = await readFile("./brochure.pdf");
|
|
490
|
+
const { data: file } = await ecod.infra.storage.media.upload(buffer, {
|
|
491
|
+
folder: "documents",
|
|
492
|
+
filename: "brochure.pdf",
|
|
493
|
+
contentType: "application/pdf",
|
|
494
|
+
});
|
|
804
495
|
|
|
805
|
-
|
|
806
|
-
|
|
496
|
+
const { data: download } = await ecod.infra.storage.files.getDownloadUrl<{
|
|
497
|
+
data: { downloadUrl: string };
|
|
498
|
+
}>(file.key);
|
|
499
|
+
console.log(download.downloadUrl); // short-lived URL
|
|
807
500
|
```
|
|
808
501
|
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
### Webhooks
|
|
812
|
-
|
|
813
|
-
Access via `ecod.webhooks`.
|
|
502
|
+
The same call takes a browser `File`. Other calls: `files.list(folder)` and
|
|
503
|
+
`files.delete(key)`.
|
|
814
504
|
|
|
815
|
-
|
|
505
|
+
## Realtime events
|
|
816
506
|
|
|
817
|
-
|
|
507
|
+
Events stream over a socket that's scoped to your workspace. You only receive
|
|
508
|
+
your own workspace's events.
|
|
818
509
|
|
|
819
|
-
|
|
510
|
+
```ts
|
|
511
|
+
import type { Message } from "@ecodrix/erix-api";
|
|
820
512
|
|
|
821
|
-
|
|
513
|
+
const onMessage = ({ conversationId, message }: { conversationId: string; message: Message }) => {
|
|
514
|
+
console.log(conversationId, message.direction, message.text);
|
|
515
|
+
};
|
|
822
516
|
|
|
823
|
-
|
|
517
|
+
ecod
|
|
518
|
+
.on("inbox.message", onMessage)
|
|
519
|
+
.on("message_status_update", ({ messageId, status }) => console.log(messageId, status))
|
|
520
|
+
.on("resync", () => {
|
|
521
|
+
// Reconnected after a gap too large to replay. Refetch your data.
|
|
522
|
+
});
|
|
824
523
|
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
524
|
+
// Later, e.g. in a React effect cleanup:
|
|
525
|
+
ecod.off("inbox.message", onMessage);
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
| Event | Payload / meaning |
|
|
529
|
+
| --------------------------------------------- | ----------------------------------------------------------------------- |
|
|
530
|
+
| `inbox.message` | `{ conversationId, message }`. Every stored message: any channel, inbound or outbound |
|
|
531
|
+
| `message_status_update` | `{ messageId, conversationId, status }`. Sent, delivered, read or failed |
|
|
532
|
+
| `ai_suggestion` / `ai_handoff` | An AI draft is waiting / the AI handed a chat to a person |
|
|
533
|
+
| `workflow-run-update` | Flow run progress |
|
|
534
|
+
| `platform:notification` | A new in-app notification |
|
|
535
|
+
| `knowledge.source_updated` | A knowledge base source changed status |
|
|
536
|
+
| `resync` | Missed events couldn't be replayed. Refetch |
|
|
537
|
+
|
|
538
|
+
Each event's last argument is `{ seq }`. After a reconnect the SDK sends back
|
|
539
|
+
the last `seq` it saw and the server replays what was missed. A replay can
|
|
540
|
+
repeat an event, so write handlers that are safe to run twice (upsert by ID).
|
|
541
|
+
|
|
542
|
+
## Webhook signature verification
|
|
543
|
+
|
|
544
|
+
Flow's webhook step sends JSON to your URL. If you set a secret on the
|
|
545
|
+
Webhook connection, the request is signed with it in
|
|
546
|
+
`x-ecodrix-signature: sha256=<hex>`. Verify the signature against the **raw**
|
|
547
|
+
body, before you parse it. Node only.
|
|
548
|
+
|
|
549
|
+
```ts
|
|
550
|
+
import { APIError } from "@ecodrix/erix-api";
|
|
551
|
+
import { ecod } from "./ecodrix"; // your server-side client from Quick start
|
|
552
|
+
|
|
553
|
+
export async function POST(req: Request) {
|
|
554
|
+
const raw = await req.text();
|
|
555
|
+
try {
|
|
556
|
+
const event = await ecod.webhooks.constructEvent(
|
|
557
|
+
raw,
|
|
558
|
+
req.headers.get("x-ecodrix-signature") ?? undefined,
|
|
559
|
+
process.env.ECOD_WEBHOOK_SECRET!,
|
|
560
|
+
);
|
|
561
|
+
console.log("verified", event);
|
|
562
|
+
return Response.json({ received: true });
|
|
563
|
+
} catch (err) {
|
|
564
|
+
// WebhookSignatureError: APIError with code "invalid_signature"
|
|
565
|
+
const message = err instanceof APIError ? err.message : "Bad request";
|
|
566
|
+
return new Response(message, { status: 400 });
|
|
567
|
+
}
|
|
831
568
|
}
|
|
832
569
|
```
|
|
833
570
|
|
|
834
|
-
|
|
571
|
+
## Raw requests
|
|
835
572
|
|
|
836
|
-
|
|
573
|
+
Use `ecod.request()` for an endpoint that has no typed method yet. You still
|
|
574
|
+
get auth, retries and the timeout. Paths are relative to `/v1/api`:
|
|
837
575
|
|
|
838
|
-
```
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
### Idempotency Keys
|
|
844
|
-
|
|
845
|
-
Prevent duplicate requests caused by automatic retries:
|
|
846
|
-
|
|
847
|
-
```typescript
|
|
848
|
-
await ecod.email.sendEmailCampaign(
|
|
849
|
-
{ subject: "Promo", recipients: ["user@example.com"], html: "..." },
|
|
850
|
-
{ idempotencyKey: "promo_campaign_uuid_2026_q2" },
|
|
851
|
-
);
|
|
852
|
-
```
|
|
853
|
-
|
|
854
|
-
### Webhook Signature Verification
|
|
855
|
-
|
|
856
|
-
Cryptographically verify that webhook payloads originated from ECODrIx:
|
|
857
|
-
|
|
858
|
-
```typescript
|
|
859
|
-
app.post(
|
|
860
|
-
"/api/webhooks",
|
|
861
|
-
express.raw({ type: "application/json" }),
|
|
862
|
-
async (req, res) => {
|
|
863
|
-
try {
|
|
864
|
-
const event = await ecod.webhooks.constructEvent(
|
|
865
|
-
req.body.toString(),
|
|
866
|
-
req.headers["x-ecodrix-signature"],
|
|
867
|
-
"whsec_your_webhook_secret",
|
|
868
|
-
);
|
|
869
|
-
console.log("Verified event:", event);
|
|
870
|
-
res.json({ received: true });
|
|
871
|
-
} catch (err) {
|
|
872
|
-
return res.status(400).send(`Invalid signature: ${err.message}`);
|
|
873
|
-
}
|
|
874
|
-
},
|
|
875
|
-
);
|
|
876
|
-
```
|
|
877
|
-
|
|
878
|
-
### Raw Execution Engine
|
|
879
|
-
|
|
880
|
-
Access any API endpoint — including experimental or custom ones — with full authentication and retry benefits:
|
|
881
|
-
|
|
882
|
-
```typescript
|
|
883
|
-
const { data } = await ecod.request("POST", "/api/saas/beta-feature", {
|
|
884
|
-
experimentalFlag: true,
|
|
576
|
+
```ts
|
|
577
|
+
await ecod.request("POST", "/infra/connect/send/email", {
|
|
578
|
+
to: "customer@example.com",
|
|
579
|
+
subject: "Your booking is confirmed",
|
|
580
|
+
html: "<p>See you on Monday at 11:00.</p>",
|
|
885
581
|
});
|
|
886
582
|
```
|
|
887
583
|
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
## Real-time Events
|
|
584
|
+
## Errors
|
|
891
585
|
|
|
892
|
-
|
|
586
|
+
Failed calls throw. The API answers with an envelope like
|
|
587
|
+
`{ success: false, error, message, code? }`, and the SDK turns it into one of
|
|
588
|
+
these classes:
|
|
893
589
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
console.log(`New lead: ${leadId}`);
|
|
901
|
-
})
|
|
902
|
-
.on("crm.lead_updated", ({ leadId, stageName }) => {
|
|
903
|
-
console.log(`${leadId} moved to ${stageName}`);
|
|
904
|
-
})
|
|
905
|
-
.on("meet.scheduled", ({ meetLink }) => {
|
|
906
|
-
console.log(`Meeting booked: ${meetLink}`);
|
|
907
|
-
})
|
|
908
|
-
.on("automation.failed", ({ trigger, reason }) => {
|
|
909
|
-
alertTeam(`Automation ${trigger} failed: ${reason}`);
|
|
910
|
-
});
|
|
590
|
+
| Class | When |
|
|
591
|
+
| --------------------- | -------------------------------------------------------------------- |
|
|
592
|
+
| `EcodrixError` | Base class of everything below |
|
|
593
|
+
| `APIError` | Any other failure. Has `.status`, `.code` (when sent) and `.message` |
|
|
594
|
+
| `AuthenticationError` | HTTP 401 or 403: bad key or client code, missing scope, or a role that isn't allowed |
|
|
595
|
+
| `RateLimitError` | HTTP 429, after the SDK's own retries run out |
|
|
911
596
|
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
```
|
|
915
|
-
|
|
916
|
-
### Standard Event Reference
|
|
917
|
-
|
|
918
|
-
| Event | Payload | Description |
|
|
919
|
-
| --------------------------- | -------------------------------- | ------------------------------ |
|
|
920
|
-
| `whatsapp.message_received` | `{ from, body, conversationId }` | Inbound WhatsApp message |
|
|
921
|
-
| `whatsapp.message_sent` | `{ to, messageId }` | Outbound message delivered |
|
|
922
|
-
| `crm.lead_created` | `{ leadId, phone }` | New CRM lead created |
|
|
923
|
-
| `crm.lead_updated` | `{ leadId, stageName }` | Lead stage or fields updated |
|
|
924
|
-
| `meet.scheduled` | `{ meetingId, meetLink }` | Google Meet appointment booked |
|
|
925
|
-
| `storage.upload_confirmed` | `{ key, url, sizeBytes }` | File upload confirmed |
|
|
926
|
-
| `automation.failed` | `{ trigger, reason, leadId }` | Automation execution failed |
|
|
927
|
-
|
|
928
|
-
---
|
|
929
|
-
|
|
930
|
-
## Error Handling
|
|
931
|
-
|
|
932
|
-
All methods throw typed errors you can catch and inspect:
|
|
933
|
-
|
|
934
|
-
```typescript
|
|
935
|
-
import {
|
|
936
|
-
APIError,
|
|
937
|
-
AuthenticationError,
|
|
938
|
-
RateLimitError,
|
|
939
|
-
Ecodrix,
|
|
940
|
-
} from "@ecodrix/erix-api";
|
|
597
|
+
```ts
|
|
598
|
+
import { APIError, AuthenticationError, RateLimitError } from "@ecodrix/erix-api";
|
|
941
599
|
|
|
942
600
|
try {
|
|
943
|
-
|
|
601
|
+
await ecod.product.erix.leads.retrieve("LEAD_ID");
|
|
944
602
|
} catch (err) {
|
|
945
603
|
if (err instanceof AuthenticationError) {
|
|
946
|
-
|
|
947
|
-
console.error("Check your credentials.");
|
|
604
|
+
console.error("Check the key, client code, scope or role:", err.message);
|
|
948
605
|
} else if (err instanceof RateLimitError) {
|
|
949
|
-
|
|
950
|
-
console.warn("Rate limit hit.");
|
|
606
|
+
console.warn("Rate limited, back off and retry later");
|
|
951
607
|
} else if (err instanceof APIError) {
|
|
952
|
-
console.error(`API
|
|
608
|
+
console.error(`API error ${err.status ?? "network"}: ${err.message}`, err.code);
|
|
953
609
|
} else {
|
|
954
610
|
throw err;
|
|
955
611
|
}
|
|
956
612
|
}
|
|
957
613
|
```
|
|
958
614
|
|
|
959
|
-
|
|
615
|
+
`ecod.request()` throws a plain `APIError` for every status.
|
|
960
616
|
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
617
|
+
**Retries and timeouts.** The SDK retries up to 3 times with exponential
|
|
618
|
+
backoff. It retries network errors (any method), 5xx responses on idempotent
|
|
619
|
+
methods (GET, PUT, DELETE) and 429 responses. Each request times out after
|
|
620
|
+
30 s; change that with the `timeout` option. A timed-out request isn't retried.
|
|
621
|
+
A network error can also hit a POST whose response was lost, and the retry
|
|
622
|
+
then sends it a second time. Pass your own reference where a method takes one
|
|
623
|
+
(`refs`, `orderId`) so a duplicate is safe.
|
|
967
624
|
|
|
968
|
-
|
|
625
|
+
## TypeScript
|
|
969
626
|
|
|
970
|
-
|
|
627
|
+
Methods return the response body, so pass a type argument to get it typed.
|
|
628
|
+
The SDK exports the main entity and parameter types (`Lead`,
|
|
629
|
+
`CreateLeadParams`, `ListLeadsParams`, `Pipeline`, `PipelineStage`,
|
|
630
|
+
`Conversation`, `Message`, `SendMessageParams`, `PausedRun`, `AiSuggestion`,
|
|
631
|
+
`KnowledgeSource`, `PaginatedResponse` and more).
|
|
971
632
|
|
|
972
|
-
|
|
633
|
+
```ts
|
|
634
|
+
import type { Lead, PaginatedResponse } from "@ecodrix/erix-api";
|
|
973
635
|
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
<script src="https://cdn.jsdelivr.net/npm/@ecodrix/erix-api/dist/ts/browser/index.global.js"></script>
|
|
977
|
-
<script>
|
|
978
|
-
const ecod = new Ecodrix.Ecodrix({
|
|
979
|
-
apiKey: "your_api_key",
|
|
980
|
-
clientCode: "YOUR_CLIENT_CODE",
|
|
981
|
-
});
|
|
636
|
+
const page = await ecod.product.erix.leads.list<PaginatedResponse<Lead>>({ limit: 50 });
|
|
637
|
+
console.log(page.pagination.total, page.data[0]?.firstName);
|
|
982
638
|
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
</script>
|
|
639
|
+
// Page through every lead without handling pages yourself.
|
|
640
|
+
for await (const lead of ecod.product.erix.leads.listAutoPaging<Lead>({ status: "open" })) {
|
|
641
|
+
console.log(lead.phone);
|
|
642
|
+
}
|
|
988
643
|
```
|
|
989
644
|
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
---
|
|
993
|
-
|
|
994
|
-
## Contributing
|
|
645
|
+
## Versioning
|
|
995
646
|
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
647
|
+
- The SDK follows [semver](https://semver.org) on its own line (3.x). Breaking
|
|
648
|
+
changes only land in a major release. See [CHANGELOG.md](CHANGELOG.md).
|
|
649
|
+
- The API server has its own version numbers: platform v1.0 is server 1.0.0.
|
|
650
|
+
The SDK calls the `/v1` routes, and the `version` option chooses the route
|
|
651
|
+
version.
|
|
652
|
+
- A method marked **(3.2.0)** needs SDK 3.2.0 or later.
|
|
1001
653
|
|
|
1002
|
-
|
|
654
|
+
Guides: https://api.ecodrix.com/docs. Endpoint reference:
|
|
655
|
+
https://api.ecodrix.com/reference.
|
|
1003
656
|
|
|
1004
657
|
## License
|
|
1005
658
|
|
|
1006
|
-
[MIT](LICENSE)
|
|
659
|
+
[MIT](LICENSE)
|