TypeScript SDK
Call the Kalpana API from Node, Bun or Deno with typed requests, retries, render waits and image uploads built in.
@kalpana-one/sdk wraps every public endpoint in this reference. It runs anywhere with fetch: Node 20.3 and later, Bun, Deno and edge runtimes.
npm install @kalpana-one/sdkpnpm add @kalpana-one/sdkyarn add @kalpana-one/sdkbun add @kalpana-one/sdkThe package is an ES module. Use import; require() works on Node 20.19 and later.
Render files
import { createKalpana } from "@kalpana-one/sdk";
const kalpana = createKalpana({
apiKey: process.env.KALPANA_API_KEY,
workspace: "acme",
});
const { jobs } = await kalpana.jobs.render({
jobs: [
{
page: "k7fq2m8a",
modifications: {
Headline: "Weekend offer",
Photo: { asset: "shoe.png", fit: "cover" },
},
},
],
});
console.log(jobs[0].file?.url);jobs.render creates up to 10 jobs, renders them and resolves once every file is ready. Each job is a page (or a flow, rendered as a video) with its modifications and output. It throws RenderError for the first job that fails or is cancelled. For more control, jobs.create({ jobs, run: true }) returns as soon as the API answers (it holds the answer up to 30 seconds while the jobs render, and waitSeconds shortens that), and jobs.list(ids) reads the jobs again until none is processing. See Create jobs.
const { jobs } = await kalpana.jobs.render(
{
jobs: [
{ page: feedPage, modifications: { Headline: "Lemon" } },
{ page: storyPage, modifications: { Headline: "Lemon" }, output: { format: "jpeg" } },
{ flow: launchVideo, output: { format: "mp4" } },
],
},
{ timeoutMs: 30 * 60_000, onUpdate: (request) => console.log(request.status) },
);Use the SDK on a server you trust. Never put an API key in browser or mobile code.
Configuration
const kalpana = createKalpana({
apiKey: "sk-...", // default: KALPANA_API_KEY
workspace: "acme", // default: KALPANA_WORKSPACE; slug or id
baseUrl: "https://app.kalpana.one/api", // default: KALPANA_BASE_URL
timeoutMs: 60_000,
maxRetries: 2,
});Every call takes an optional workspace that overrides the client's. Some endpoints name the field workspaceId, workspace_id or slug; the SDK sends the right one.
The last argument of every call takes signal, timeoutMs, maxRetries and headers.
Modifications
Jobs and batch rows fill layers through modifications, as described in Modifications. The SDK takes them as plain objects:
const modifications = {
Headline: "50% off",
Price: "<s1>₹8,999</s1> <s2>₹12,999</s2>", // a text with two styles
Photo: { asset: asset.id, fit: "cover", zoom: 1.1, panY: -0.05 },
Badge: { visible: false },
Subline: null, // keep the page's own value
"p3rtv9we@Headline": "50% off, in stories", // one page only
};Read a template's pages and layer names from kalpana.templates.get(id) and kalpana.templates.summary(id).
Batches
A batch renders every row on every page it names (pages, from any templates), on every page of templates, or as a flow's video. It never waits: it answers at once, and you poll it or take its webhooks.
const { id, jobCount, creditCost } = await kalpana.batches.create({
name: "September products",
pages: [feedPage, storyPage],
rows: products.map((product) => ({
modifications: { Title: product.title, Photo: product.assetId },
output: { format: "png" },
})),
run: true, // spends credits; leave out to keep a draft
});
const batch = await kalpana.batches.waitFor(id);
const { files } = await kalpana.batches.download(id);batches.validate checks rows without saving them and answers every issue, with each row as the server read it on each page. batches.validateStream yields each row as it is checked:
for await (const event of kalpana.batches.validateStream({ pages, rows })) {
if (event.type === "row" && !event.valid) console.log(event.row, event.issues);
}batches.rows.list(id) and batches.rows.listAll(id) read a batch's jobs; batches.update(id, { add, update, remove }) changes its pending ones.
Lists
List calls return one page. listAll walks every page:
for await (const template of kalpana.templates.listAll({ search: "sale" })) {
console.log(template.name);
}Images
import { readFile } from "node:fs/promises";
const asset = await kalpana.assets.upload({ file: await readFile("logo.png"), filename: "logo.png" });
const fromUrl = await kalpana.assets.uploadFromUrl({ url: "https://cdn.shopify.com/..." });
const { assets } = await kalpana.assets.findByName({ names: ["red-shirt", "blue-shirt"] });The SDK hashes and base64-encodes uploads for you.
Errors
A failed call throws a subclass of APIError with status, code, message and data:
| Class | Status |
|---|---|
BadRequestError (issues lists each invalid field) | 400 |
AuthenticationError | 401 |
PaymentRequiredError | 402 |
PermissionDeniedError | 403 |
NotFoundError | 404 |
ConflictError | 409 |
UnprocessableEntityError | 422 |
RateLimitError | 429 |
InternalServerError (traceId for support) | 5xx |
APIConnectionError and APITimeoutError mean no answer came back. All of them extend KalpanaError.
Rate limits (429) are retried for any call, waiting as long as Retry-After says. Network failures and 5xx answers are retried only for reads, because a write may have been applied before the connection dropped. See Errors.
Webhooks
Verify the Kalpana-Signature header against the raw body, then read the event:
import { constructWebhookEvent } from "@kalpana-one/sdk";
const event = await constructWebhookEvent({
body: await request.text(),
signature: request.headers.get("kalpana-signature"),
secret: process.env.KALPANA_WEBHOOK_SECRET,
});
if (event.type === "job.completed") console.log(event.data.creative.url);For a single job's callback URL (webhookUrl), the secret comes from the API key: await callbackSigningSecret(apiKey). See Receiving webhooks.
What the client covers
| Resource | Calls |
|---|---|
workspaces | list, get |
templates | list, listAll, get, summary, update, delete, duplicate, pages.rename, pages.delete, collections.get, collections.set |
templateCollections | list, create, update, delete, addTemplate |
jobs | create, render, list, get, waitFor, update, run, cancel, delete |
batches | list, listAll, get, create, update, delete, run, cancel, waitFor, download, validate, validateStream, rows.list, rows.listAll |
assets | list, listAll, upload, uploadFromUrl, uploads.create, uploads.complete, findByName, rename, delete, deleteMany, setCollections |
assetCollections | list, create, update, delete, addAssets, removeAssets |
fonts | list, listAll, upload, uploads.create, uploads.complete |
files (finished creatives) | list, listAll, rename, delete, deleteMany |
folders (of creatives) | list, listAll, create, update, delete, download, addFiles, removeFiles |
webhooks | list, get, create, update, delete, test, eventTypes, status, samples, deliveries.list, deliveries.get, deliveries.redeliver |
The package is on npm as @kalpana-one/sdk. For the terminal, see the CLI.
Updated 9 Nov 2026