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

@esimfly/sdk

v0.1.0

Published

Official Node.js / TypeScript SDK for the eSIMfly Business API — sell eSIMs, manage usage and receive webhooks

Readme

@esimfly/sdk — Node.js / TypeScript SDK for the eSIMfly Business API

Sell eSIMs from your own app: browse the catalogue, order, top up, track usage and receive signed webhooks — with request signing, retries and error handling done for you.

  • Zero runtime dependencies, Node.js 18+ (uses the built-in fetch)
  • TypeScript types for every request and response, ESM + CommonJS
  • HMAC-SHA256 signing with a fresh request id per call
  • Idempotency keys on orders, paced catalogue sync, pending-order polling
  • verifyWebhookSignature / constructWebhookEvent for deliveries

Full API reference: https://docs.esimfly.net · Get credentials: Business Dashboard → Settings → API Keys.

Keep the SDK on your server. The secret key must never be shipped to a browser or mobile app.

Install

npm install @esimfly/sdk

Quick start

import { ESIMfly } from '@esimfly/sdk';

const esimfly = new ESIMfly({
  accessCode: process.env.ESIMFLY_ACCESS_CODE!, // esf_...
  secretKey: process.env.ESIMFLY_SECRET_KEY!,   // sk_...
});

const { balance, currency } = await esimfly.balance.get();
console.log(`Balance: ${balance} ${currency}`);

Sell an eSIM in three steps

1. Sync the catalogue into your database (scheduled, every 6–12 h)

Do not call the catalogue per customer request — copy it and serve your storefront from your own tables. listAll pages with limit=100 and pauses 1 s between pages to stay inside the rate limit.

const runStartedAt = new Date();

await esimfly.packages.sync(async (packages) => {
  await db.packages.upsertMany(
    packages.map((p) => ({
      packageCode: p.package_code,     // opaque — store verbatim
      name: p.name,
      region: p.region,
      type: p.type,                    // local | regional | global
      dataGb: p.data_amount_gb,
      validityDays: p.validity_days,
      cost: p.cost,                    // your buy price
      currency: p.currency,
      sellPrice: p.cost * 1.3,         // your margin, your rules
      countries: p.countries ?? [],
      lastSeenAt: runStartedAt,
      isActive: true,
    })),
  );
});

// Only after a fully successful run: hide packages that disappeared (never delete them —
// your orders reference them).
await db.packages.updateMany({ where: { lastSeenAt: { lt: runStartedAt } }, data: { isActive: false } });

2. Create the order (inside your checkout)

import { ESIMflyError } from '@esimfly/sdk';

try {
  const order = await esimfly.orders.create({
    packageCode: cart.packageCode,
    quantity: 1,
    idempotencyKey: cart.id,           // your own id — a retry can never charge twice
  });

  await db.orders.update(cart.id, {
    orderReference: order.orderReference,
    amount: order.amount,
    currency: order.currency,
    status: order.status,              // 'completed' | 'pending_details'
  });

  for (const esim of order.esims) {
    await db.esims.create({
      iccid: esim.iccid,
      lpaString: esim.lpaString,        // render your own QR code from this
      appleInstallUrl: esim.directAppleInstallUrl,
      androidInstallUrl: esim.directAndroidInstallUrl,
      expiresAt: esim.expired_time,
      totalBytes: esim.total_volume,
    });
  }

  if (order.status === 'pending_details') {
    // Rare (asynchronously provisioned packages). Poll up to 10 minutes, then hand to support.
    const ready = await esimfly.orders.waitForEsim(order.orderReference);
    console.log('eSIM ready:', ready.esim.iccid);
  }
} catch (err) {
  if (err instanceof ESIMflyError && err.code === 'INSUFFICIENT_BALANCE') {
    const { needToLoad } = err.response as { needToLoad: number };
    alertOps(`Top up the eSIMfly balance: ${needToLoad}`);
  } else {
    throw err;
  }
}

3. Deliver and support

// "My eSIM" screen — cheap, cache 5–15 min
const usage = await esimfly.esims.usage({ iccid });
console.log(`${usage.data.remaining_mb} MB left, expires ${usage.validity.expires_at}`);

// Top-up screen
const { packages } = await esimfly.topups.packages({ iccid, limit: 100 });
await esimfly.topups.create({ iccid, packageCode: packages[0].package_code });

// Support console — live from the network, throttle per eSIM
const live = await esimfly.esims.status({ iccid });
console.log(live.last_network.operator, live.device.model, live.data_usage.used_gb);
const events = await esimfly.esims.networkEvents({ iccid }); // events[].is_allowed === false → wrong network

// Actions
await esimfly.esims.suspend({ iccid });   // eSIMfly-network eSIMs only
await esimfly.esims.activate({ iccid });
await esimfly.esims.sendSms({ iccid }, 'Your eSIM is ready. Enable Data Roaming to connect.');
await esimfly.esims.cancel({ iccid });    // only before installation — refunds to your balance

