npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@ecodrix/erix-api

v1.3.4

Published

Official Isomorphic SDK for the ECODrIx platform. Native support for WhatsApp, CRM, Storage, and Meetings across TS, JS, Python, and Java.

Downloads

3,910

Readme

@ecodrix/erix-api

NPM Version License: MIT TypeScript OpenAPI Node.js

The official, isomorphic SDK for the ECODrIx platform.

Manage WhatsApp conversations, CRM leads, pipelines, automations, marketing campaigns, file storage, and Google Meet — all from a single, type-safe library.


Table of Contents


Installation

# pnpm (recommended)
pnpm add @ecodrix/erix-api

# npm
npm install @ecodrix/erix-api

Requires: Node.js >= 18


Quick Start

import { Ecodrix } from "@ecodrix/erix-api";

const ecod = new Ecodrix({
  apiKey: process.env.ECOD_API_KEY!,
  clientCode: process.env.ECOD_CLIENT_CODE,
});

// Send a WhatsApp message
await ecod.whatsapp.messages.send({
  to: "+919876543210",
  text: "Hello from ECODrIx!",
});

// Create a CRM lead
const lead = await ecod.crm.leads.create({
  firstName: "Priya",
  phone: "+919876543210",
  source: "website",
});

// Fire an automation trigger
await ecod.events.trigger({
  trigger: "trial_started",
  phone: "+919876543210",
  createLeadIfMissing: true,
});

Configuration

Pass an options object to new Ecodrix(options):

| Option | Type | Required | Default | Description | | ------------ | -------- | ----------- | ------------------------- | ---------------------------------------------- | | apiKey | string | ✅ Yes | — | Your ECOD Platform API key | | clientCode | string | Recommended | — | Your tenant ID — scopes all requests | | baseUrl | string | No | https://api.ecodrix.com | Override the API base URL (e.g. for local dev) | | socketUrl | string | No | Same as baseUrl | Override the Socket.io server URL |

const ecod = new Ecodrix({
  apiKey: process.env.ECOD_API_KEY!,
  clientCode: process.env.ECOD_CLIENT_CODE,
  baseUrl: "http://localhost:4000", // For local development
});

Resources

WhatsApp

Access via ecod.whatsapp.


ecod.whatsapp.messages

Send a text message

await ecod.whatsapp.messages.send({
  to: "+919876543210",
  text: "Your appointment is confirmed!",
});

Send a media message (image, video, document, audio)

await ecod.whatsapp.messages.send({
  to: "+919876543210",
  mediaUrl: "https://cdn.ecodrix.com/invoice.pdf",
  mediaType: "document",
  filename: "invoice.pdf",
});

Send a template message via queue

await ecod.whatsapp.messages.sendTemplate({
  to: "+919876543210",
  templateName: "appointment_reminder",
  language: "en_US",
  variables: ["Priya", "Tomorrow 10AM"],
});

Mark messages as read

await ecod.whatsapp.messages.markRead("message_id");

ecod.whatsapp.conversations

// List conversations (cursor-based pagination)
const { data } = await ecod.whatsapp.conversations.list({
  limit: 20,
  status: "open",
  after: "cursor_token",
});

// Get a specific conversation
const conv = await ecod.whatsapp.conversations.retrieve("conversation_id");

// Get messages in a conversation
const { data: msgs } = await ecod.whatsapp.conversations.messages("conversation_id", {
  limit: 50,
});

// Create a conversation explicitly
await ecod.whatsapp.conversations.create({ phone: "+919876543210", name: "Priya" });

// Link a conversation to a CRM lead
await ecod.whatsapp.conversations.linkLead("conversation_id", "lead_id");

// Mark a conversation as read (clears unread badge)
await ecod.whatsapp.conversations.markRead("conversation_id");

// Delete / archive a conversation
await ecod.whatsapp.conversations.delete("conversation_id");

