JsonCut
All articlesDeveloper guides

Why one bad row should not stop your bulk render

Bulk rendering should not stop at the first bad row: validate rows before queueing, let valid items render, retry safely with idempotency keys and resubmit only corrected rows.

A single malformed row should not kill a 5,000-variant batch. This guide shows how row-level validation, partial-failure handling and idempotency keys keep valid renders moving — and how to resubmit only the rows that failed.

The 5,000-row file with two bad rows

It is the classic batch scenario: a product feed lands with thousands of rows, marketing wants variants for every SKU by tomorrow, and somewhere in row 3,187 someone typed 79,9O instead of 79.90. What should happen next is a design decision, not an accident: either the whole batch dies, or the 4,998 good rows render and the two broken rows come back with a readable reason.

Legacy export pipelines often behave like the first option — one exception aborts the loop, and a retry re-renders everything, including rows that already succeeded. This article walks through the second option: bulk rendering with row-level validation, partial-failure handling and retry-safe idempotency, using JsonCut's typed template API as the concrete example. The numbers above are an illustration; the mechanisms below are documented behavior.

Where bulk renders actually fail

Bulk failures cluster into three stages, and each stage deserves its own handling:

StageTypical failureRight response
Input validationWrong field name, bad number, missing media, wrong MIME typeReject the row before any render work starts
RenderingOutput settings incompatible, media unavailable mid-runFail that row, keep the batch moving, expose a retryable flag
DeliveryYour webhook endpoint is down, artifact download times outRetry delivery; never re-render what already completed

The key principle: validate everything you can before expensive work begins, and never let one stage's failure invalidate another stage's finished results.

Row-level validation: reject early, render the rest

JsonCut's bulk rendering is built around a typed template contract: you publish a project as a template, every input has an explicit type (string, number, boolean, color, image, video, audio, sanitized HTML or bounded string arrays), and every row in a batch is validated against the pinned template version before it is queued.

Three documented consequences matter for error handling:

  • JSON and CSV batches are capped at 500 rows. Larger feeds are split into multiple batches — which is also the natural unit for retries and progress reporting.
  • Every row is validated before queueing. Invalid rows are reported with their index and a safe error message; they do not prevent valid rows from rendering.
  • The batch is pinned to one immutable template version. A row that validated correctly cannot suddenly behave differently because someone republished the design mid-run.

For CSV, headers must match the template input IDs, and values are parsed against the published input types before any valid row is queued. If you are designing the input side of that contract — which fields should be typed how — see the earlier guide on template input design.

The batch contract at a glance

FieldPurposeError-handling role
items or csvRow payload (exactly one of them per request)Wrong shape fails fast, before rendering
Batch-level idempotencyKeyIdentifies the whole requestA resent batch with the same key cannot duplicate the run
Row-level idempotencyKeyIdentifies one outputCorrected resubmissions keep stable row identity
Batch-level defaultsMerged first; row values take precedenceFewer per-row mistakes to validate away
Template versionBatch pinned to an immutable versionDeterministic validation and rendering across retries

Splitting a large feed into predictable groups of up to 500 rows — with their own batch keys — gives you bounded blast radius: one bad group is one corrected resubmission, not a 5,000-row rerun.

Build a correction loop, not a full rerun

When validation reports broken rows, the goal is to resubmit only what failed:

  • In Studio, invalid rows remain visible with their index and safe error, and you can correct and resubmit selected rows.
  • Through the API, you submit a new batch containing only the corrected rows — and keep the same explicit template version when the approved design must remain unchanged.

This is where stable row identity pays off. If row sku-1042-de failed on a malformed price and you resubmit it with the same row-level idempotency key after fixing the value, your delivery logic can key outputs by that stable identifier instead of by batch position. The result: no duplicate artifacts, no double delivery, no manual deduplication spreadsheet.

Idempotency: why retries don't double your renders

Retries are unavoidable — networks drop, deploys restart workers. Idempotency keys are what keep a retry from becoming a second render of the same work. JsonCut's API loop asks you to use an Idempotency-Key for render creation and to obey the returned retryable flag when handling errors.

The pattern is older than this API and worth understanding on its own. Payment APIs popularized it; Stripe's documentation describes the semantics precisely: a client generates a unique key per logical operation, the server saves the first response for that key, and any retry with the same key returns the same saved result — including stored errors — instead of executing the operation twice. Stripe also notes two practical guardrails: reuse a key with different parameters and the server rejects the mismatch, and keys have a lifetime (Stripe prunes them after at least 24 hours), so "same key" must always mean "same intended operation."

Applied to bulk rendering:

SituationWithout idempotencyWith idempotency keys
Timeout after submitting a batchResend? Maybe render everything twiceResend same key; get the same answer
Worker crash mid-batchAmbiguous state, full manual auditRow keys identify completed outputs
Row failed, you fixed the valueNew row identity? Outputs driftSame row key, corrected payload, stable identity

Track every row through the run

Bulk work in JsonCut is asynchronous: you submit, then either poll GET /api/v2/bulk-renders/{id} or receive signed webhooks, and you can cancel unfinished work with DELETE /api/v2/bulk-renders/{id}. Individual renders move through asynchronous states — queued, rendering, completed, failed or cancelled — the same lifecycle described in the render and webhook guide.

For operators, expose progress in the units they actually act on:

MetricQuestion it answers
Rows acceptedDid validation pass the whole file?
Rows rejected (with reasons)Which cells need fixing, and by whom?
Renders queued / renderingIs the system working or waiting?
Outputs completedWhat is ready for delivery?
Render failures (retryable or not)What needs a retry versus a design fix?

A single percentage hides all of that. Five counters tell the on-call person exactly what to do next.

A production checklist

  1. Validate the entire input file before reserving render work; treat field-level errors as data, not exceptions.
  2. Batch in bounded groups (JsonCut caps JSON and CSV batches at 500 rows) with explicit template versions.
  3. Send a batch-level idempotency key per logical submission and stable row-level keys per output.
  4. Continue valid rows when a row fails; export failures with index and reason.
  5. Obey the retryable flag; never blind-retry a non-retryable error.
  6. Key delivery on row identity, not batch position, so corrected resubmissions cannot double-send.
  7. Keep one dashboard question per failure stage: validation, rendering, delivery.

FAQ

Does one invalid row cancel the whole batch? No. Rows are validated against the pinned template version before queueing; invalid rows are reported with their index and a safe error, and valid rows continue to render.

What happens if I retry a batch that already ran? If you reuse the same idempotency key, the API answers with the recorded result instead of executing the work again. Use a fresh key only when you genuinely want a new logical operation.

How do I fix the rows that failed? In Studio, correct and resubmit selected rows. Through the API, submit a new batch with only the corrected rows, keeping the same explicit template version if the approved design must stay unchanged.

Sources

https://jsoncut.com/docs/templates/ — JsonCut documentation, typed templates and bulk rendering — accessed 2026-10-09 https://jsoncut.com/docs/ — JsonCut documentation, the four-step API loop and idempotency — accessed 2026-10-09 https://jsoncut.com/docs/quickstart/ — JsonCut documentation, asynchronous render creation, polling and artifacts — accessed 2026-10-09 https://docs.stripe.com/api/idempotent_requests — Stripe API reference, idempotent request semantics — accessed 2026-10-09

CONTINUE EXPLORING
Typed templates and bulk renderingVideo generation API guideTemplate input designDynamic OG images from your app data