Kalpana Docs
Batch MCP

Batches

Check rows, stage a batch with its credit cost, run it, follow it and collect the files.

validate_batch

Checks rows against what the batch would render (pages, every page of some templates, or a flow's video) before anything is saved. It reports every problem by row and field, and shows each row as the server understood it on each page, so a misread value shows before any credits are spent. It runs the same checks as create_batch, and also returns the jobs the batch would make and their creditCost.

A batch renders exactly one of:

  • pages: page ids (from get_template_inputs, and from any templates). Each row renders once per page.
  • templates: template ids. Each row renders once on every page of each template, in page order.
  • flow: one flow, a video joining some of its template's pages. Each row renders as that video, with a video output format.

Each of rows (1 to 1,000) is an object with its own modifications and output. templateId, pageIds, flowIds, columns, defaults, and a batch-level outputFormat or stillTime are no longer accepted, and a request that sends one is refused.

modifications key a layer by its name (case-insensitive) or its id, the key that get_template_inputs gives, and take plain values:

  • Text layer: a string ("Diwali Sale"), or { text, visible }. When the layer has several styles (styles in get_template_inputs), mark runs with their tags: "<s1>Big</s1> <s2>sale on</s2> <s1>shoes</s1>". A style can repeat, in any order, and the server turns each run into a span in that style. Untagged text is s1, except whitespace between runs, which keeps the run before it. Tags do not nest; \<s1> is a literal <s1>.
  • Image layer: a string (an asset id, the image's file name with or without its extension, or a designer image key), or { asset, fit, zoom, panX, panY, visible }. fit is cover (the default: fills the slot and crops), contain (shows the whole image) or stretch; fill and fit are accepted as cover and contain. zoom is 0.2 to 4, about the slot's centre, where 1 is none. panX and panY are -1 to 1, a fraction of the slot's width or height; positive moves the image right or down. Leaving out asset keeps the template's image and only re-frames it.
  • Hide a layer: { "visible": false }.
  • One page: with several pages, a plain key fills every page that has the layer, and a key no page has is refused. Write <pageId>@<layer> to fill only that page, such as k7fq2m8a@Headline.
  • A page visited twice in a video: <pageId>#2@<layer> fills a page's second visit.
  • null: leaves the layer at the page's own value. Layers left out of modifications keep it too.

output is { format, fileName, stillTime }, and every field is optional (the format is png when omitted):

  • format: png, jpeg or webp render an image. mp4 or gif render the whole motion as a video. Video needs an animated template (isAnimated) and costs credits by its length and size. A flow needs a video format.
  • fileName: the file's name, without extension, up to 200 characters.
  • stillTime: for an image of an animated page, the second of the motion to show, from 0 to 600. Omit it to use the page's own still time, else the last frame. Refused with a video format.

A row for a three-page carousel, with a page key, an image object and a null:

{
  "pages": ["k7fq2m8a", "p3x9d1we", "h5n8c4ty"],
  "rows": [
    {
      "modifications": {
        "Headline": "Diwali Sale",
        "Product photo": { "asset": "kurta-red.jpg", "fit": "contain", "zoom": 1.2, "panY": -0.1 },
        "p3x9d1we@Headline": "Up to 40% off",
        "Brand logo": null
      },
      "output": { "format": "png", "fileName": "diwali-red-kurta" }
    }
  ]
}

A video row, where the second time the flow visits a page it shows different copy and one layer is hidden:

{
  "flow": "r2m6v0qb",
  "rows": [
    {
      "modifications": {
        "Headline": "Festive Offer",
        "k7fq2m8a#2@Headline": { "text": "Last 3 days", "visible": true },
        "Badge": { "visible": false }
      },
      "output": { "format": "mp4", "fileName": "festive-offer-video" }
    }
  ]
}

When two layers share a name, rows that use that name are refused with ambiguous_layer and both ids. The agent fills each layer by id and tells the user, who can rename one of them in Kalpana.

  • Annotations: readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false

  • Input Parameters (exactly one of pages, templates and flow):

    ParameterTypeRequiredDescription
    pagesstring[]One of these1 to 20 page ids, from any templates.
    templatesstring[]One of these1 to 20 template ids; every page of each.
    flowstringOne of theseOne flow id, rendered as a video.
    rowsArray<{ modifications, output? }>Yes1 to 1,000 rows. modifications defaults to {}; output is { format, fileName, stillTime }.
  • Output Schema:

    {
      valid: boolean;
      rowCount: number;
      validRows: number;
      invalidRows: number;
      jobCount: number;       // Jobs the batch makes: one per row per page
      creditCost: number;     // Credits those jobs spend when they render
      issues: Array<{
        row: number;          // Zero-based position in rows
        field: string;        // The modifications key the problem is in
        code?: string;        // For example "unknown_layer", "ambiguous_layer", "bad_value", "type_mismatch", "asset_not_found"
        message: string;
      }>;
      rows: Array<{           // Each row as the server understood it, on each page
        row: number;
        pages: Array<{
          page: string;       // The page, or the flow for a video
          modifications: Record<string, unknown>; // By layer: text as strings, images as { asset: <id>, name: <file name>, fit?, ... }
        }>;
      }>;
      warnings?: string[];    // Not errors: pass these on to the user, such as a flow longer than a video renders
    }

An answer with issues, for a row that names a layer the page does not have and an image the workspace cannot find:

{
  "valid": false,
  "rowCount": 2,
  "validRows": 1,
  "invalidRows": 1,
  "jobCount": 2,
  "creditCost": 2,
  "issues": [
    { "row": 1, "field": "Hedline", "code": "unknown_layer", "message": "No layer named \"Hedline\" on page k7fq2m8a." },
    { "row": 1, "field": "Product photo", "code": "asset_not_found", "message": "No image \"kurta-green.jpg\" in this workspace." }
  ],
  "rows": [
    {
      "row": 0,
      "pages": [
        {
          "page": "k7fq2m8a",
          "modifications": {
            "Headline": "Diwali Sale",
            "Product photo": { "asset": "5f0c2a8e-3b1d-4c6a-9e7f-2d84a1b6c0e3", "name": "kurta-red.jpg" }
          }
        }
      ]
    }
  ]
}

create_batch

Saves a new draft batch with one pending job per row for each page (from pages, or every page of templates), or one video per row for a flow. Does not spend credits or start rendering. Every call makes a separate batch, so repeating a call duplicates it, and one invalid row rejects the whole request with nothing saved. Rows are checked as in validate_batch.

create_batch calculates the exact credit cost required to run the batch. The agent must present this number to the user before calling run_batch.

  • Annotations: readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false

  • Input Parameters (exactly one of pages, templates and flow):

    ParameterTypeRequiredDescription
    namestringYesBatch name shown in Kalpana and used in output file names, 1 to 200 characters.
    pagesstring[]One of these1 to 20 page ids, from any templates.
    templatesstring[]One of these1 to 20 template ids; every page of each.
    flowstringOne of theseOne flow id, rendered as a video.
    rowsArray<{ modifications, output? }>Yes1 to 1,000 rows, as in validate_batch.
  • Output Schema:

    {
      id: string;               // Created batch UUID
      status: string;           // "draft" until run_batch
      rowCount: number;         // Rows given
      jobCount: number;         // Jobs created: one per row per page
      creditCost: number;       // Exact number of credits required to run
    }

run_batch

Dispatches rendering jobs for a pending batch. Consumes credits from the workspace's organization. A draft batch is published when it runs; if the run fails, it stays a draft. When the balance is too low nothing is queued and the error says there are not enough credits. An archived batch is refused, and a batch with a pending job on a deleted template returns Template not found and queues nothing. Jobs reset to pending by update_batch or update_job render on the next call.

  • Annotations: readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false

  • Input Parameters:

    ParameterTypeRequiredDescription
    batchIdstringYesUUID returned by create_batch.
    webhooksbooleanNoWhether the run sends batch.completed and job.completed to the workspace's webhook endpoints. Omit to keep the batch's setting, which is on.
  • Output Schema:

    {
      message: string;
      dispatched: number;       // Number of jobs queued for rendering
      failed: number;           // Number of jobs that failed dispatch
    }

get_batch

Returns a batch's progress and its job counts. A batch has one job per row and page, so 10 rows on 3 pages is 30 jobs.

  • Annotations: readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false

  • Input Parameters:

    ParameterTypeRequiredDescription
    batchIdstringYesUUID of the batch.
  • Output Schema:

    {
      id: string;
      workspaceId: string;
      name: string;
      createdAt: string;
      jobs: {                   // one job per row and page
        total: number;
        succeeded: number;      // rendered, with a file
        failed: number;
        processing: number;
        cancelled: number;
        pending: number;        // waiting for run_batch
      };
      status: "pending" | "processing" | "completed" | "partial" | "failed" | "cancelled";
    }

    status is processing while any job renders and pending while jobs wait for run_batch. Once every job has finished it is completed when all rendered, partial when some rendered and others failed or were cancelled, failed when none rendered and at least one failed, and cancelled when none rendered, none failed and at least one was cancelled. The counts carry the detail.


list_batches

Returns a paginated list of recent batches in the connected workspace. total counts every match, so it also answers how many batches exist.

  • Annotations: readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false

  • Input Parameters:

    ParameterTypeRequiredDefaultDescription
    searchstringNo—Optional search filter for batch name.
    templateIdstringNo—Optional template filter.
    pagenumberNo1Page number.
    limitnumberNo10Items per page (capped at 20).
  • Output Schema:

    {
      items: Array<{
        id: string;
        workspaceId: string;
        name: string;
        createdAt: string;
        jobs: {                   // one job per row and page
          total: number;
          succeeded: number;      // rendered, with a file
          failed: number;
          processing: number;
          cancelled: number;
          pending: number;        // waiting for run_batch
        };
        status: "pending" | "processing" | "completed" | "partial" | "failed" | "cancelled";
      }>;
      total: number;
      page: number;
      limit: number;
    }

get_batch_jobs

Lists a batch's jobs in row order, each row's pages together, one page of results at a time. Each job is shown as in Single jobs, with its modifications (the values it renders, by layer), status, render error and file. A job is editable while it is pending and has no file; processing, rendered and failed jobs cannot change. These job ids and values are the input for update_batch and update_job.

  • Annotations: readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false

  • Input Parameters:

    ParameterTypeRequiredDefaultDescription
    batchIdstringYes—UUID of the batch.
    pagenumberNo1Page number. A page past the end is empty.
    limitnumberNo10Jobs per page (capped at 20).
  • Output Schema:

    {
      jobs: Array<Job>;         // As in Single jobs
      total: number;
      page: number;
      limit: number;
    }

get_batch_outputs

Fetches download URLs for completed creative renders. Download links are pre-signed and short-lived.

  • Annotations: readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false

  • Input Parameters:

    ParameterTypeRequiredDefaultDescription
    batchIdstringYes—UUID of the completed batch.
    pagenumberNo1Page number.
    limitnumberNo10Items per page (capped at 20).
  • Output Schema:

    {
      items: Array<{
        id: string;             // Job UUID
        rowIndex?: number;      // The job's row in the batch, from 0; one row's pages and flows share it
        flowId?: string | null; // The flow this job renders, if any
        status: "pending" | "processing" | "success" | "failed" | "cancelled";
        outputKind: "image" | "video";
        outputFormat: "png" | "jpeg" | "webp" | "mp4" | "gif";
        contentType: string | null;
        downloadUrl: string | null; // Pre-signed download link
      }>;
      page: number;
      limit: number;
    }

update_batch

Renames a batch and changes, adds or removes its pending jobs in one request. It works on any batch that is not archived, but only pending jobs change: processing, rendered and failed jobs are refused. update merges each listed job's modifications over its own (a key given again replaces it, null clears it back to the page's value) and replaces the output fields given; unlisted jobs are untouched. add creates new jobs from rows on pages, templates or a flow, as create_batch does, so repeating a request with add duplicates them. remove deletes pending jobs permanently. Nothing renders until run_batch, and adding or removing jobs changes what it costs.

Needs templates:read and batches:create.

  • Annotations: readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false

  • Input Parameters:

    ParameterTypeRequiredDescription
    batchIdstringYesUUID of the batch.
    namestringNoNew batch name, 1 to 200 characters.
    updateArray<{ job, modifications?, output? }>NoPending jobs to change, up to 1,000. job is the id from get_batch_jobs; modifications holds only the layers to change; output holds only the output fields to change.
    add{ pages | templates | flow, rows }NoNew rows, with the same target and rows as create_batch.
    removestring[]NoIds of pending jobs to delete permanently, up to 1,000.
  • Output Schema:

    { id: string; added: number; updated: number; removed: number; }

Rename a batch, correct one job's headline, clear a logo back to the page's value, switch another job to a webp and drop a job:

{
  "batchId": "6b0e7c1a-52d4-4f0b-8a39-1c7d9e2f4a65",
  "name": "Diwali sale, kurtas",
  "update": [
    {
      "job": "0d3f9a7e-8b21-4c5d-a6e4-93b1c27f5d08",
      "modifications": { "Headline": "Diwali Sale, 40% off", "Brand logo": null }
    },
    { "job": "9a4c1e6b-37d2-4f80-b5a9-6e0d8c3f1b72", "output": { "format": "webp" } }
  ],
  "remove": ["c18e5d02-4a7b-49f3-8e6d-2b9f0a7c3e41"]
}

update_job

Changes one pending job that has no file yet, in a batch or on its own. modifications merge over the job's own (a key given again replaces it, null clears it back to the page's value) and output fields replace the job's, so repeating the same request gives the same result. Failed, processing and rendered jobs are refused. Switching the format to mp4 or gif makes it a video job, which needs an animated page and costs credits by its length and size. Does not render. For several jobs at once, or to add and remove jobs, use update_batch.

  • Annotations: readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false

  • Input Parameters:

    ParameterTypeRequiredDescription
    jobIdstringYesUUID of the pending job.
    modificationsobjectNoOnly the layers to change, keyed and valued as in validate_batch; null clears one.
    output{ format?, fileName?, stillTime? }NoOnly the output fields to change.
  • Output Schema: the job.