// Bulk delete conversations
await ecod.whatsapp.conversations.bulkDelete(["conv_1", "conv_2"]);

ecod.whatsapp.templates

// List pre-approved Meta templates
const { data } = await ecod.whatsapp.templates.list();

// Sync templates from the Meta Business Account
await ecod.whatsapp.templates.sync();

ecod.whatsapp.broadcasts

Send a personalised WhatsApp template message to multiple recipients at once.

await ecod.whatsapp.broadcasts.create({
  name: "April Promo",
  templateName: "discount_offer",
  templateLanguage: "en_US",
  recipients: [
    { phone: "+919876543210", variables: ["Priya", "20%"] },
    { phone: "+919123456789", variables: ["Ravi", "30%"] },
  ],
});

// List past broadcasts
const { data } = await ecod.whatsapp.broadcasts.list({ status: "completed" });

ecod.whatsapp.sendTemplate — Direct Template (Bypass Queue)

Use this for high-priority utility templates that must deliver immediately outside the automation queue.

const result = await ecod.whatsapp.sendTemplate({
  phone: "+919876543210",
  templateName: "payment_confirmation",
  variables: {
    name: "Priya",
    amount: "₹1,500",
  },
});
// result.messageId → WA message ID

CRM

Access via ecod.crm.


ecod.crm.leads

Create

const { data: lead } = await ecod.crm.leads.create({
  firstName: "Priya",
  lastName: "Sharma",
  phone: "+919876543210",
  email: "[email protected]",
  source: "website",
  pipelineId: "pipeline_id",
  stageId: "stage_id",
  metadata: { utmSource: "google" },
});

Upsert by phone — Creates if absent, updates if exists

await ecod.crm.leads.upsert({
  leadData: { phone: "+919876543210", firstName: "Priya" },
  trigger: "webinar_joined",
});

List with filters

const { data } = await ecod.crm.leads.list({
  status: "new",
  source: "whatsapp",
  pipelineId: "pipeline_id",
  page: 1,
  limit: 25,
});

Retrieve

const { data: lead } = await ecod.crm.leads.retrieve("lead_id");
const { data } = await ecod.crm.leads.retrieveByPhone("+919876543210");
const { data } = await ecod.crm.leads.retrieveByRef("orderId", "ORD-123");

Update

await ecod.crm.leads.update("lead_id", { email: "[email protected]" });
await ecod.crm.leads.updateMetadata("lead_id", {
  refs: { orderId: "ORD-456" },
  extra: { plan: "pro" },
});

Move & Convert

await ecod.crm.leads.move("lead_id", "target_stage_id");
await ecod.crm.leads.convert("lead_id", "won", "Signed contract");

Tags

await ecod.crm.leads.tags("lead_id", { add: ["vip", "hot"], remove: ["cold"] });

Score

await ecod.crm.leads.recalculateScore("lead_id");

Bulk

await ecod.crm.leads.import(leadArray); // Bulk import
await ecod.crm.leads.delete("lead_id");
await ecod.crm.leads.bulkDelete(["id_1", "id_2"]);

ecod.crm.pipelines

// CRUD
const { data } = await ecod.crm.pipelines.list();
await ecod.crm.pipelines.create({ name: "Sales", isDefault: true });
await ecod.crm.pipelines.retrieve("pipeline_id");
await ecod.crm.pipelines.update("pipeline_id", { name: "Deals" });
await ecod.crm.pipelines.delete("pipeline_id");

// Advanced
await ecod.crm.pipelines.setDefault("pipeline_id");
await ecod.crm.pipelines.duplicate("pipeline_id", "Sales Copy");
await ecod.crm.pipelines.archive("pipeline_id");
const { data: board } = await ecod.crm.pipelines.board("pipeline_id");
const { data: forecast } = await ecod.crm.pipelines.forecast("pipeline_id");