Webhooks

Subscribe once, then react to events instead of polling.

const { webhook } = await esimfly.webhooks.set({
  webhookUrl: 'https://your-server.com/api/esimfly-webhook',
  events: ['esim.installed', 'esim.status.changed', 'esim.usage.threshold'],
});
await secrets.save('ESIMFLY_WEBHOOK_SECRET', webhook.secret); // shown once

Receiver (Express) — verify on the raw body, dedupe on X-Webhook-Id, answer within 10 s:

import express from 'express';
import { constructWebhookEvent, ESIMflyError } from '@esimfly/sdk';

app.post('/api/esimfly-webhook', express.raw({ type: 'application/json' }), async (req, res) => {
  let event;
  try {
    event = constructWebhookEvent(req.body, req.header('X-Webhook-Signature'), process.env.ESIMFLY_WEBHOOK_SECRET!);
  } catch (err) {
    return res.status(err instanceof ESIMflyError && err.code === 'INVALID_SIGNATURE' ? 401 : 400).end();
  }

  const deliveryId = req.header('X-Webhook-Id')!;
  if (await db.webhookDeliveries.exists(deliveryId)) return res.json({ received: true });
  await db.webhookDeliveries.insert({ id: deliveryId, event: event.event, payload: event.data });
  res.json({ received: true });

  // process after responding
  switch (event.event) {
    case 'esim.installed':
      await db.esims.update({ iccid: event.data.iccid }, { installedAt: event.data.installed_at });
      break;
    case 'esim.status.changed':
      await db.esims.update({ iccid: event.data.iccid }, { status: event.data.new_status, expiresAt: event.data.expiry_date });
      break;
    case 'esim.usage.threshold':
      if ((event.data.threshold_remaining_mb ?? Infinity) <= 200) await notifyLowData(event.data.iccid);
      break;
  }
});

| Event | When | Latency | |---|---|---| | esim.installed | profile enabled on a device for the first time | seconds | | esim.profile.updated | every SM-DP+ state change (chatty — usually skip) | seconds | | esim.usage.threshold | 500 / 200 / 100 / 50 MB remaining | seconds | | esim.status.changed | NEW → ACTIVE → DEPLETED / EXPIRED / CANCELLED | ≤ 30 min | | esim.provisioned | asynchronously provisioned order ready | seconds |

Errors

Every failure is an ESIMflyError. Branch on code, never on the message.

import { ESIMflyError } from '@esimfly/sdk';

try {
  await esimfly.topups.create({ iccid, packageCode });
} catch (err) {
  if (err instanceof ESIMflyError) {
    err.code;        // 'ESIM_NOT_TOPPABLE' | 'INSUFFICIENT_BALANCE' | 'RATE_LIMIT_EXCEEDED' | ...
    err.status;      // HTTP status
    err.response;    // parsed API body (extra fields such as needToLoad, details.ineligibleEsims)
    err.requestId;   // the RT-RequestID that was sent — quote it to support
    err.isRetryable; // network / timeout / 5xx
  }
}

Retries: GETs and orders that carry an idempotencyKey are retried up to maxRetries (default 2) on network errors, timeouts and 5xx, always with a fresh request id. Top-ups and orders without a key are never retried automatically — on a timeout, check esims.usage({ iccid }) before retrying.

Rate limits are per API key (typically 100/min, 1,000/h, 10,000/day). The last response's headers are on esimfly.rateLimit (limit, remaining, reset); a rejection surfaces as RATE_LIMIT_EXCEEDED.

Configuration

new ESIMfly({
  accessCode: 'esf_...',
  secretKey: 'sk_...',
  baseUrl: 'https://esimfly.net/api/v1/business', // default
  timeoutMs: 30_000,                                // default
  maxRetries: 2,                                    // default; 0 disables
  fetch: customFetch,                               // proxies, tests
  userAgent: 'my-shop/2.1',                         // appended to the SDK user agent
});

API surface

| Resource | Methods | |---|---| | balance | get() | | packages | list(params), listAll(options) (async iterator), sync(handler, options) | | orders | create(params), get(orderReference), waitForEsim(orderReference, options), list(params) | | esims | list(params), find(iccid), usage({ iccid } \| { orderId }), status(id), networkEvents(id), usageReport(id, days), suspend(id), activate(id), cancel(id), sendSms(id, message) | | topups | packages({ iccid }), create({ iccid, packageCode }) | | webhooks | get(), set({ webhookUrl, events }) | | helpers | verifyWebhookSignature(rawBody, header, secret), constructWebhookEvent(rawBody, header, secret), signRequest(...) |

id is { iccid } or { esimId }.

Support

[email protected] · https://docs.esimfly.net