cancel_batch

Stops a batch: every job still pending or processing becomes cancelled. A cancelled job is final and is not charged; a render already under way may finish, but its result is discarded. Jobs that already rendered keep their output and their charge. Cancelled jobs cannot be resumed: to render them again, create a new batch with those rows. get_batch shows the cancelled count, and repeating the call cancels nothing more.

Needs batches:create on the connection and renders:create in the user's workspace role.

  • Annotations: readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false

  • Input Parameters:

    ParameterTypeRequiredDescription
    batchIdstringYesBatch ID returned by create_batch or list_batches.
  • Output Schema:

    {
      cancelled: number;        // Jobs this call cancelled; 0 when they had all finished
      stoppedMessages: number;  // Queued renders removed before they started
    }

Cancelling cannot be undone. Agents confirm with the user before calling cancel_batch or cancel_job.


cancel_job

Stops one job that is pending or processing: it becomes cancelled, final and not charged; a render already under way may finish, but its result is discarded. A job that already finished is left as it is. Other jobs in its batch keep rendering; cancel_batch stops them all.

Needs batches:create on the connection and renders:create in the user's workspace role.

  • Annotations: readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false

  • Input Parameters:

    ParameterTypeRequiredDescription
    jobIdstringYesJob ID from get_batch_jobs.
  • Output Schema:

    {
      cancelled: number;        // 1 when this call cancelled the job; 0 when it had already finished
      stoppedMessages: number;  // Queued renders removed before they started
    }
Was this page helpful?

Updated 10 Nov 2026

On this page