@ ecodrix/ erix- api
官方类型SDK ECODRIx 函数 平台. 使用您自己的后端来工作空间: CRM 引导和 管道、流程自动化、统一收件箱(WhatsApp、Instagram、Facebook、, 电报、电子邮件、网络聊天、发票和付款链接、文件存储和实况 事件。 每一次通话都被监视到你的钥匙所属的工作空间.
安装
pnpm add @ecodrix/erix-api
# or: npm install @ecodrix/erix-api
- 节点.js 18+ (使用全球
fetch财务报告和审定财务报表Blob) (中文(简体) ). 船舶无害环境管理和共同JS 以捆绑的类型定义构建 . - 浏览器: 通过捆绑器( Vite, Next.js, webpack) 支持 代理模式 只加无键 网站聊天访问客户端没有 CDN建设.
快开始
在您的服务器上运行 。 将密钥保存在只使用服务器的环境变量中 .
import { ECODrIxAPI } from "@ecodrix/erix-api";
const ecod = new ECODrIxAPI({
apiKey: process.env.ECOD_API_KEY!,
clientCode: "ERIX_CLNT_XXXXXX", // Settings → Developer in the dashboard
});
const { data: lead } = await ecod.product.erix.leads.create({
firstName: "Test",
lastName: "Customer",
phone: "+91XXXXXXXXXX",
source: "website",
});
await ecod.product.erix.events.trigger({
trigger: "enquiry_received",
phone: "+91XXXXXXXXXX",
});
// The client keeps a realtime socket open. Close it when your process stops.
process.on("SIGTERM", () => ecod.disconnect());
| 选项 | 类型 | 默认 | 页:1 |
|---|---|---|---|
apiKey | string | 无 | 工作空间密钥或 a 范围密钥仅限服务器. |
clientCode | string | 无 | 你的工作空间代码,例如. ERIX_CLNT_XXXXXX。 。 。 . |
version | "v1" | "v1" | API路由版本. |
timeout | number (单位:百万美元) | 30000 | 每一个请求. 0 禁用 . |
proxy | { url, ticket? } | 无 | 浏览器模式 。 替换 apiKey。 。 。 . |
如何安排客户
客户端遵循API的路由树:
| 命名空间 | 它包含什么 |
|---|---|
ecod.product.erix.* | 客户关系管理leads, (中文(简体) ), pipelines, (中文(简体) ), activities, (中文(简体) ), analytics, (中文(简体) ), events ( Flow 触发器), chat (输入), invoices, (中文(简体) ), payments, (中文(简体) ), ai |
ecod.infra.connect.* | 频道 : whatsapp.templates, (中文(简体) ), email, (中文(简体) ), meet, (中文(简体) ), connections |
ecod.infra.storage.* | 文件 : media, (中文(简体) ), files, (中文(简体) ), folders |
ecod.platform.* | 工作空间 : knowledge (大赦国际知识库), users.notifications |
ecod.webhooks | Webhook 签名验证 |
ecod.on() 编号 : ecod.off() | 实时活动 |
ecod.request() | 还没有输入方法就授权调用任何端点 |
浏览器使用( 代理模式)
从不将工作空间密钥放入浏览器中. 这包括: NEXT_PUBLIC_*, (中文(简体) ),
VITE_* 或任何其它的变量 最终在客户端捆绑。 谁
打开dev工具可以读取它,一个工作空间密钥可以完全访问.
在浏览器中,以代理模式运行 SDK :
- 您的服务器曝光
/api/erix/*它检查您的登录用户, 并添加 键,并将请求转发到 ECODrIx. - 实时来说,你的服务器薄荷 5分钟的套接字票。 浏览器得到 罚单,没有钥匙.
代理路由(Next.js App路由器, app/api/erix/[...path]/route.ts:
const API = "https://api.ecodrix.com";
const PASS_HEADERS = ["content-type", "accept", "idempotency-key"];
const DROP_HEADERS = ["content-encoding", "content-length", "transfer-encoding", "connection"];
async function proxy(req: Request, ctx: { params: Promise<{ path: string[] }> }) {
const user = await requireUser(req); // your own session check
if (!user) {
return Response.json({ success: false, error: "Unauthorized" }, { status: 401 });
}
const { path } = await ctx.params;
const rel = `/${path.map(encodeURIComponent).join("/")}`;
if (!/^\/v\d+\/api\//.test(rel)) {
return Response.json({ success: false, error: "Not found" }, { status: 404 });
}
const headers = new Headers({
"x-api-key": process.env.ECOD_API_KEY!,
"x-client-code": "ERIX_CLNT_XXXXXX",
});
for (const name of PASS_HEADERS) {
const value = req.headers.get(name);
if (value) headers.set(name, value);
}
const hasBody = req.method !== "GET" && req.method !== "HEAD";
const upstream = await fetch(`${API}${rel}${new URL(req.url).search}`, {
method: req.method,
headers,
body: hasBody ? await req.arrayBuffer() : undefined,
redirect: "manual",
});
const out = new Headers(upstream.headers);
for (const name of DROP_HEADERS) out.delete(name);
return new Response(upstream.body, { status: upstream.status, headers: out });
}
export { proxy as GET, proxy as POST, proxy as PUT, proxy as PATCH, proxy as DELETE };
车票路线(app/api/erix/ticket/route.ts:
export async function POST(req: Request) {
const user = await requireUser(req); // your own session check
if (!user) return new Response("Unauthorized", { status: 401 });
const res = await fetch("https://api.ecodrix.com/v1/api/platform/realtime/ticket", {
method: "POST",
headers: {
"x-api-key": process.env.ECOD_API_KEY!,
"x-client-code": "ERIX_CLNT_XXXXXX",
},
cache: "no-store",
});
const body = (await res.json().catch(() => null)) as { data?: { ticket?: string } } | null;
const ticket = body?.data?.ticket;
if (!res.ok || !ticket) return new Response("Ticket unavailable", { status: 502 });
return new Response(ticket, {
headers: { "content-type": "text/plain", "cache-control": "no-store" },
});
}
浏览器客户端 :
import { ECODrIxAPI } from "@ecodrix/erix-api";
export const ecod = new ECODrIxAPI({
proxy: {
url: "/api/erix",
ticket: () =>
fetch("/api/erix/ticket", { method: "POST" }).then((r) =>
r.ok ? r.text() : Promise.reject(new Error("No realtime ticket")),
),
},
});
SDK每一次重联都会得到一张新票. 无 ticket后退时
调用工作而不打开套接字.
任何人签到您的应用程序 现在可以做 任何工作空间密钥所能做的. 如果你的用户不应该拥有所有这些,允许列出他们所需要的路径 代理路线.
网站聊天访问客户端
@ecodrix/erix-api/visitor 是一个匿名网站的单独条目
访问者与工作空间聊天。 它不需要钥匙,只达到
游客自己的线条。 用这个 fetch 和负载 socket.io-client 仅在
所以聊天部件不会把SDK装满 ERIX 聊天部件
运行在它.
import { WebchatVisitor, WebchatVisitorError } from "@ecodrix/erix-api/visitor";
const chat = new WebchatVisitor({
clientCode: "ERIX_CLNT_XXXXXX",
// The visitor's only secret: random, 20–80 characters of A-Z a-z 0-9 _ -.
// Keep it (e.g. in localStorage) to continue the same thread next visit.
sessionId,
// The site the chat runs on. It must be on the workspace's allowed domains.
parent: "https://www.example.in",
});
const { data: messages, widget, consentNotice } = await chat.messages();
try {
await chat.send("Do you open on Sunday?", {
messageId: crypto.randomUUID(), // resend with the same id: stored once
page: { path: location.pathname, title: document.title },
});
} catch (err) {
if (err instanceof WebchatVisitorError) {
console.warn(err.code, err.status, err.field, err.message);
}
}
await chat.contact({ name: "Ravi", phone: "98765 43210" });
现场答复和打字/visitor 套接字 :
chat.connect({
onMessage: (message) => upsert(message), // `from`: visitor | ai | team | system
onTyping: ({ on, waiting }) => showTyping(on, waiting), // waiting: the team will reply
onStatus: ({ ids, status }) => markTicks(ids, status), // "read": the team opened the chat
onError: () => startPolling(), // live is down: poll messages()
onReconnect: () => {
stopPolling();
chat.messages().then(redraw); // nothing missed is replayed
},
});
chat.disconnect();
滴答,跟WhatsApp一样 每个消息都有 status (单位:千美元)sent ✓, delivered ✓✓,
read 参观者自己是 delivered 曾经存储和 read 一次
团队打开了聊天。 报告企业的答复,最多50 ids a
调用; 勾选只向前移动 :
await chat.receipts(replyIds, "delivered"); // the chat got them
await chat.receipts(replyIds, "read"); // open and on screen
err.code | 含义 |
|---|---|
VALIDATION_ERROR | 坏输入. err.field 说吧, err.message 是访客副本 |
ORIGIN_NOT_ALLOWED | 网站不在工作空间允许的域上 |
NOT_FOUND | 未知客户端代码 |
RATE_LIMITED | 太多的信息或连接 。 单 |
MALICIOUS_INPUT | 文本被拒绝作为脚本或注射 |
INTERNAL_ERROR | 服务器错误 。 再试一次 |
NETWORK_ERROR | 无答(离线,30 s超时). 再试一次 |
活套接字只接受 ERIX 部件框架的出处 。 您的聊天用户界面
当HTTP网站是工作空间的网站(浏览器)时,自有的页面在HTTP上工作
和民意测验 messages() 用于答复.
范围化密钥
对于一个能完成一项工作的整合(一个启动"流"事件的预订形式, ),在下方创建范围化的密钥 设置 → 开发者 → 范围化 API 密钥。 。 。 . 范围化的密钥可以被撤销,可以过期并有使用跟踪. 每一个 一次性补助金:
| 目的 | 赠款 | 工程为 |
|---|---|---|
| 触发事件 | flow:events | product.erix.events.trigger() |
| 发送WhatsApp | connect:whatsapp | chat.messages.send({ to }), (中文(简体) ), sendTemplate() |
| 发送电子邮件 | connect:email | POST /infra/connect/send/email |
| 发送电报 | connect:telegram | POST /infra/connect/send/telegram |
钥匙看起来像 erix_<scope>_<secret>。通过一个 apiKey服务器
强制执行其赠款,因此赠款以外的任何电话都无法
AuthenticationError (HTTP 403) (韩语). 超普通的 HTTP 您也可以以
Authorization: Bearer erix_<scope>_…。 。 。 .
import { ECODrIxAPI } from "@ecodrix/erix-api";
const events = new ECODrIxAPI({
apiKey: process.env.ECOD_EVENTS_KEY!, // erix_flow_… with flow:events
clientCode: "ERIX_CLNT_XXXXXX",
});
await events.product.erix.events.trigger({ trigger: "booking_created", phone: "+91XXXXXXXXXX" });
客户关系管理:领导和管道
// Create or update by phone. `refs` are your own IDs, kept on the lead.
await ecod.product.erix.leads.upsert({
leadData: { phone: "+91XXXXXXXXXX", firstName: "Test", source: "website" },
refs: { orderId: "ORD-1042" },
});
const { data: leads, pagination } = await ecod.product.erix.leads.list({
status: "open", // open | won | lost | archived
search: "test",
limit: 25,
});
await ecod.product.erix.leads.move("LEAD_ID", "STAGE_ID"); // pipeline column
await ecod.product.erix.leads.setFollowUp("LEAD_ID", "2026-10-20T15:00:00+05:30", "Share the quote");
await ecod.product.erix.leads.tags("LEAD_ID", { add: ["hot"], remove: ["cold"] });
await ecod.product.erix.leads.convert("LEAD_ID", "won", "Advance received");
await ecod.product.erix.leads.delete("LEAD_ID"); // archives; history stays
其他检查 : retrieve(id), (中文(简体) ), retrieveByPhone(phone), (中文(简体) ),
retrieveByRef(key, value), (中文(简体) ), update(id, patch), (中文(简体) ), notes(id), (中文(简体) ),
createNote(id, { content }), (中文(简体) ), activities(id), (中文(简体) ), profile(id), (中文(简体) ),
import(leads) 财务报告和审定财务报表 merge(primaryId, secondaryId)。 。 。 .
数据保护(DPDP). 只有业主和管理人员才能做到这一点:
const { data: everything } = await ecod.product.erix.leads.export("LEAD_ID"); // (3.2.0)
// Irreversible. Wipes personal data across CRM, inbox and automations.
// Invoices are kept for GST records.
const { data: result } = await ecod.product.erix.leads.erase("LEAD_ID"); // (3.2.0)
console.log(result.invoicesKept);
管道:
const { data: pipeline } = await ecod.product.erix.pipelines.create({
name: "Admissions",
stages: ["Enquiry", "Visit booked", "Fee paid", "Admitted"],
});
const { data: pipelines } = await ecod.product.erix.pipelines.list();
const { data: board } = await ecod.product.erix.pipelines.board("PIPELINE_ID");
await ecod.product.erix.pipelines.addStage("PIPELINE_ID", { name: "Follow-up", color: "#f59e0b" });
// Moves the stage's leads to another stage of the same pipeline, then deletes it.
await ecod.product.erix.pipelines.deleteStage("STAGE_ID", "TARGET_STAGE_ID");
流程:事件和自定义触发器
events.trigger() 启动一个事件。 每个有匹配触发的活性流动
启动联系人运行 。 惠璶 phone 或者说 email。 。 。 .
const res = await ecod.product.erix.events.trigger({
trigger: "appointment_booked",
phone: "+91XXXXXXXXXX",
variables: { slot: "Mon 11:00", branch: "Indore" },
createLeadIfMissing: true,
leadData: { firstName: "Test", source: "website" },
delayMinutes: 30, // or delaySeconds, or runAt (ISO with offset). Use one.
idempotencyKey: "booking-8812", // a retry with the same key won't start flows twice
});
console.log(res.data?.idempotencyKey, res.data?.leadId);
createLeadIfMissing,延迟和 runAt 每个工作空间都会释放.
没有发布,服务器会忽略他们,在回应中会这样说.
定义自己的触发器, 以便启动它们 :
await ecod.product.erix.events.createCustomEvent({
name: "appointment_confirm", // lowercase letters, digits, _ . -
displayName: "Appointment confirmed",
});
const { data } = await ecod.product.erix.events.listCustomEvents(); // data.triggers
await ecod.product.erix.events.deleteCustomEvent("TRIGGER_ID");
一些人在批准阶段停下来等待一个人。 你可以列出这些 并决定:
const { data } = await ecod.product.erix.ai.approvals(); // { drafts, runs }
for (const run of data.runs) {
// Pass run.nodeId (3.2.0) so a stale view can't decide a different step.
await ecod.product.erix.ai.decideRun(run.id, "approve", run.nodeId);
}
频道:收件箱和直接发送
所有频道共用一个收件箱. 任何线索都可以用
chat.messages.send({ conversationId })。 。 。 .
const { data: threads } = await ecod.product.erix.chat.conversations.list({
channel: "whatsapp",
limit: 20,
});
const { data: messages, hasMore } = await ecod.product.erix.chat.conversations.messages(
"CONVERSATION_ID",
{ limit: 50 }, // page back with { before: oldestMessage.createdAt }
);
await ecod.product.erix.chat.messages.send({
conversationId: "CONVERSATION_ID",
text: "Your order is packed and ships today.",
});
await ecod.product.erix.chat.conversations.markRead("CONVERSATION_ID");
// Open (or find) a thread for a number and link it to a lead.
const { data: thread } = await ecod.product.erix.chat.conversations.create({
phone: "+91XXXXXXXXXX",
channel: "whatsapp",
});
await ecod.product.erix.chat.conversations.linkLead(thread.id, "LEAD_ID");
发送一个文件。 上传到您的存储器中, 然后附加 :
const input = document.querySelector<HTMLInputElement>("#attachment")!;
const media = await ecod.product.erix.chat.messages.upload(input.files![0]);
await ecod.product.erix.chat.messages.send({
conversationId: "CONVERSATION_ID",
media,
text: "Invoice attached",
});
直接WhatsApp发送, 没有收件串编号 :
// An approved template. Use this to start a conversation.
await ecod.product.erix.chat.messages.sendTemplate({
to: "+91XXXXXXXXXX",
templateName: "order_update",
language: "en",
variables: ["ORD-1042", "₹1,499"],
});
// Free text. Only works inside WhatsApp's 24-hour customer service window.
await ecod.product.erix.chat.messages.send({ to: "+91XXXXXXXXXX", text: "Thanks, we got your enquiry." });
管理模板为 ecod.infra.connect.whatsapp.templates数字 : list(), (中文(简体) ),
sync() 财务报告和审定财务报表 retrieve(name)。 。 。 .
发票和付款链接
金额以卢比计。 这张发票是25,000美元+18%的GST=-29,500美元:
const { data: invoice } = await ecod.product.erix.invoices.create({
clientName: "Test Customer",
clientPhone: "+91XXXXXXXXXX",
leadId: "LEAD_ID",
items: [{ description: "Website design", quantity: 1, rate: 25000, hsnSac: "998314" }],
taxRate: 18,
dueDate: "2026-11-15",
});
// A link on your connected gateway (Razorpay or Stripe). Calling again returns the same link.
const { data: link } = await ecod.product.erix.invoices.paymentLink(invoice.id); // (3.2.0)
console.log(link.paymentLinkUrl);
await ecod.product.erix.invoices.sendWhatsApp(invoice.id); // (3.2.0) with the payment link
await ecod.product.erix.invoices.sendEmail(invoice.id); // (3.2.0) PDF + payment link
发票不能删除,只能作废(invoices.void(id)因此,GST记录
保持原样 其他电话: list({ status }), (中文(简体) ), getInvoice(id) 财务报告和审定财务报表
update(id, patch). 会计出口
product.erix.invoiceExport数字 : csv(), (中文(简体) ), tally() 财务报告和审定财务报表 zoho()。 。 。 .
一个一次性的付款链接 与发票无关 或者你拿的付款 离线 :
await ecod.infra.connect.connections.createPaymentLink({
amount: 1499, // ₹1,499
description: "Consultation fee",
customer: { phone: "+91XXXXXXXXXX" },
});
// Cash, UPI or bank transfer. With orderId, a retry is counted once.
await ecod.product.erix.payments.capture({ leadId: "LEAD_ID", amount: 5000, orderId: "UPI-2026-0042" });
存储上传
media.upload() 获得一个预先签名的 URL, 直接上传到存储, 然后
确认上传,全部在一个呼叫中。 文件字节从不经过
API,你的密钥从不去存储主机.
import { readFile } from "node:fs/promises";
const buffer = await readFile("./brochure.pdf");
const { data: file } = await ecod.infra.storage.media.upload(buffer, {
folder: "documents",
filename: "brochure.pdf",
contentType: "application/pdf",
});
const { data: download } = await ecod.infra.storage.files.getDownloadUrl<{
data: { downloadUrl: string };
}>(file.key);
console.log(download.downloadUrl); // short-lived URL
同一个电话需要浏览器 File其他电话: files.list(folder) 财务报告和审定财务报表
files.delete(key)。 。 。 .
实时活动
事件流过一个套接字 被放大到工作空间 你只收到一个 工作空间的事.
import type { Message } from "@ecodrix/erix-api";
const onMessage = ({ conversationId, message }: { conversationId: string; message: Message }) => {
console.log(conversationId, message.direction, message.text);
};
ecod
.on("inbox.message", onMessage)
.on("message_status_update", ({ messageId, status }) => console.log(messageId, status))
.on("resync", () => {
// Reconnected after a gap too large to replay. Refetch your data.
});
// Later, e.g. in a React effect cleanup:
ecod.off("inbox.message", onMessage);
| 活动 | 有效载荷/ 含义 |
|---|---|
inbox.message | { conversationId, message }。每个已存储消息:任何通道、进出 |
message_status_update | { messageId, conversationId, status }。发送、交付、读取或失败 |
ai_suggestion 编号 : ai_handoff | AI的草稿正在等待中 |
workflow-run-update | 流量运行进度 |
platform:notification | 新的申请通知 |
knowledge.source_updated | 知识库来源改变状态 |
resync | 错过的事件无法重播. 追溯 |
每个事件的最后论点是 { seq }。在重新连接后, SDK 将返回
最后一个 seq 它看到了,服务器重放 错过的东西。 重播游戏
重复一个事件, 所以写一个可以安全运行两次的处理器( 通过 ID 更新) .
Webhook 签名验证
Flow的 Webhook 步骤将 JSON 发送给您的 URL 。 如果你把一个秘密
Webhook 连接, 请求与它一起签名于
x-ecodrix-signature: sha256=<hex>。对照 原始内容
身体,在你分析它之前。 只有节点.
import { APIError } from "@ecodrix/erix-api";
import { ecod } from "./ecodrix"; // your server-side client from Quick start
export async function POST(req: Request) {
const raw = await req.text();
try {
const event = await ecod.webhooks.constructEvent(
raw,
req.headers.get("x-ecodrix-signature") ?? undefined,
process.env.ECOD_WEBHOOK_SECRET!,
);
console.log("verified", event);
return Response.json({ received: true });
} catch (err) {
// WebhookSignatureError: APIError with code "invalid_signature"
const message = err instanceof APIError ? err.message : "Bad request";
return new Response(message, { status: 400 });
}
}
原始请求
使用 ecod.request() 对于还没有输入方法的终点。 还是你
获取认证,重复和超时。 路径相对 /v1/api数字 :
await ecod.request("POST", "/infra/connect/send/email", {
to: "[email protected]",
subject: "Your booking is confirmed",
html: "<p>See you on Monday at 11:00.</p>",
});
错误
呼叫失败 。 API用一个信封回答像
{ success: false, error, message, code? }而SDK把它变成其中之一
这些课程:
| 类 | 何时 |
|---|---|
EcodrixError | 下面所有东西的基级 |
APIError | 任何其它失败。 有过 .status, (中文(简体) ), .code (发送时)和 .message |
AuthenticationError | HTTP 401或403:坏键或客户端代码,缺失范围,或不允许的角色 |
RateLimitError | HTTP 429,在SDK自己的回放耗尽之后 |
import { APIError, AuthenticationError, RateLimitError } from "@ecodrix/erix-api";
try {
await ecod.product.erix.leads.retrieve("LEAD_ID");
} catch (err) {
if (err instanceof AuthenticationError) {
console.error("Check the key, client code, scope or role:", err.message);
} else if (err instanceof RateLimitError) {
console.warn("Rate limited, back off and retry later");
} else if (err instanceof APIError) {
console.error(`API error ${err.status ?? "network"}: ${err.message}`, err.code);
} else {
throw err;
}
}
ecod.request() 抛出平原 APIError 每个状态.
复次及时出. SDK以指数重试了三次
备份。 它重复网络错误( 任何方法) , 5xx 对同位素的答复
方法(GET、PUT、DELETE)和429个响应。 每一次请求都会在
30 s; 修改 timeout 选项。 超时请求没有重审.
网络出错也可以击中 POST 的响应已丢失, 而重试
然后再次发送它。 将您自己的参考信息传递给一个方法
(单位:千美元)refs, (中文(简体) ), orderId所以,一个复制是安全的.
类型脚本
方法返回响应正体,所以通过一个类型参数使其得到打字.
SDK导出主要实体和参数类型(Lead, (中文(简体) ),
CreateLeadParams, (中文(简体) ), ListLeadsParams, (中文(简体) ), Pipeline, (中文(简体) ), PipelineStage, (中文(简体) ),
Conversation, (中文(简体) ), Message, (中文(简体) ), SendMessageParams, (中文(简体) ), PausedRun, (中文(简体) ), AiSuggestion, (中文(简体) ),
KnowledgeSource, (中文(简体) ), PaginatedResponse (见a/cn.9/wg.iii/wp.iii/wp.48,第2段).
import type { Lead, PaginatedResponse } from "@ecodrix/erix-api";
const page = await ecod.product.erix.leads.list<PaginatedResponse<Lead>>({ limit: 50 });
console.log(page.pagination.total, page.data[0]?.firstName);
// Page through every lead without handling pages yourself.
for await (const lead of ecod.product.erix.leads.listAutoPaging<Lead>({ status: "open" })) {
console.log(lead.phone);
}
版本
- SDK跟着 铸造 在自己的线路(3.x)上. 断开 仅改变主要释放区的土地。 见 变动情况。 。 。 .
- API服务器有自己的版本号:平台v1.0是服务器1.0.
SDK称
/v1路线,以及version选项选择路径 版本. - 标记的方法 (3.2.0) (中文(简体) ) 需要SDK 3.2.0或以后.
指南和每个终点: https://api.ecodrix.com/docs (中文(简体) )。 。 。 .