Bulk Calculate: Many Calculators in One Call
Run up to 100 calculators in one project with a single request, then poll once for every item's results. Each one is saved as a sheet in that project, exactly as a single calculate saves one. Use it when you have a set of designs to run at once — a floor of joists, a batch of columns — instead of calling POST /v1/templates/{code}/calculate once per design.
This is an asynchronous flow:
- Submit the batch and receive a
batchId. - Poll for status until
stateiscompletedorfailed. - Read every item's results from the final poll.
Endpoints
Code
Bulk Calculate → · Get Batch →
1. Submit the batch
Each item is what a single calculate takes (a templateCode and attributes) plus an optional ref of your own that is echoed back on that item's result. Items can use different templates.
Code
| Field | Required | Description |
|---|---|---|
projectId | yes | Project the sheets are created in. |
items[] | yes | 1 to 100 items, reported back in this order. |
items[].templateCode | yes | Template to create the sheet from. |
items[].attributes | yes | Input values keyed by widget reference ID, as for a single calculate: an object; an item without one is refused. |
items[].ref | no | Your identifier for the item, up to 64 characters, unique within the batch. Echoed on the item's result. |
items[].name | no | Sheet name. Defaults to the template name plus the item's ref or position. |
items[].preset | no | A preset code of the template to apply before attributes. |
A 201 Created means the batch was accepted and the sheets are being created and solved. The body is the batch status (see below) with items: null; the Location header is the path to poll (/v1/projects/{projectId}/batches/{batchId}, relative to the API base, not a full URL) and Retry-After says how many seconds to wait.
Code
The request is accepted whole or rejected whole
If any item is invalid, the response is 422, nothing is created, and details lists every problem across every item so you can fix them all in one pass. Each details[].field is prefixed with the item it belongs to, for example items[2].templateCode.
Code
code is TOO_MANY_ITEMS when the batch exceeds 100 items, and VALIDATION_ERROR otherwise.
One batch per project at a time
While a batch is running for a project, another submission for that project returns 409 with code: "BATCH_IN_PROGRESS". The blocking batch's id is in details under field: "batchId". Poll it, and resubmit once it is terminal.
Code
2. Poll until the batch is terminal
Code
While the batch runs, state is processing, progressPercent and summary.solved advance, and items is null. The Location and Retry-After headers are present on every response until the batch is terminal, then absent.
Code
| Field | Description |
|---|---|
batchId | The batch's identifier — the one you poll. |
type | Always bulk-calculate. |
projectId | Project the batch's sheets were created in. |
itemCount | Number of items the batch was submitted with. |
state | processing, completed, or failed. Treat an unrecognised value as still in progress. |
progressPercent | Share of the batch's own sheets solved so far, 0 to 100, while processing. The batch writes it as it goes, so it can trail summary.solved by a poll — the example above reads 0 with one item already solved. Reads 100 once completed, whatever summary.solved is; read summary for the outcome, never this. |
summary | total, solved, errored, stale, checkErrored, pending, where pending is total - solved - errored - stale. See Reading the counts. While processing they are progress, and pending can briefly read 0 before state turns — poll on state, never on pending. |
error | Why the batch itself failed. BATCH_ABANDONED means the batch stopped reporting progress and will not resume; resubmit its items. |
items | null until the batch is terminal, then one entry per item in request order. |
createdAt | When the batch was accepted. |
completedAt | When it reached a terminal state. null until then. |
durationSeconds | How long the batch took, set once it reached a terminal state and the duration was recorded. It can be null on a failed batch that stopped without recording one. |
Reading the counts
Four counters, because four different things can be true of an item and the fix differs for each.
| Counter | Means | What to do |
|---|---|---|
solved | Results were computed from the inputs the sheet holds now. | Read them. |
errored | The batch could not produce results: the solver refused the sheet, the sheet is gone, or the batch stopped before reaching it. | Retry. |
stale | The batch did solve the sheet, and its inputs changed afterwards. Nothing failed — usually you edited the sheet yourself. | Re-solve that sheet, or resubmit the item. |
checkErrored | The sheet solved, but its governing check produced no verdict. These items are also counted in solved. | The results are current; there is just no pass/fail to read. |
pending is total - solved - errored - stale, so it reaches 0 once every item has an outcome.
To confirm every item is usable, check errored, stale and checkErrored are all 0 — not
errored alone.
On a terminal batch the counters are not frozen. They are recomputed from the sheets' current
state on every poll, so the same batchId can report different counts later — a batch that read
solved: 3 can read solved: 2, stale: 1 after someone edits one of its sheets, still
completed, with no error. If you need the batch's outcome as it stood when it finished, record it
when you first read the terminal poll.
How long to poll
Retry-After is 2 seconds, and the header is on every response until the batch is terminal — read the interval from it rather than hard-coding one, so a future change reaches you without a code change.
How long a batch takes depends on how many items it has and which templates they use, so do not assume a fixed duration. Poll at the interval, and read progressPercent if you need to show progress while it runs.
A batch that stops reporting progress is eventually declared abandoned: 10 minutes without progress once it is processing, or 1 hour while it is still queued. From then on it polls as failed with BATCH_ABANDONED, and the items are yours to resubmit. You do not need your own timeout for the batch to end; you need one only if you want to give up sooner than that.
3. Read the results
Once state is terminal (completed or failed), items holds one entry per requested item, in request order. Each solved item is exactly what GET /v1/sheets/{sheetId} returns for that sheet, plus your ref — so one parser handles both. A failed batch lists every item too: the ones it solved before failing carry their results, the rest carry the batch's error.
An item may also carry warnings, the same non-fatal findings a single calculate returns on its own response: an attribute the template does not define (type: "unknown_field", ignored) or a null value. The item still solved; the field named never landed.
Code
An item that has no results carries status: "unsolved" and an error naming why — see Errors for every code and what to do about it.
A completed batch can have summary.solved of 0: every item ran, and none could be solved. Either way summary.pending is 0 once the batch is terminal. state becomes failed when the batch itself could not run, or when it stopped reporting progress and will not resume (BATCH_ABANDONED); error says which.
Reading the verdict
Each solved item carries the template's governing check as summaryCheck, summaryCheckError and, when the check failed, summaryCheckFailReasons.
- An item is usable when
summaryCheckis present andsummaryCheckErrorisfalse. It passes whensummaryCheckistrueor a number from 0 to 1. - A check that could not be evaluated (a demand divided by zero, a missing member) reports
summaryCheckError: truewith nosummaryCheck. The item still readsstatus: "solved", because its results are current, andsummary.checkErroredcounts it. To know every item is usable, checksummary.errored == 0andsummary.checkErrored == 0, noterroredalone. summaryCheckFailReasonsis present only when the check evaluated and failed.
Never read passed as the verdict. It records whether the sheet has ever passed: it becomes true on the first passing solve and stays true afterwards, so a design this batch found failing can still report passed: true. Read summaryCheck and summaryCheckError together, as above.
Moving from POST /v1/timber-beam/calculate
The typed route wraps the same solve in a results object of its own. A bulk item reports the sheet as GET /v1/sheets/{sheetId} does, so the verdict sits at the item's top level and results holds only the template's flagged outputs, which for timberBeamASD is none.
Typed results.* | Bulk item |
|---|---|
results.utilizationRatio | summaryCheck |
results.summaryCheck.value | summaryCheck |
results.summaryCheck.referenceId | summaryCheckReferenceId |
results.summaryCheck.error | summaryCheckError |
results.summaryCheck.failReasons ([] when none) | summaryCheckFailReasons (absent when none) |
results.passed (forced false on an invalid check) | passed is the has-ever-passed latch; read summaryCheck and summaryCheckError instead |
results.warning string on an invalid check | summaryCheckError: true with summaryCheck absent |
Errors
Errors arrive at three levels, and the level tells you what to do: the request was refused and nothing exists, the batch ran but could not finish, or one item has no results while the rest are fine. Branch on error.code, not on the message — messages are written for people and change.
error.code | Level | Status | Means |
|---|---|---|---|
VALIDATION_ERROR | Request | 400 / 422 | 400 for malformed JSON; 422 for a well-formed request an item or field failed. Nothing was created. details[].field names each problem, prefixed items[i]. for a per-item one. |
TOO_MANY_ITEMS | Request | 422 | items exceeded 100. Nothing was created. |
BATCH_IN_PROGRESS | Request | 409 | A batch is already running for this project. details carries one entry, field: "batchId", whose message is the batch to poll. |
NOT_FOUND | Request | 404 | The project or batch does not exist, or is not accessible with this API key. |
BATCH_FAILED | Batch | 200 | The batch could not be run. |
VALIDATION_CHANGED | Batch | 200 | A template became unavailable between acceptance and processing. |
BATCH_ABANDONED | Batch | 200 | The batch stopped reporting progress and will not resume. Resubmit its items. |
SOLVE_FAILED | Item | 200 | The solver could not answer this sheet. Retry the batch; if every item reports this, the cause is usually not your inputs, so retry before changing them. |
RESULTS_STALE | Item | 200 | The batch solved this sheet, but its inputs have changed since — edited in the app, patched through the API, the project's defaults changed, or the sheet was upgraded — so the stored results no longer match. Counted in summary.stale, not in errored: nothing failed. Solve the sheet again, or resubmit the item. |
SHEET_UNAVAILABLE | Item | 200 | There is no sheet to report: it was deleted after the batch ran, or the batch died before creating it and recorded no reason of its own. Resubmit the item to get a fresh sheet. |
TEMPLATE_NOT_FOUND | Item | 200 | The template is no longer accessible with this API key. Check your plan's template access. |
Batch- and item-level errors arrive inside a successful poll — a batch that failed is still a 200 on GET, with state: "failed" and an error. Only the request tier uses a 4xx status. A batch-level code also appears on each unfinished item, so an item carrying BATCH_FAILED, VALIDATION_CHANGED or BATCH_ABANDONED is telling you the batch stopped, not that the item itself was at fault.
Treat an unrecognised code at any level as a failure to retry — new codes can be added.
Notes
- Order and
ref. Items always come back in request order, but areflets you match results without relying on position. - Sheets persist. The sheets a batch creates are ordinary sheets in the project; open any of them in the app via its
sheetUrl, or delete them withPOST /v1/sheets/bulk-delete. - Retries. Resubmitting an identical batch after the first completed creates a second batch and a second set of sheets. If you are unsure whether a submission went through, poll the
batchIdfrom the409or check the project's sheets first.
Next Steps
- Sheet Lifecycle — refine one of the batch's sheets and re-solve it, without creating a new one
- Generic Templates — discover template codes and inspect the fields a template takes
- API Reference — full schemas for the batch request, status, and items