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

@llcrm/crm-sdk

v8.0.0

Published

Official TypeScript SDK for the LigueLead CRM API. Generated from OpenAPI 3.1 with hand-authored auth, cursor pagination, and typed errors.

Readme

@liguelead/crm-sdk

Official TypeScript SDK for the LigueLead CRM API. Generated from OpenAPI 3.1 with hand-authored auth, cursor pagination, and typed errors.

  • Fully typed: Operations + request/response shapes inferred from the OpenAPI spec at build time. Zero any.
  • Fetch-based: Modern fetch runtime; no axios, no node-only dependencies. Works in Node >= 20, Bun, Deno, browsers.
  • Cursor auto-pagination: for await (const item of autoPaginate(client.raw.listContacts, params)).
  • Typed errors: instanceof CrmAuthError | CrmNotFoundError | CrmRateLimitError | CrmValidationError.
  • Dual auth: API keys (ll_live_*) for server-to-server + JWT (with optional refresh callback) for user-bound flows.
  • Auto-retry on 429 (opt-in): single retry after retry-after seconds.

Install

npm install @liguelead/crm-sdk
# or: pnpm add @liguelead/crm-sdk
# or: yarn add @liguelead/crm-sdk

Quickstart

import { CrmClient } from '@liguelead/crm-sdk';

const client = new CrmClient({ baseUrl: 'https://api.liguelead.com.br', auth: { type: 'apiKey', value: process.env.LIGUELEAD_API_KEY! } });
const contacts = await client.raw.listContacts({ query: { limit: 50 } });

Three lines: install, create client, first call.

Authentication

The SDK supports two auth modes via a discriminated union:

API key (server-to-server)

Generate an API key in the /settings/api-keys portal (Phase 65). The key has a ll_live_ prefix and granular scopes (e.g., contacts:read, tickets:write).

import { CrmClient } from '@liguelead/crm-sdk';

const client = new CrmClient({
  baseUrl: 'https://api.liguelead.com.br',
  auth: { type: 'apiKey', value: 'll_live_abc123...' },
});

Every action via API key is audited with api_key_id (CLAUDE.md regra 12).

JWT (user-bound, refresh-friendly)

For apps where the user is logged in via Supabase Auth, pass either a static JWT or a refresh callback:

// Static JWT
const client = new CrmClient({
  baseUrl: 'https://api.liguelead.com.br',
  auth: { type: 'jwt', value: jwtFromSupabase },
});

// Refresh-friendly (re-fetches the token before each request)
const client = new CrmClient({
  baseUrl: 'https://api.liguelead.com.br',
  auth: {
    type: 'jwt',
    getToken: async () => {
      const { data: { session } } = await supabase.auth.getSession();
      return session?.access_token ?? '';
    },
  },
});

The getToken callback is invoked before every request, so token refresh from NextAuth/Auth0/Supabase Auth integrations works transparently.

Per-request auth override (I-5)

The constructor-level auth applies to every request through the request interceptor. For ad-hoc cases where you need to override the Authorization header on a single call (e.g., impersonation, on-behalf-of flows, multi-tenant ops), pass headers as the second argument to any generated SDK function via client.raw:

const client = new CrmClient({
  baseUrl: 'https://api.liguelead.com.br',
  auth: { type: 'apiKey', value: 'll_live_default_...' },
});

// Override Authorization for THIS call only — interceptor sees the header is already
// set and does not overwrite it.
const result = await client.raw.listContacts(
  { query: { limit: 50 } },
  { headers: { Authorization: 'Bearer ll_live_other_user_key' } },
);

Subsequent calls without headers revert to the constructor-level auth — there is no persistent state change.

Auto-retry on 429

Enable single-retry behavior for rate-limited requests via the autoRetry option:

const client = new CrmClient({
  baseUrl: 'https://api.liguelead.com.br',
  auth: { type: 'apiKey', value: 'll_live_...' },
  autoRetry: true,
});

When the server returns 429, the SDK sleeps for retry-after seconds (parsed from the response header) and retries the request once. If the retry also returns 429, CrmRateLimitError is thrown — the consumer can then decide whether to wait further or give up.

Error handling

All non-2xx responses throw a typed CrmError subclass. Use instanceof for branching:

import {
  CrmAuthError,
  CrmNotFoundError,
  CrmRateLimitError,
  CrmValidationError,
  CrmError,
} from '@liguelead/crm-sdk';

try {
  await client.raw.contactsControllerFindOne({ path: { id: 'unknown' } });
} catch (err) {
  if (err instanceof CrmAuthError) {
    // 401/403 — redirect to login or rotate API key
  } else if (err instanceof CrmNotFoundError) {
    console.log(`${err.entity} ${err.id} does not exist`);
  } else if (err instanceof CrmRateLimitError) {
    await new Promise((r) => setTimeout(r, err.retryAfter * 1000));
  } else if (err instanceof CrmValidationError) {
    for (const fieldErr of err.fieldErrors) {
      console.error(`${fieldErr.field}: ${fieldErr.message}`);
    }
  }
  if (err instanceof CrmError) {
    console.error('request id:', err.requestId);
  }
}

Every CrmError carries requestId (ULID from the x-request-id response header) for correlation with backend Sentry/pino logs.

Auto-pagination

Cursor-paginated list endpoints expose Symbol.asyncIterator via autoPaginate:

import { autoPaginate } from '@liguelead/crm-sdk';

for await (const contact of autoPaginate(client.raw.listContacts, { query: { limit: 100 } })) {
  console.log(contact.id, contact.email);
}

The helper detects pagination exhaustion via meta.hasMore + meta.nextCursor and stops yielding when the server signals no more data.

Environment variables

  • LIGUELEAD_API_KEY — convention for server-side scripts. The SDK does NOT read it automatically; pass via auth.value.

TypeScript

The SDK is written in TypeScript with "strict": true and no any. The package ships both CJS and ESM bundles plus .d.ts files. Generated types are re-exported under the types namespace:

import { types } from '@liguelead/crm-sdk';
// Access generated operation params + response shapes via types.*

Versioning

The SDK follows the CRM API's major version. @liguelead/[email protected] corresponds to CRM API v8.x.y. Breaking changes in the API trigger a major bump and require an accept-breaking-change PR label on the PR that introduces them (see .github/workflows/openapi-diff.yml).

Source of truth

The SDK is auto-generated from packages/codegen/openapi.json in the LigueLead CRM monorepo via @hey-api/openapi-ts 0.96. Do NOT edit src/generated/ by hand.

License

UNLICENSED — internal use within the LigueLead organization. Contact the maintainers for redistribution rights.