// Stage management
await ecod.crm.pipelines.addStage("pipeline_id", { name: "Negotiation", color: "#f59e0b" });
await ecod.crm.pipelines.reorderStages("pipeline_id", ["stage_1", "stage_3", "stage_2"]);
await ecod.crm.pipelines.updateStage("stage_id", { probability: 80 });
await ecod.crm.pipelines.deleteStage("stage_id", "fallback_stage_id");

ecod.crm.automations

// CRUD
const { data } = await ecod.crm.automations.list();
await ecod.crm.automations.create({ name: "Follow-up", trigger: "lead_created", nodes: [], edges: [] });
await ecod.crm.automations.update("rule_id", { isActive: false });
await ecod.crm.automations.toggle("rule_id");
await ecod.crm.automations.deleteRule("rule_id");
await ecod.crm.automations.bulkDelete(["rule_1", "rule_2"]);

// Testing & events
await ecod.crm.automations.test("rule_id", "lead_id");
const { data: events } = await ecod.crm.automations.getAvailableEvents();

// Enrollment management
const { data } = await ecod.crm.automations.enrollments("rule_id", { status: "active", page: 1 });
await ecod.crm.automations.getEnrollment("enrollment_id");
await ecod.crm.automations.pauseEnrollment("rule_id", "enrollment_id");
await ecod.crm.automations.resumeEnrollment("rule_id", "enrollment_id");

// Run inspection
const { data: runs } = await ecod.crm.automations.runs("rule_id");
await ecod.crm.automations.getRun("run_id");
await ecod.crm.automations.resumeRun("run_id");
await ecod.crm.automations.abortRun("run_id");

// Unlock a wait_event node from external tool (e.g. Zapier, payment gateway callback)
await ecod.crm.automations.webhookEvent("rule_id", "payment_received", {
  amount: 1500,
  currency: "INR",
});

ecod.crm.sequences

Manually enroll / unenroll leads in drip automation sequences.

await ecod.crm.sequences.enroll({
  leadId: "lead_id",
  ruleId: "rule_id",
  variables: { discount: "20%" },
});
await ecod.crm.sequences.unenroll("enrollment_id");
const { data } = await ecod.crm.sequences.listForLead("lead_id");

ecod.crm.activities

// Lead timeline (all CRM events in order)
const { data } = await ecod.crm.activities.timeline("lead_id", { page: 1, limit: 50 });

// Filtered activity list
const { data } = await ecod.crm.activities.list("lead_id", { type: "call" });

// Log a call outcome
await ecod.crm.activities.logCall("lead_id", {
  outcome: "answered",
  duration: 120,
  notes: "Interested in Pro plan",
});

// Log a generic activity
await ecod.crm.activities.log({
  leadId: "lead_id",
  type: "email_opened",
  title: "Campaign Email Opened",
});

// --- Notes ---
const { data: notes } = await ecod.crm.activities.notes.list("lead_id");
await ecod.crm.activities.notes.create("lead_id", { content: "Call scheduled for Monday" });
await ecod.crm.activities.notes.update("note_id", "Updated note text");
await ecod.crm.activities.notes.pin("note_id"); // pin to top
await ecod.crm.activities.notes.pin("note_id", false); // unpin

ecod.crm.analytics

const { data: overview } = await ecod.crm.analytics.overview("pipeline_id");
const { data: conversion } = await ecod.crm.analytics.conversionRate("pipeline_id");
const { data: velocity } = await ecod.crm.analytics.dealVelocity("pipeline_id");
const { data: stageTime } = await ecod.crm.analytics.stageTime("pipeline_id");

ecod.crm.scoring

const { data } = await ecod.crm.scoring.getConfig();
await ecod.crm.scoring.updateConfig({ rules: [...] });

ecod.crm.payments

const { data } = await ecod.crm.payments.list("lead_id");

ecod.crm.automationDashboard

const { data: stats } = await ecod.crm.automationDashboard.stats();
const { data: logs } = await ecod.crm.automationDashboard.logs({ limit: 10, status: "failed" });
await ecod.crm.automationDashboard.retryFailedEvent("log_id");

