More / SDKs
SDKs.
Official TypeScript and Python clients for the REST API. Typed responses with the same field names as the API, and automatic retries on 429 and 5xx.
Install
TypeScriptPython
npm install @leadsetsai/sdk
The TypeScript SDK has no runtime dependencies and runs on Node.js 18+, Bun, Deno and edge runtimes. The Python SDK has a sync Leadsets and an async AsyncLeadsets client.
Create a client
Pass your API key, or leave it out and the client reads LEADSETS_API_KEY (or LEADSETS_KEY) from the environment. Never hard-code it. See Authentication.
TypeScriptPython
import { Leadsets } from "@leadsetsai/sdk";
const ls = new Leadsets(process.env.LEADSETS_KEY);
const res = await ls.people.search({
function: ["revenue"],
seniority: ["vp", "head"],
country: "US",
limit: 10,
});
console.log(res.items[0].match_reasons, res.usage);import os
from leadsets import Leadsets
ls = Leadsets(os.environ["LEADSETS_KEY"])
res = ls.people.search(
function=["revenue"],
seniority=["vp", "head"],
country="US",
limit=10,
)
print(res.items[0].match_reasons, res.usage)Methods
One method per REST route. Arguments are the route's body parameters, with the same names in both languages.
Errors and retries
Every API error is thrown as a typed error with the API's status, type, message and request_id. Branch on the class:
| Class | Status | type |
|---|
| InvalidRequestError | 400 | invalid_request (param names the field) |
| AuthenticationError | 401 | unauthorized |
| FreeRecordsExhaustedError | 402 | free_records_exhausted |
| ScrapeUnitsExhaustedError | 402 | scrape_units_exhausted |
| PermissionDeniedError | 403 | forbidden, with the upgrade URL when a plan change would allow the call |
| NotFoundError | 404 | not_found |
| IdempotencyConflictError | 409 | idempotency_conflict |
| RateLimitError | 429 | window_full, weekly_limit, too_many_concurrent |
| InternalServerError | 5xx | internal_error |
| ServiceUnavailableError | 503 | unavailable |
| APIConnectionError | none | No answer from the API. |
| APITimeoutError | none | The request timed out. |
Both payment errors extend PaymentRequiredError, every HTTP error extends APIError, and everything extends LeadsetsError.
TypeScriptPython
import { APIError, FreeRecordsExhaustedError, NotFoundError, RateLimitError } from "@leadsetsai/sdk";
try {
await ls.people.enrich({ email: "maya@northwind.io" });
} catch (err) {
if (err instanceof NotFoundError) {
// nothing to enrich; nothing was counted
} else if (err instanceof FreeRecordsExhaustedError) {
askToUpgrade();
} else if (err instanceof RateLimitError) {
console.log(err.type, err.meter, err.resetsAt, err.retryAfter);
} else if (err instanceof APIError) {
console.log(err.status, err.type, err.message, err.requestId);
} else {
throw err;
}
}from leadsets import APIError, FreeRecordsExhaustedError, NotFoundError, RateLimitError
try:
ls.people.enrich(email="maya@northwind.io")
except NotFoundError:
pass # nothing to enrich; nothing was counted
except FreeRecordsExhaustedError:
ask_to_upgrade()
except RateLimitError as e:
print(e.type, e.meter, e.resets_at, e.retry_after)
except APIError as e:
print(e.status_code, e.type, e.message, e.request_id)Retries. The SDKs retry 429, 5xx, timeouts and network errors up to maxRetries / max_retries times (default 2). They wait for Retry-After when the API sends one, and otherwise back off exponentially with jitter. A Retry-After longer than maxRetryDelay / max_retry_delay (default 60 seconds), such as a full weekly window, is thrown at once so a run doesn't hang. 400, 401, 402, 403, 404 and 409 are never retried. Every error type is in Errors and retries.
Idempotency keys
The SDKs send an Idempotency-Key with every paid call (every POST) and reuse it across their own retries, so a retry never counts records twice. Pass your own key to make a call safe to repeat across processes, for example one key per CRM row. See Idempotency.
TypeScriptPython
await ls.people.enrich({ email: row.email }, { idempotencyKey: `crm-${row.id}` });ls.people.enrich(email=row["email"], idempotency_key=f"crm-{row['id']}")searchAll / search_all follows next_cursor for you and yields one record at a time. Each record returned counts, so cap a run with maxItems / max_items; the last page asks only for what is left. searchPages / search_pages yields whole pages when you want to read usage as you go. See Pagination.
TypeScriptPython
for await (const company of ls.companies.searchAll({ industry: "saas" }, { maxItems: 250 })) {
save(company);
}for company in ls.companies.search_all(industry="saas", max_items=250):
save(company)Without an SDK
Every SDK method is one HTTPS request with a JSON body, so any HTTP client works: send Authorization: Bearer and Content-Type: application/json. Each reference page has a cURL example, starting with POST /v1/people/search. For agents, use MCP instead: no code at all.
Was this page useful? Tell us