@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.
Maintainers
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
fetchruntime; 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-afterseconds.
Install
npm install @liguelead/crm-sdk
# or: pnpm add @liguelead/crm-sdk
# or: yarn add @liguelead/crm-sdkQuickstart
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 viaauth.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.
