TypeScript SDK
The SDK is generated from the v2 contract, so every route, field and enum in the API reference has a typed method. It has no runtime dependencies, ships ESM and CommonJS with types for both, and runs on Node.js 20 and later, Cloudflare Workers, Deno and Bun.
Works in two minutes
Code
Save this as hello.mjs and run node hello.mjs:
Code
Expected: your organization, the scopes of the key, and a new agent in draft. The rest of this page builds on that client.
Method names
Method names are the operationId of each route in the contract: agents.create, cases.takeControl, webhookEndpoints.create, pipelines.stages.update. Every method is method(...pathParams, body?, options?). A route without a body is method(...pathParams, options?). Types are exported under Types:
Code
Client options
| Option | Default | Use |
|---|---|---|
apiKey | LELEMON_API_KEY | The key. The constructor throws without one |
baseUrl | https://api.lelemon.cl | Another API, such as a local one |
subaccount | none | Sends Lelemon-Subaccount on every call |
maxRetries | 2 | Retries on 429, 500, 502, 503 and 504, and on network errors |
timeoutMs | 60000 | Time limit of each attempt |
retryDelayMs | 500 | Base delay between retries. Retry-After wins when present |
fetch | global fetch | Your own implementation |
Request options
The last argument of every method is the only options object. It carries query, idempotencyKey, subaccount, signal and headers.
Code
Idempotency
Every POST sends an Idempotency-Key. The SDK generates one when you do not pass idempotencyKey, and reuses it across its own retries, so a retry after a timeout never does the work twice. Pass your own key when the retry may come from a different process: a job that runs twice must send the same key both times. See Idempotency.
Try before you do: dryRun
Routes that send a message, cost money or are hard to undo accept dryRun. The call runs the same checks as the real one and answers what would happen, with dryRun: true; nothing is saved or sent.
Code
Pagination
Lists return a promise that is also an async iterable: loop over it to read every page, or await it to get the first page with data, hasMore and nextCursor.
Code
The cursor is opaque. Pass cursor in query to continue a page you stored. See Lists.
Errors
Every API error is a typed class. Branch on code, never on the text of detail.
Code
LelemonApiError carries status, code (typed as the codes of the error catalog), param, detail, docUrl, requestId, fix, budget and retryAfterSeconds. One subclass per family extends it:
| Class | Status |
|---|---|
BadRequestError | 400 |
AuthenticationError | 401 |
PermissionError | 403 |
NotFoundError | 404 |
ConflictError | 409 |
UnprocessableError | 422 |
RateLimitError | 429 |
ApiServerError | 5xx |
Outside the API: LelemonConnectionError covers network failures, LelemonTimeoutError an attempt that took longer than timeoutMs, LelemonAbortError a call you aborted with signal (which also stops the wait before a retry), and LelemonSignatureVerificationError a webhook that failed verification. All extend LelemonError. Send the requestId to support when you need help with a call.
Timeouts and retries
An attempt that takes longer than timeoutMs throws LelemonTimeoutError. Before that, the SDK retries 429, 500, 502, 503, 504 and network errors up to maxRetries times, waiting Retry-After when the API sent it and an exponential delay with jitter otherwise. A 4xx other than 429 is never retried. Because every POST carries an idempotency key, retrying is safe.
Code
Subaccounts
A platform acts on a customer's subaccount with its own key. Set it for one call, or scope a whole client:
Code
withSubaccount returns a new client with the same options and Lelemon-Subaccount set on every call. See Run Lelemon for your customers.
Webhooks
Lelemon.webhooks.constructEvent(rawBody, signatureHeader, secret) verifies the signature and returns the event, typed by its type. It is asynchronous, takes the raw body (a string, a Buffer or a Uint8Array) and throws LelemonSignatureVerificationError when the signature, the timestamp or the secret do not match. verifyHeader only verifies. Both accept { toleranceSeconds }, which defaults to 300.
Code
Always verify against the raw body: a body that was parsed and serialized again will not match. More in Verify signatures.
Helpers for your own endpoints
The SDK also verifies the calls Lelemon makes to your systems:
verifyRequestchecks the signature of an action call andderiveToolSecretderives a per-tool secret.verifyMcpAssertion(token, secrets, { audience, expectedTool, expectedArgs })checks the JWT that Lelemon sends to your MCP server when you turn on its run identity. See What an MCP server of yours receives.connectorbuilds and validates the documents of a data connector. See Connect your back office and the CLI.
What's next
- Set up an account from zero shows every call with the SDK.
- Command line to check and serve a data connector.
- MCP server to drive Lelemon from Claude or Cursor.