Events & Workflows

Access via ecod.events. Connect your external applications to the CRM automation engine.

List all available triggers

const { data: triggers } = await ecod.events.list();

Register a custom event

await ecod.events.assign({
  name: "cart_abandoned",
  displayName: "Shopping Cart Abandoned",
  pipelineId: "pipeline_123",
});

// Deactivate
await ecod.events.unassign("cart_abandoned");
await ecod.events.unassignBulk(["event_a", "event_b"]);

Fire an event / trigger workflows

await ecod.events.trigger({
  trigger: "cart_abandoned",
  phone: "+919876543210",
  variables: { items: "T-Shirt, Mug", total: "₹850" },
  createLeadIfMissing: true,
  delayMinutes: 30,
  // Optional: auto-book a Google Meet during the workflow
  requiresMeet: true,
  meetConfig: {
    summary: "Follow-up call",
    duration: 30,
    timezone: "Asia/Kolkata",
  },
});

Custom event definitions (CRM-managed)

const { data } = await ecod.events.listCustomEvents();
await ecod.events.createCustomEvent({ name: "webinar_attended", displayName: "Webinar Attended" });
await ecod.events.deleteCustomEvent("event_id");
await ecod.events.emit({ eventName: "webinar_attended", leadId: "lead_id" });

Marketing

Access via ecod.marketing.

Email campaigns

await ecod.marketing.emails.sendCampaign({
  recipients: ["[email protected]"],
  subject: "Summer Sale!",
  html: "<h1>Get 20% off!</h1>",
});
await ecod.marketing.emails.sendTest("[email protected]");

Campaigns (Email + SMS)

await ecod.marketing.campaigns.create({ name: "Q2 Push", type: "email" });
await ecod.marketing.campaigns.list({ status: "active" });
await ecod.marketing.campaigns.retrieve("campaign_id");
await ecod.marketing.campaigns.update("campaign_id", { subject: "New subject" });
await ecod.marketing.campaigns.send("campaign_id", { scheduledAt: "2026-05-01T09:00:00Z" });
await ecod.marketing.campaigns.stats("campaign_id");
await ecod.marketing.campaigns.delete("campaign_id");

WhatsApp marketing (CRM-integrated template dispatch)

Unlike the direct ecod.whatsapp.sendTemplate, this endpoint resolves CRM field variables automatically.

await ecod.marketing.whatsapp.sendTemplate({
  phone: "+919876543210",
  templateName: "seasonal_offer",
  variables: { discount: "40%" },
});

Meetings

Access via ecod.meet. Backed by Google Meet.

const { data: meeting } = await ecod.meet.create({
  leadId: "lead_id",
  participantName: "Priya Sharma",
  participantPhone: "+919876543210",
  startTime: "2026-04-10T10:00:00.000Z",
  endTime: "2026-04-10T10:30:00.000Z",
});
console.log(meeting.meetLink); // → https://meet.google.com/abc-defg-hij

const { data: meetings } = await ecod.meet.list({ status: "scheduled" });
const { data } = await ecod.meet.retrieve("meeting_id");
await ecod.meet.update("meeting_id", { startTime: "2026-04-11T11:00:00.000Z" });
await ecod.meet.delete("meeting_id");

Storage

Access via ecod.storage. Powered by Cloudflare R2.

This handles the full presigned-URL orchestration transparently:

  1. Requests a presigned PUT URL from the backend.
  2. Uploads directly to R2 (no proxy overhead).
  3. Confirms the upload.
// Node.js: from a Buffer
import { readFileSync } from "fs";

const fileBuffer = readFileSync("./contract.pdf");
const { data } = await ecod.storage.upload(fileBuffer, {
  folder: "customer_documents",
  filename: "contract.pdf",
  contentType: "application/pdf",
});
console.log(data.url); // → https://cdn.ecodrix.com/customer_documents/contract.pdf

