Node.js
Official TypeScript client for Node.js and Bun — create renders, wait, download assets, and run batches.
@j2i/sdk is the official TypeScript client for the jsontoimg Render API. Use it in Node.js, Bun, or any TypeScript project to create renders, wait for results, download assets, and run batches.
For shell scripts and CI, see the CLI. For raw HTTP endpoints, see the API reference. Python and PHP call the same HTTP API until those SDKs ship.
Install
npm install @j2i/sdkAuth and config
Create an API key in the dashboard under Integrations → API keys, then pass it to the client. See Authentication.
import { createClient } from "@j2i/sdk";
const client = createClient({
apiKey: process.env.JSONTOIMG_API_KEY!,
});| Option | Environment variables (first match wins) | Default |
|---|---|---|
apiKey | JSONTOIMG_API_KEY, RENDER_API_KEY | Required |
baseUrl | JSONTOIMG_API_URL, JSONTOIMG_BASE_URL, RENDER_BASE_URL | https://app.jsontoimg.com |
Use the API origin only for baseUrl (no trailing slash). /api/v1 and /mcp suffixes are stripped if present. Requests are authenticated with Authorization: Bearer <apiKey>.
Quick start
import { createClient } from "@j2i/sdk";
const client = createClient({
apiKey: process.env.JSONTOIMG_API_KEY!,
});
const job = await client.renderAndWait({
template: "<design-id>",
format: "png",
layers: { headline: { text: "Hello" } },
});
if (job.status === "done") {
console.log(job.id, job.assetUrl);
}renderAndWait submits a render, waits up to 60 seconds by default, and can write the finished file to disk with out.
Client methods
createRender
createRender(
body: CreateRenderRequest,
opts?: { wait?: number }
): Promise<RenderJob | RenderAccepted>Creates a render. Pass wait (seconds) to block until the job finishes or the wait window ends. Without wait, you get a queued job and can poll or call waitForRender later.
const accepted = await client.createRender({
template: "<design-id>",
format: "png",
});
const done = await client.createRender(
{ template: "<design-id>", format: "png" },
{ wait: 60 }
);getRender
getRender(id: string): Promise<RenderJob>Fetches the current status of a render job.
const job = await client.getRender(renderId);
console.log(job.status);waitForRender
waitForRender(
id: string,
opts?: { timeout?: number }
): Promise<RenderJob | RenderWaitRetry>Waits for a render to finish. If the job is still running, the response may include retryAfterMs. See SSE and wait.
const result = await client.waitForRender(renderId, { timeout: 60 });downloadRenderAsset
downloadRenderAsset(id: string): Promise<{
buffer: Buffer;
contentType: string;
}>Downloads the finished asset. The job must be done. Uses your API key. For <img> / OG tags, use job.assetUrl (includes ?sig=) or signImageUrl.
import { writeFile } from "node:fs/promises";
const { buffer, contentType } = await client.downloadRenderAsset(renderId);
await writeFile("out.png", buffer);
console.log(contentType);signImageUrl
signImageUrl(body: SignImageRequest): Promise<SignedImageUrl>Mints an HMAC-signed URL for CMS / Open Graph. Crawlers fetch it with no API key. See URL rendering.
const { url } = await client.signImageUrl({
template: "<design-id>",
format: "png",
layers: { headline: { text: "Hello" } },
});signImageUrls
signImageUrls(body: SignImageBatchRequest): Promise<SignedImageUrlBatch>Mints up to 50 signed URLs in one call. Same contract as signImageUrl, not queued batch renders.
const { urls } = await client.signImageUrls({
jobs: [
{ template: "<design-id>", layers: { headline: { text: "Hello" } } },
{ template: "<design-id>", layers: { headline: { text: "World" } } },
],
});renderAndWait
renderAndWait(
body: CreateRenderRequest,
opts?: { wait?: number; out?: string }
): Promise<RenderJob | RenderAccepted | DryRunResult>Creates a render with wait (default 60 seconds). If you pass out and the job completes, the asset is saved to that path.
const job = await client.renderAndWait(
{
template: "<design-id>",
format: "png",
layers: { headline: { text: "Hello" } },
},
{ wait: 90, out: "./hello.png" }
);listTemplates
listTemplates(opts?: {
project?: string;
limit?: number;
offset?: number;
}): Promise<TemplateList>Returns one page of templates (default 50, max 100). If hasMore is true, pass offset for the next page. Use name and description to pick one, then call getTemplateSchema.
const { templates, hasMore } = await client.listTemplates();
const sale = templates.find((item) =>
item.description?.includes("sale")
);getTemplateSchema
getTemplateSchema(
id: string,
opts?: { version?: number }
): Promise<TemplateSchema>Returns the named layers you can override for a template.
const schema = await client.getTemplateSchema("<template-id>", { version: 1 });
for (const layer of schema.layers) {
console.log(layer.layerName, layer.layerType);
}createBatch / getBatch
createBatch(body: CreateBatchRequest): Promise<BatchAccepted>
getBatch(id: string): Promise<BatchStatus>Queue up to 50 renders in one request, then check progress. See Batch renders.
const batch = await client.createBatch({
jobs: [
{
template: "<design-id>",
format: "png",
layers: { headline: { text: "A" } },
},
{
template: "<design-id>",
format: "jpg",
layers: { headline: { text: "B" } },
},
],
});
const status = await client.getBatch(batch.id);
console.log(status.status, status.totals);Request body
{
"template": "<design-id>",
"format": "png",
"scale": 1,
"transparent": false,
"layers": {
"headline": { "text": "Hello" }
},
"variables": { "plan": "pro" },
"webhookUrl": "https://example.com/hook"
}| Field | Notes |
|---|---|
template | Design ID (required) |
version | Optional design version |
format | png | jpg | webp | pdf | avif |
scale | Default 1 |
transparent | Default false; png / webp / avif only |
layers | Layer name → property overrides |
variables | JSON for logic-rule conditions (source: variables) |
webhookUrl | Optional webhook when the job finishes |
expandVariants | Batch-only. { "layer": "<variant-set name>" } fans one job into one per variant |
Errors
API failures throw JsontoimgApiError:
import { createClient, JsontoimgApiError } from "@j2i/sdk";
try {
await client.createRender({ template: "<design-id>" });
} catch (err) {
if (err instanceof JsontoimgApiError) {
console.error(err.status, err.message, err.code, err.details);
}
throw err;
}| Property | Meaning |
|---|---|
status | HTTP status |
code | API error code when present |
details | Extra validation or context |
body | Raw response body |
See Errors for the full error shape.
Advanced
client.api is a typed low-level HTTP client if you need an endpoint shape the helpers do not wrap. The one helper-less path is GET /api/v1/renders/{id}/events (SSE).
const { data, error, response } = await client.api.GET(
"/api/v1/renders/{id}/events",
{ params: { path: { id: renderId } } }
);Useful exports include CreateRenderRequest, RenderJob, BatchStatus, TemplateList, TemplateSchema, JsontoimgApiError, resolveApiKey, and resolveBaseUrl.
Related
- CLI — same API from the shell
- MCP — drive renders from Cursor, Claude, and VS Code
- Webhooks — completion callbacks
- URL rendering — signed URLs for CMS / OG
- API reference — OpenAPI and Try it