API reference
Base URL: https://api.workbookapi.com. Send keys only as Authorization: Bearer <key>, from your server. There is no browser CORS access. An active key sent in a URL is revoked and we email its owner.
An admin key manages models and calculates. A calculate-only key calls calculate and scenarios; use it in production apps. Create and revoke keys in the dashboard.
Use defined workbook names in ref where possible, so new versions survive moved cells. For example, use a name such as Principal instead of Inputs!B2.
Each call starts from the version's saved inputs. Omitted inputs keep their saved value. Outputs are scalars for cells, arrays of rows for ranges, and null for blanks; spreadsheet errors are objects such as {"error":"#N/A"}. Dates are workbook serial numbers.
Only successful calculations and batch rows with outputs count as scenarios. A retry counts again. Usage is committed when the platform has read the whole response body; your connection can still fail after that. Set your client timeout to at least 90 seconds. See all limits.
Within /v1, fields and codes may be added. Ignore unknown fields and handle unknown error codes by HTTP status.
POST /v1/models
Upload a workbook as a new model. Admin key.
Request, response and error codes
post:
tags: [models]
operationId: uploadModel
summary: Upload a workbook as a new model
description: |
Creates a model from an `.xlsx` or `.xlsm` file and returns its first
version and compatibility report.
- Files up to 10 MiB. The 8 MiB JSON body limit does not apply here.
- The entire multipart body must be read within 60 s. A slow body
holds an isolate memory reservation; a 10 MiB upload needs about
1.4 Mbit/s. Exceeding this deadline answers 503
`service_unavailable` with `Retry-After` and is not counted.
- Isolate memory capacity refusals are 503 `service_unavailable`
with `Retry-After` and are not counted toward daily uploads.
- The check runs on the server and takes up to 30 s.
- A model is admitted only if its measured calculation memory is at
most 98 MiB.
- One upload at a time per account. It uses one of the plan's
concurrent slots.
- Occupies one model slot while the model exists.
- Counts against daily uploads as listed in "Order of checks".
- When every check instance is busy, the answer is 503 with
`Retry-After`; there is no queue.
security:
- adminKey: []
requestBody:
required: true
x-max-bytes: 10551296
x-violation: file_too_large
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
contentMediaType: application/octet-stream
description: The workbook. The part's filename becomes `file_name`. Format is recognised from the bytes, not the name.
x-max-bytes: 10485760
x-violation: file_too_large
responses:
'201':
description: Model created; the first version is active.
headers:
X-Request-Id: {$ref: '#/components/headers/X-Request-Id'}
content:
application/json:
schema: {$ref: '#/components/schemas/UploadResult'}
'400': {$ref: '#/components/responses/Error400'}
'401': {$ref: '#/components/responses/Error401'}
'402': {$ref: '#/components/responses/Error402'}
'403': {$ref: '#/components/responses/Error403'}
'413': {$ref: '#/components/responses/Error413'}
'422': {$ref: '#/components/responses/Error422'}
'429': {$ref: '#/components/responses/Error429'}
'500': {$ref: '#/components/responses/Error500'}
'503': {$ref: '#/components/responses/Error503'}
x-error-codes: [invalid_request, unauthorized, plan_required, model_limit, insufficient_scope, file_too_large, not_xlsx, old_xls, encrypted, damaged_zip, model_too_large, check_timeout, engine_load, rate_limited, upload_limit, concurrency_limited, compute_limit, internal_error, trials_paused, service_unavailable]GET /v1/models/{model_id}
Read a model, its active version and its interface. Admin key.
Request, response and error codes
parameters:
- $ref: '#/components/parameters/ModelId'
get:
tags: [models]
operationId: getModel
summary: Read a model, its active version and its interface
security:
- adminKey: []
responses:
'200':
description: The model.
headers:
X-Request-Id: {$ref: '#/components/headers/X-Request-Id'}
content:
application/json:
schema:
allOf:
- $ref: '#/components/schemas/WithRequestId'
- $ref: '#/components/schemas/Model'
'401': {$ref: '#/components/responses/Error401'}
'402': {$ref: '#/components/responses/Error402'}
'403': {$ref: '#/components/responses/Error403'}
'404': {$ref: '#/components/responses/Error404'}
'429': {$ref: '#/components/responses/Error429'}
'500': {$ref: '#/components/responses/Error500'}
'503': {$ref: '#/components/responses/Error503'}
x-error-codes: [unauthorized, plan_required, insufficient_scope, not_found, rate_limited, internal_error, trials_paused, service_unavailable]DELETE /v1/models/{model_id}
Delete a model with all its versions. Admin key.
Request, response and error codes
parameters:
- $ref: '#/components/parameters/ModelId'
delete:
tags: [models]
operationId: deleteModel
summary: Delete a model with all its versions
description: |
Deletes the model's version files, snapshots, interface and reports from
primary storage and frees its slot. Usage already counted in the period
stays.
Erasure:
- From this response on, the model answers 404 and nothing can read
its files. A calculation already running on it finishes; a
running upload of a new version cannot publish.
- The deletion task erases the files that exist at deletion:
usually before this response, otherwise once running
calculations end, within about 3 minutes.
- A write that was still in flight can still complete in storage
later. That file is never readable. The first completed orphan
sweep cycle that starts after the file is one hour old erases it:
within 2 days of its appearing (a cycle starts daily and a cycle
not finished within 12 hours raises an operator alert).
- Each bound counts per object from its appearance: a late file
within 2 days; a backup copy within 30 days of being made. There
is no fixed deadline from deletion by which all content is gone
while an unknown in-flight write can still arrive.
security:
- adminKey: []
responses:
'200':
description: Deleted.
headers:
X-Request-Id: {$ref: '#/components/headers/X-Request-Id'}
content:
application/json:
schema: {$ref: '#/components/schemas/Deleted'}
'401': {$ref: '#/components/responses/Error401'}
'402': {$ref: '#/components/responses/Error402'}
'403': {$ref: '#/components/responses/Error403'}
'404': {$ref: '#/components/responses/Error404'}
'429': {$ref: '#/components/responses/Error429'}
'500': {$ref: '#/components/responses/Error500'}
'503': {$ref: '#/components/responses/Error503'}
x-error-codes: [unauthorized, plan_required, insufficient_scope, not_found, rate_limited, internal_error, trials_paused, service_unavailable]POST /v1/models/{model_id}/versions
Upload a new version of the workbook under the same model id. Admin key.
Request, response and error codes
parameters:
- $ref: '#/components/parameters/ModelId'
post:
tags: [models]
operationId: uploadVersion
summary: Upload a new version of the workbook under the same model id
description: |
Same file checks, limits and daily counting as an upload.
If the model has an interface, the new workbook must keep it. Every
declared input must still be one constant cell of the declared type,
and every declared output must resolve.
The new version becomes active atomically. Calculations already
running finish on the previous version. A rejected version leaves the
active one unchanged. Uses no extra slot.
security:
- adminKey: []
requestBody:
required: true
x-max-bytes: 10551296
x-violation: file_too_large
content:
multipart/form-data:
schema:
type: object
required: [file]
properties:
file:
type: string
contentMediaType: application/octet-stream
x-max-bytes: 10485760
x-violation: file_too_large
responses:
'201':
description: New version accepted and active.
headers:
X-Request-Id: {$ref: '#/components/headers/X-Request-Id'}
content:
application/json:
schema: {$ref: '#/components/schemas/VersionResult'}
'400': {$ref: '#/components/responses/Error400'}
'401': {$ref: '#/components/responses/Error401'}
'402': {$ref: '#/components/responses/Error402'}
'403': {$ref: '#/components/responses/Error403'}
'404': {$ref: '#/components/responses/Error404'}
'409': {$ref: '#/components/responses/Error409'}
'413': {$ref: '#/components/responses/Error413'}
'422': {$ref: '#/components/responses/Error422'}
'429': {$ref: '#/components/responses/Error429'}
'500': {$ref: '#/components/responses/Error500'}
'503': {$ref: '#/components/responses/Error503'}
x-error-codes: [invalid_request, unauthorized, plan_required, insufficient_scope, not_found, version_conflict, file_too_large, not_xlsx, old_xls, encrypted, damaged_zip, model_too_large, check_timeout, engine_load, interface_broken, rate_limited, upload_limit, concurrency_limited, compute_limit, internal_error, trials_paused, service_unavailable]PUT /v1/models/{model_id}/interface
Declare the model's inputs and outputs. Admin key.
Request, response and error codes
parameters:
- $ref: '#/components/parameters/ModelId'
put:
tags: [models]
operationId: putInterface
summary: Declare the model's inputs and outputs
description: |
Replaces the whole interface. It is checked against the active version.
A rejected interface leaves the previous one in force.
Uses one concurrent slot through an `interface` lease, reserves no
scenarios, and holds 55 s against the compute limit until measured
work replaces the hold. Admission can answer 429
`concurrency_limited` or `compute_limit`.
security:
- adminKey: []
requestBody:
required: true
x-max-bytes: 8388608
x-violation: request_too_large
content:
application/json:
schema: {$ref: '#/components/schemas/Interface'}
examples:
quickstart:
value:
inputs:
- {name: principal, ref: "Inputs!B2", type: number, min: 1000, max: 10000000}
- {name: rate, ref: "Inputs!B3", type: number, min: 0, max: 1}
- {name: term_months, ref: "Inputs!B4", type: number, allowed: [120, 180, 240, 360]}
outputs:
- {name: monthly_payment, ref: "Summary!B2"}
responses:
'200':
description: Interface saved.
headers:
X-Request-Id: {$ref: '#/components/headers/X-Request-Id'}
content:
application/json:
schema: {$ref: '#/components/schemas/InterfaceResult'}
'400': {$ref: '#/components/responses/Error400'}
'401': {$ref: '#/components/responses/Error401'}
'402': {$ref: '#/components/responses/Error402'}
'403': {$ref: '#/components/responses/Error403'}
'404': {$ref: '#/components/responses/Error404'}
'409': {$ref: '#/components/responses/Error409'}
'413': {$ref: '#/components/responses/Error413'}
'422': {$ref: '#/components/responses/Error422'}
'429': {$ref: '#/components/responses/Error429'}
'500': {$ref: '#/components/responses/Error500'}
'503': {$ref: '#/components/responses/Error503'}
x-error-codes: [invalid_request, unauthorized, plan_required, insufficient_scope, not_found, version_conflict, request_too_large, invalid_interface, rate_limited, concurrency_limited, compute_limit, internal_error, trials_paused, service_unavailable]GET /v1/models/{model_id}/compatibility
Compatibility report of the active version on the current engine build. Admin key.
Request, response and error codes
parameters:
- $ref: '#/components/parameters/ModelId'
get:
tags: [models]
operationId: getCompatibility
summary: Compatibility report of the active version on the current engine build
description: |
Every live version is rebuilt with a new calculation-engine build
before that build starts serving. `engine_build` names the build that
calculates this version. If a version's snapshot for that build is
missing, the answer is 503 `service_unavailable` until it is rebuilt.
If a new build cannot open the version or needs more than 98 MiB, the
version keeps calculating on the previous build until
`engine_cutoff_at`, 7 days after the new build's release. The report
and `engine_build` are then the previous build's. Builds switch at
most once per 7 days, so the previous build stays available until
the cutoff. After the cutoff,
`calculate` and `scenarios` answer 422 `model_too_large` or
`engine_load`. This lasts until a new version is accepted or a later
build opens the workbook.
security:
- adminKey: []
responses:
'200':
description: The report.
headers:
X-Request-Id: {$ref: '#/components/headers/X-Request-Id'}
content:
application/json:
schema: {$ref: '#/components/schemas/CompatibilityResult'}
'401': {$ref: '#/components/responses/Error401'}
'402': {$ref: '#/components/responses/Error402'}
'403': {$ref: '#/components/responses/Error403'}
'404': {$ref: '#/components/responses/Error404'}
'429': {$ref: '#/components/responses/Error429'}
'500': {$ref: '#/components/responses/Error500'}
'503': {$ref: '#/components/responses/Error503'}
x-error-codes: [unauthorized, plan_required, insufficient_scope, not_found, rate_limited, internal_error, trials_paused, service_unavailable]POST /v1/models/{model_id}/calculate
Calculate one scenario. Admin or calculate-only key.
Request, response and error codes
parameters:
- $ref: '#/components/parameters/ModelId'
post:
tags: [calculation]
operationId: calculate
summary: Calculate one scenario
description: |
Sets the given declared inputs, recalculates, and returns declared
outputs by name.
- Over 30 s of processor time: 422 `calculation_timeout`.
- The outputs over 4 MiB, serialized as in this response (under
their output names): 422 `response_too_large`; nothing is
billed.
- Bills 1 scenario on 200, including when an output holds a
spreadsheet error.
- Stopped by memory on an admitted model: 503 `service_unavailable`.
security:
- adminKey: []
- calcKey: []
requestBody:
required: true
x-max-bytes: 8388608
x-violation: request_too_large
content:
application/json:
schema: {$ref: '#/components/schemas/CalculateRequest'}
examples:
quickstart:
value: {inputs: {principal: 250000, rate: 0.045, term_months: 360}}
responses:
'200':
description: Calculated.
headers:
X-Request-Id: {$ref: '#/components/headers/X-Request-Id'}
content:
application/json:
schema: {$ref: '#/components/schemas/CalculateResult'}
examples:
quickstart:
value:
request_id: req_Z3J5c2R1bW15cmVxdWVzdA
model_id: mdl_bG9hbm1vZGVsZXhhbXBsZQ
version_id: ver_Zmlyc3R2ZXJzaW9uZXhhbQ
engine_build: bbb9fe99
outputs: {monthly_payment: 1365.0655645475256}
warnings: []
billed_scenarios: 1
'400': {$ref: '#/components/responses/Error400'}
'401': {$ref: '#/components/responses/Error401'}
'402': {$ref: '#/components/responses/Error402'}
'404': {$ref: '#/components/responses/Error404'}
'409': {$ref: '#/components/responses/Error409'}
'413': {$ref: '#/components/responses/Error413'}
'422': {$ref: '#/components/responses/Error422'}
'429': {$ref: '#/components/responses/Error429'}
'500': {$ref: '#/components/responses/Error500'}
'503': {$ref: '#/components/responses/Error503'}
x-error-codes: [invalid_request, unauthorized, plan_required, model_over_plan_limit, quota_exceeded, not_found, interface_required, request_too_large, invalid_input, unknown_output, calculation_timeout, engine_error, response_too_large, model_too_large, engine_load, rate_limited, concurrency_limited, compute_limit, internal_error, trials_paused, service_unavailable]POST /v1/models/{model_id}/scenarios
Calculate a batch of scenarios. Admin or calculate-only key.
Request, response and error codes
parameters:
- $ref: '#/components/parameters/ModelId'
post:
tags: [calculation]
operationId: runScenarios
summary: Calculate a batch of scenarios
description: |
Runs up to 10,000 scenarios. The whole batch is reserved against the
quota first: a batch larger than the remaining quota is refused with 402
`quota_exceeded`, with no partial run.
Rows come back in order. Each row has either `outputs` or `error`. A
scenario with a bad input becomes an error row and the batch continues.
A batch can stop early with `truncated=true`:
- `time_limit`: no new part starts after 25 s. A part that runs past
30 s of processor time is lost, but the rows of earlier parts stay.
- `response_value_limit`: 100,000 output values.
- `response_size_limit`: the next row does not fit the 8 MiB of
serialized response, or a later row's outputs exceed 4 MiB
serialized under their output names. The rows before it are
returned.
- `engine_error`, for one of two reasons:
- the engine failed on a scenario; that scenario is the last row
and `completed` includes it;
- a later part was stopped by memory; the rows of earlier parts are
returned and `completed` counts only them.
Whole-batch errors:
- the first row's outputs over 4 MiB (serialized under their
output names): 422 `response_too_large`;
- the first part stopped by memory: 503 `service_unavailable`.
To resume, send `scenarios.slice(completed)`; indexes restart at 0.
Only rows with `outputs` are billed. The worst-case response time is
about 80 s (see "Time").
security:
- adminKey: []
- calcKey: []
requestBody:
required: true
x-max-bytes: 8388608
x-violation: request_too_large
content:
application/json:
schema: {$ref: '#/components/schemas/ScenariosRequest'}
responses:
'200':
description: Batch calculated, possibly truncated.
headers:
X-Request-Id: {$ref: '#/components/headers/X-Request-Id'}
content:
application/json:
schema: {$ref: '#/components/schemas/ScenariosResult'}
'400': {$ref: '#/components/responses/Error400'}
'401': {$ref: '#/components/responses/Error401'}
'402': {$ref: '#/components/responses/Error402'}
'404': {$ref: '#/components/responses/Error404'}
'409': {$ref: '#/components/responses/Error409'}
'413': {$ref: '#/components/responses/Error413'}
'422': {$ref: '#/components/responses/Error422'}
'429': {$ref: '#/components/responses/Error429'}
'500': {$ref: '#/components/responses/Error500'}
'503': {$ref: '#/components/responses/Error503'}
x-error-codes: [invalid_request, unauthorized, plan_required, model_over_plan_limit, quota_exceeded, not_found, interface_required, request_too_large, batch_too_large, unknown_output, response_too_large, model_too_large, engine_load, rate_limited, concurrency_limited, compute_limit, internal_error, trials_paused, service_unavailable]Errors
Every JSON error has request_id and error.code / error.message. Extra fields are listed in ErrorBody below. Every response carries X-Request-Id; 429 and 503 also carry Retry-After in seconds. Error responses bill no scenarios.
| HTTP status | Code |
|---|---|
| 400 | invalid_request |
| 401 | unauthorized |
| 402 | model_limit |
| 402 | model_over_plan_limit |
| 402 | plan_required |
| 402 | quota_exceeded |
| 403 | insufficient_scope |
| 404 | not_found |
| 409 | interface_required |
| 409 | version_conflict |
| 413 | batch_too_large |
| 413 | file_too_large |
| 413 | request_too_large |
| 422 | calculation_timeout |
| 422 | check_timeout |
| 422 | damaged_zip |
| 422 | encrypted |
| 422 | engine_error |
| 422 | engine_load |
| 422 | interface_broken |
| 422 | invalid_input |
| 422 | invalid_interface |
| 422 | model_too_large |
| 422 | not_xlsx |
| 422 | old_xls |
| 422 | response_too_large |
| 422 | unknown_output |
| 429 | compute_limit |
| 429 | concurrency_limited |
| 429 | rate_limited |
| 429 | upload_limit |
| 500 | internal_error |
| 503 | service_unavailable |
| 503 | trials_paused |
Fields and schemas
The definitions below are linked from each operation. Required fields, types, constraints and response fields come from the API contract.
Retry-After
Retry-After:
description: Seconds until a retry can succeed.
schema: {type: integer, minimum: 1}X-Request-Id
X-Request-Id:
description: Unique per request; equals `request_id` in /v1 bodies.
schema: {$ref: '#/components/schemas/RequestId'}ModelId
ModelId:
name: model_id
in: path
required: true
description: '`mdl_` + 22 base64url chars. Unknown, malformed and other accounts'' ids all give 404 `not_found`.'
schema: {type: string, examples: [mdl_bG9hbm1vZGVsZXhhbXBsZQ]}ApiName
ApiName:
type: string
pattern: '^[A-Za-z_][A-Za-z0-9_.-]{0,63}$'
description: Interface name. Cell addresses and workbook names are never accepted in place of it.BilledScenarios
BilledScenarios:
type: integer
minimum: 0
description: |
Scenarios this response bills: 1 for calculate, the rows with
`outputs` for scenarios. Usage is committed after the platform has
read the whole body (info "Billing"). A body cut off before that is
not billed, and a failure after it can leave the response unbilled.
It is never billed twice.CalculateRequest
CalculateRequest:
type: object
additionalProperties: false
properties:
inputs: {$ref: '#/components/schemas/InputMap'}
outputs: {$ref: '#/components/schemas/OutputSelection'}CalculateResult
CalculateResult:
allOf:
- $ref: '#/components/schemas/WithRequestId'
- type: object
required: [model_id, version_id, engine_build, outputs, warnings, billed_scenarios]
properties:
model_id: {type: string}
version_id: {type: string, description: The version that calculated (the one active when the request started).}
engine_build: {$ref: '#/components/schemas/EngineBuild'}
outputs:
type: object
additionalProperties: {$ref: '#/components/schemas/OutputValue'}
warnings: {$ref: '#/components/schemas/Warnings'}
billed_scenarios: {$ref: '#/components/schemas/BilledScenarios'}CellRef
CellRef:
type: string
minLength: 1
maxLength: 255
description: 'A cell (`Inputs!B2`, `''Loan Inputs''!B2`), a range for outputs (`Summary!B2:B6`), or a defined name, workbook-wide or sheet-scoped (`Principal`, `''Inputs''!Principal`). Defined names survive moved cells in new versions.'CellValue
CellValue:
oneOf:
- {type: number}
- {type: string}
- {type: boolean}
- {type: 'null'}
- $ref: '#/components/schemas/ErrorValue'CheckValue
CheckValue:
type: object
required: [k]
description: 'Value in a mismatch: k = n (number), s (string), b (boolean), e (error), empty.'
properties:
k: {enum: [n, s, b, e, empty]}
v: {}CompatibilityResult
CompatibilityResult:
allOf:
- $ref: '#/components/schemas/WithRequestId'
- type: object
required: [model_id, version_id, engine_build, engine_cutoff_at, engine_cutoff_reason, report]
properties:
model_id: {type: string}
version_id: {type: string}
engine_build: {$ref: '#/components/schemas/EngineBuild'}
engine_cutoff_at: {$ref: '#/components/schemas/EngineCutoff'}
engine_cutoff_reason: {$ref: '#/components/schemas/EngineCutoffReason'}
report: {$ref: '#/components/schemas/Report'}Deleted
Deleted:
type: object
required: [model_id, deleted]
properties:
request_id: {$ref: '#/components/schemas/RequestId'}
model_id: {type: string}
deleted: {const: true}EngineBuild
EngineBuild:
type: string
pattern: '^[0-9a-f]{8,40}$'
description: Build of the calculation engine that produced a report or a result.EngineCutoff
EngineCutoff:
type: [string, 'null']
format: date-time
description: 'Set when the current engine build cannot open this version or needs more than 98 MiB. Until this time the version calculates on the previous build; afterwards calculate and scenarios answer 422 model_too_large or engine_load. null otherwise.'EngineCutoffReason
EngineCutoffReason:
enum: [model_too_large, engine_load, null]
description: 'Set with engine_cutoff_at: the 422 code calculate and scenarios answer after the cutoff. null otherwise.'ErrorBody
ErrorBody:
type: object
required: [error]
properties:
request_id:
$ref: '#/components/schemas/RequestId'
description: Present on every /v1 response.
error:
type: object
required: [code, message]
properties:
code: {$ref: '#/components/schemas/ErrorCode'}
message: {type: string, description: Human-readable; no server paths, stack traces or engine text.}
billing_url: {type: string, format: uri, const: 'https://app.workbookapi.com/billing', description: Every 402.}
remaining: {type: integer, minimum: 0, description: 'quota_exceeded: max(0, quota − used − reserved), where quota is the admission quota — the lowest of the period quota and the quota of each plan change still pending at Paddle.'}
resets_at: {type: string, format: date-time, description: 'quota_exceeded, compute_limit, usage_above_target_quota: end of the current usage period (for a trial: the trial end, after which access ends); upload_limit: next 00:00 UTC.'}
used: {type: integer, minimum: 0, description: 'usage_above_target_quota: scenarios of this period, billed plus reserved by calls in progress.'}
quota: {type: integer, minimum: 0, description: 'usage_above_target_quota: the refusal bound — the lower of the requested plan''s quota and this period''s quota after the change, computed for the moment of the request.'}
required_mib: {type: [number, 'null'], description: 'model_too_large: measured calculation memory; null when the workbook was refused before it could be measured (pre-screen estimate, workbook text over the string limit).'}
limit_mib: {const: 98, description: model_too_large.}
limit_bytes: {type: integer, description: 'file_too_large: 10485760; request_too_large: 8388608; response_too_large: 4194304.'}
limit: {type: integer, description: 'batch_too_large: 10000.'}
details:
type: array
description: invalid_request, invalid_interface, interface_broken, invalid_input, unknown_output.
items: {$ref: '#/components/schemas/Problem'}
report:
$ref: '#/components/schemas/Report'
description: engine_load on upload and new version.ErrorCode
ErrorCode:
type: string
enum:
- batch_too_large
- calculation_timeout
- check_timeout
- compute_limit
- concurrency_limited
- damaged_zip
- encrypted
- engine_error
- engine_load
- file_too_large
- insufficient_scope
- interface_broken
- interface_required
- internal_error
- invalid_input
- invalid_interface
- invalid_request
- model_limit
- model_over_plan_limit
- model_too_large
- not_found
- not_xlsx
- old_xls
- plan_required
- quota_exceeded
- rate_limited
- request_too_large
- response_too_large
- service_unavailable
- trials_paused
- unauthorized
- unknown_output
- upload_limit
- version_conflictErrorValue
ErrorValue:
type: object
required: [error]
additionalProperties: false
properties:
error: {type: string, examples: ['#N/A', '#DIV/0!', '#VALUE!', '#REF!', '#NAME?', '#NUM!', '#NULL!', '#SPILL!', '#CALC!']}InputMap
InputMap:
type: object
additionalProperties: {$ref: '#/components/schemas/Scalar'}
description: Declared input name → value. Undeclared name, wrong type, outside min/max or not in allowed → `invalid_input`.Interface
Interface:
type: object
required: [inputs, outputs]
additionalProperties: false
description: Names are unique across inputs and outputs; two inputs may not share a cell. At most 4,096 inputs, 100 outputs and 10,000 output cells per scenario — over a limit is 422 `invalid_interface`, not 400.
properties:
inputs:
type: array
maxItems: 4096
x-violation: invalid_interface
items: {$ref: '#/components/schemas/InterfaceInput'}
outputs:
type: array
minItems: 1
maxItems: 100
x-violation: invalid_interface
items: {$ref: '#/components/schemas/InterfaceOutput'}
x-limits:
output_cells_per_scenario: 10000InterfaceInput
InterfaceInput:
type: object
required: [name, ref, type]
additionalProperties: false
description: |
The ref must be one constant or blank cell, or a name for one such cell.
The declared type must match a non-blank cell's value.
`min`/`max` apply to `number` only and cannot be combined with
`allowed`; `allowed` values must be of the declared type.
Breaking a rule gives 422 `invalid_interface`.
properties:
name: {$ref: '#/components/schemas/ApiName'}
ref: {$ref: '#/components/schemas/CellRef'}
type: {enum: [number, string, boolean]}
min: {type: number}
max: {type: number}
allowed:
type: array
minItems: 1
items: {$ref: '#/components/schemas/Scalar'}InterfaceOutput
InterfaceOutput:
type: object
required: [name, ref]
additionalProperties: false
properties:
name: {$ref: '#/components/schemas/ApiName'}
ref: {$ref: '#/components/schemas/CellRef'}InterfaceResult
InterfaceResult:
allOf:
- $ref: '#/components/schemas/WithRequestId'
- type: object
required: [model_id, version_id, interface]
properties:
model_id: {type: string}
version_id: {type: string, description: The active version the interface was checked against.}
interface: {$ref: '#/components/schemas/Interface'}Model
Model:
type: object
required: [model_id, file_name, created_at, state, active_version, interface]
properties:
model_id: {type: string}
file_name: {type: string, maxLength: 255, description: File name of the active version.}
created_at: {type: string, format: date-time}
state: {$ref: '#/components/schemas/ModelState'}
active_version:
type: object
required: [version_id, created_at, engine_build, engine_cutoff_at, engine_cutoff_reason]
properties:
version_id: {type: string}
created_at: {type: string, format: date-time}
engine_build: {$ref: '#/components/schemas/EngineBuild'}
engine_cutoff_at: {$ref: '#/components/schemas/EngineCutoff'}
engine_cutoff_reason: {$ref: '#/components/schemas/EngineCutoffReason'}
interface:
oneOf:
- $ref: '#/components/schemas/Interface'
- {type: 'null'}ModelState
ModelState:
type: string
enum: [callable, no_interface, over_plan_limit, plan_required, past_engine_cutoff]
description: |
The first failing check of the /v1 order, as calculate would meet it:
- plan_required: dashboard only, the account has no access (402);
- over_plan_limit: 402 model_over_plan_limit;
- no_interface: 409 interface_required;
- past_engine_cutoff: the active version is past engine_cutoff_at,
so calculate answers 422 with engine_cutoff_reason;
- callable: none of these.
A version before its cutoff stays callable; engine_cutoff_at shows
the date.OutputSelection
OutputSelection:
type: array
minItems: 1
maxItems: 100
uniqueItems: true
items: {type: string}
description: Declared output names; default all declared outputs. An undeclared name or a cell address is 422 `unknown_output`.OutputValue
OutputValue:
description: A scalar for a single cell; an array of rows for a range, a multi-cell name or a dynamic array.
oneOf:
- $ref: '#/components/schemas/CellValue'
- type: array
items:
type: array
items: {$ref: '#/components/schemas/CellValue'}Problem
Problem:
type: object
required: [problem]
properties:
element: {enum: [input, output]}
name: {type: string, description: API name as sent by the client.}
field: {type: string, description: 'invalid_request: JSON pointer of the offending field.'}
problem:
type: string
enum:
- invalid_json
- schema
- content_type
- not_declared
- type
- out_of_range
- not_allowed
- formula
- multi_cell
- type_mismatch
- not_found
- duplicate_name
- duplicate_cell
- invalid_bounds
- too_many_inputs
- too_many_outputs
- too_many_cells
expected: {type: string, description: 'type / type_mismatch: the declared type.'}Report
Report:
type: object
required: [ok, engine_build]
description: |
Compatibility check, same function as the browser check on the site.
The site's camelCase keys map 1:1 to the snake_case keys here, for
example `matchedStrict` → `matched_strict` and `frozenUdf` →
`frozen_udf`. Feature keys map the same way.
With `ok=false` the check was incomplete (`reason`). The model is still
accepted, and the counts may be missing.
properties:
ok: {type: boolean}
reason:
enum: [not_saved_by_excel, no_checkable_formula_cells, no_results_saved_by_excel, engine_load]
description: Only when ok=false.
message: {type: string, description: Explanation of `reason`, as on the site. Never contains engine text.}
cells: {type: integer, minimum: 0, description: Formula cells compared.}
matched: {type: integer, minimum: 0, description: Relative tolerance 1e-6.}
matched_strict: {type: integer, minimum: 0, description: Relative tolerance 1e-9.}
mismatches:
type: array
maxItems: 200
items:
type: object
required: [cell, formula, expected, got, cause]
properties:
cell: {type: string, examples: ['Summary!B7']}
formula: {type: string}
expected: {$ref: '#/components/schemas/CheckValue'}
got: {$ref: '#/components/schemas/CheckValue'}
cause: {enum: [named_formula, unsupported_function, table_reference, other]}
mismatches_truncated: {type: boolean}
features:
type: object
additionalProperties: false
properties:
named_formulas: {type: boolean}
vba: {type: boolean}
external_links: {type: boolean}
external_data: {type: boolean, description: External data or Power Query connections.}
pivot_tables: {type: boolean}
strict_ooxml: {type: boolean}
iterative_calc: {type: boolean}
date1904: {type: boolean}
data_tables: {type: boolean}
excluded:
type: object
description: Skipped formula cells by reason (for example volatile, no_cached_value, external_ref, data_table, frozen_udf).
additionalProperties: {type: integer, minimum: 1}
frozen_udf:
type: object
required: [count, functions]
properties:
count: {type: integer, minimum: 0}
functions: {type: array, items: {type: string}}
calc_mode: {enum: [auto, manual, autoNoTable]}
date_system: {enum: ['1900', '1904']}
load_ms: {type: number}
eval_ms: {type: number}
engine_build: {$ref: '#/components/schemas/EngineBuild'}RequestId
RequestId:
type: string
pattern: '^req_[A-Za-z0-9_-]{22}$'RowError
RowError:
type: object
required: [code, message]
properties:
code: {enum: [invalid_input, engine_error]}
message: {type: string}
details:
type: array
items: {$ref: '#/components/schemas/Problem'}Scalar
Scalar:
oneOf:
- {type: number}
- {type: string}
- {type: boolean}ScenarioRow
ScenarioRow:
oneOf:
- type: object
required: [index, outputs]
additionalProperties: false
properties:
index: {type: integer, minimum: 0}
outputs:
type: object
additionalProperties: {$ref: '#/components/schemas/OutputValue'}
- type: object
required: [index, error]
additionalProperties: false
properties:
index: {type: integer, minimum: 0}
error: {$ref: '#/components/schemas/RowError'}ScenariosRequest
ScenariosRequest:
type: object
required: [scenarios]
additionalProperties: false
properties:
scenarios:
type: array
minItems: 1
maxItems: 10000
x-violation: batch_too_large
items:
type: object
additionalProperties: false
properties:
inputs: {$ref: '#/components/schemas/InputMap'}
outputs: {$ref: '#/components/schemas/OutputSelection'}ScenariosResult
ScenariosResult:
allOf:
- $ref: '#/components/schemas/WithRequestId'
- type: object
required: [model_id, version_id, engine_build, rows, requested, completed, truncated, truncation_reason, billed_scenarios, warnings]
properties:
model_id: {type: string}
version_id: {type: string}
engine_build: {$ref: '#/components/schemas/EngineBuild'}
rows:
type: array
maxItems: 10000
items: {$ref: '#/components/schemas/ScenarioRow'}
requested: {type: integer, minimum: 1, maximum: 10000}
completed: {type: integer, minimum: 0, description: Rows returned, error rows included. Resume with scenarios.slice(completed).}
truncated: {type: boolean}
truncation_reason:
enum: [time_limit, response_value_limit, response_size_limit, engine_error, null]
billed_scenarios: {$ref: '#/components/schemas/BilledScenarios'}
warnings: {$ref: '#/components/schemas/Warnings'}UploadResult
UploadResult:
allOf:
- $ref: '#/components/schemas/WithRequestId'
- type: object
required: [model_id, version_id, file_name, report]
properties:
model_id: {type: string}
version_id: {type: string}
file_name: {type: string}
report: {$ref: '#/components/schemas/Report'}VersionResult
VersionResult:
allOf:
- $ref: '#/components/schemas/UploadResult'
- type: object
required: [previous_version_id, changed_inputs]
properties:
previous_version_id: {type: string}
changed_inputs:
type: [array, 'null']
items: {$ref: '#/components/schemas/ApiName'}
description: Declared inputs whose saved value differs from the previous version. Empty without an interface. null when the comparison could not run.Warnings
Warnings:
type: array
items: {type: string}
description: Workbook-level warnings (mismatch count, unsupported features, skipped cells, manual mode) and add-in/macro reach naming affected outputs by API name. Never sheet names, cell addresses, formulas or workbook function names.WithRequestId
WithRequestId:
type: object
required: [request_id]
properties:
request_id: {$ref: '#/components/schemas/RequestId'}