// Browser: from an <input> element
const file = document.getElementById("file-input").files[0];
const { data } = await ecod.storage.upload(file, {
  folder: "avatars",
  filename: file.name,
  contentType: file.type,
});

Media

Access via ecod.media. Manage files stored in R2.

// Get a presigned download URL for a private file
const { data } = await ecod.media.getDownloadUrl("confidential/contract.pdf");

// Monitor storage quota
const { data: usage } = await ecod.media.getUsage();
console.log(`${usage.usedMB} MB of ${usage.limitMB} MB used`);

// List files in a folder
await ecod.media.list({ folder: "invoices" });

// Delete a file
await ecod.media.delete("invoices/old_invoice.pdf");

Email

Access via ecod.email. Backed by SMTP or AWS SES depending on tenant configuration.

await ecod.email.sendEmailCampaign({
  subject: "Summer Discount!",
  recipients: ["[email protected]"],
  html: "<h1>Get 20% off all plans!</h1>",
});

// Verify SMTP configuration
await ecod.email.sendTestEmail("[email protected]");

Notifications & Logs

Access via ecod.notifications.

// Automation execution logs
const { data: logs } = await ecod.notifications.listLogs({
  trigger: "lead_created",
  status: "failed",
  startDate: "2026-04-01",
  endDate: "2026-04-30",
});

await ecod.notifications.retrieveLog("log_id");

// Aggregate stats
const { data: stats } = await ecod.notifications.getStats({
  startDate: "2026-04-01",
  endDate: "2026-04-30",
});

// Provider webhook callback logs
const { data } = await ecod.notifications.listCallbacks({ limit: 20 });

Queue Management

Access via ecod.queue. Inspect and manage background automation jobs.

// View failed jobs
const { data: failed } = await ecod.queue.listFailed();

// Get queue health by status
const stats = await ecod.queue.getStats();
// stats → { waiting: 12, active: 3, completed: 482, failed: 7, delayed: 0 }

// Retry a failed job
await ecod.queue.retryJob("job_id");

// Remove a job permanently
await ecod.queue.deleteJob("job_id");

Health & Diagnostics

Access via ecod.health.

// Global platform status
const health = await ecod.health.system();
// { status: "ok", version: "...", env: "production", uptime: 3600, db: "connected", queueDepth: 5 }

// Tenant-scoped service readiness
const clientHealth = await ecod.health.clientHealth();
// { clientCode: "...", services: { whatsapp: "connected", email: "configured", googleMeet: "configured" }, ... }

// Background job status lookup
const job = await ecod.health.jobStatus("job_id");

Webhooks

Access via ecod.webhooks.

⚠️ Node.js only — Requires node:crypto. Not available in browser bundles.


Enterprise Capabilities

Auto-Paginating Iterators

Stream all records without manual page management:

// With optional type parameter for full type safety
for await (const lead of ecod.crm.leads.listAutoPaging<Lead>({ status: "won" })) {
  await syncToMyDatabase(lead);
}

Bulk Data Chunking

Insert thousands of leads without hitting rate limits. createMany automatically chunks and concurrently streams batches:

const results = await ecod.crm.leads.createMany(massiveArrayOfLeads, 100);
console.log(`Ingested ${results.length} leads.`);

Idempotency Keys

Prevent duplicate requests caused by automatic retries:

await ecod.email.sendEmailCampaign(
  { subject: "Promo", recipients: ["[email protected]"], html: "..." },
  { idempotencyKey: "promo_campaign_uuid_2026_q2" },
);

Webhook Signature Verification

Cryptographically verify that webhook payloads originated from ECODrIx:

app.post(
  "/api/webhooks",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    try {
      const event = await ecod.webhooks.constructEvent(
        req.body.toString(),
        req.headers["x-ecodrix-signature"],
        "whsec_your_webhook_secret",
      );
      console.log("Verified event:", event);
      res.json({ received: true });
    } catch (err) {
      return res.status(400).send(`Invalid signature: ${err.message}`);
    }
  },
);

