SDKs & REFERENCESDKs

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/sdk

Auth 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!,
});
OptionEnvironment variables (first match wins)Default
apiKeyJSONTOIMG_API_KEY, RENDER_API_KEYRequired
baseUrlJSONTOIMG_API_URL, JSONTOIMG_BASE_URL, RENDER_BASE_URLhttps://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"
}
FieldNotes
templateDesign ID (required)
versionOptional design version
formatpng | jpg | webp | pdf | avif
scaleDefault 1
transparentDefault false; png / webp / avif only
layersLayer name → property overrides
variablesJSON for logic-rule conditions (source: variables)
webhookUrlOptional webhook when the job finishes
expandVariantsBatch-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;
}
PropertyMeaning
statusHTTP status
codeAPI error code when present
detailsExtra validation or context
bodyRaw 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.

  • 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

On this page