Raw Execution Engine

Access any API endpoint — including experimental or custom ones — with full authentication and retry benefits:

const { data } = await ecod.request("POST", "/api/saas/beta-feature", {
  experimentalFlag: true,
});

Real-time Events

The SDK maintains a persistent Socket.io connection, scoped to your clientCode tenant. Use ecod.on() to subscribe:

ecod
  .on("whatsapp.message_received", (msg) => {
    console.log(`New message from ${msg.from}: ${msg.body}`);
  })
  .on("crm.lead_created", ({ leadId }) => {
    console.log(`New lead: ${leadId}`);
  })
  .on("crm.lead_updated", ({ leadId, stageName }) => {
    console.log(`${leadId} moved to ${stageName}`);
  })
  .on("meet.scheduled", ({ meetLink }) => {
    console.log(`Meeting booked: ${meetLink}`);
  })
  .on("automation.failed", ({ trigger, reason }) => {
    alertTeam(`Automation ${trigger} failed: ${reason}`);
  });

// Graceful shutdown
process.on("SIGTERM", () => ecod.disconnect());

Standard Event Reference

| Event | Payload | Description | | --------------------------- | -------------------------------- | ------------------------------ | | whatsapp.message_received | { from, body, conversationId } | Inbound WhatsApp message | | whatsapp.message_sent | { to, messageId } | Outbound message delivered | | crm.lead_created | { leadId, phone } | New CRM lead created | | crm.lead_updated | { leadId, stageName } | Lead stage or fields updated | | meet.scheduled | { meetingId, meetLink } | Google Meet appointment booked | | storage.upload_confirmed | { key, url, sizeBytes } | File upload confirmed | | automation.failed | { trigger, reason, leadId } | Automation execution failed |


Error Handling

All methods throw typed errors you can catch and inspect:

import { APIError, AuthenticationError, RateLimitError, Ecodrix } from "@ecodrix/erix-api";

try {
  const { data } = await ecod.crm.leads.retrieve("non_existent_id");
} catch (err) {
  if (err instanceof AuthenticationError) {
    // 401 — invalid API key or client code
    console.error("Check your credentials.");
  } else if (err instanceof RateLimitError) {
    // 429 — too many requests (SDK auto-retries up to 3×)
    console.warn("Rate limit hit.");
  } else if (err instanceof APIError) {
    console.error(`API Error [${err.status}]: ${err.message}`);
  } else {
    throw err;
  }
}

Error Classes

| Class | HTTP Status | Description | | --------------------- | ----------- | ------------------------------------------ | | EcodrixError | — | Base error class | | APIError | varies | Generic API error with .status and .code | | AuthenticationError | 401 | Invalid API key or client code | | RateLimitError | 429 | Too many requests |


Browser / CDN Usage

For usage without a bundler:

<!-- Via CDN (jsDelivr) -->
<script src="https://cdn.jsdelivr.net/npm/@ecodrix/erix-api/dist/ts/browser/index.global.js"></script>
<script>
  const ecod = new Ecodrix.Ecodrix({
    apiKey: "your_api_key",
    clientCode: "YOUR_CLIENT_CODE",
  });

  ecod.whatsapp.messages.send({
    to: "+919876543210",
    text: "Hello from the browser!",
  });
</script>

⚠️ Note: ecod.webhooks.constructEvent and ecod.storage.upload (from Buffer) are Node.js only and will not function in browser environments.


Contributing

  1. Fork the repository.
  2. Create a feature branch: git checkout -b feat/my-feature
  3. Make changes, then run pnpm check to validate formatting and lint.
  4. Run pnpm build to verify the output compiles cleanly.
  5. Submit a Pull Request.

License

MIT © 2026 ECODrIx Team