Developer Docs

Integrate the OpCut API in one minute

REST + Bearer Token, with two nesting engines: rectangular and irregular-shape. The rectangular engine offers sync / async modes; the irregular engine offers sync solving. Field and spec differences are listed explicitly below.

1 · Rectangular nesting API · one-minute integration (curl)

The rectangular endpoint follows opcut-io 1.0 · rect profile. Requests may carry spec: "opcut-io" and spec_version: "1.0" (defaults to 1.0 when omitted); responses always echo them. Rectangular responses keep plan[].parts and also provide the equivalent sheets[].placements; the request field parts is a compatibility alias of the legacy order.

Sign in to the console and create an API Token (prefix opcut_live_; the plaintext is shown only once at creation), then call the nesting endpoint with the Authorization: Bearer header:

# 1) Submit a nesting job (sync mode, result within the 3-second budget)
curl -X POST https://opcut.net/api/v1/optimize \\
  -H "Authorization: Bearer opcut_live_xxxx" \\
  -H "Content-Type: application/json" \\
  -d '{
    "spec": "opcut-io", "spec_version": "1.0",
    "stock":  [{ "id": "ST-1", "w": 2440, "h": 1830, "count": 1 }],
    "order":  [{ "id": "P0", "w": 600, "h": 600, "qty": 2, "grain": "none" }],
    "time_budget_s": 3.0
  }'

# 2) Response
{
  "spec": "opcut-io", "spec_version": "1.0",
  "job_id": "job_xxx",  "status": "solved",
  "plan":   [{ "stock_id": "ST-1", "utilization": 0.88,
               "parts": [{ "id": "P0", "x": 0, "y": 0, "w": 600, "h": 600, "rotated": false }] }],
  "metrics": { },
  "billing": { "call_fee": 0.05, "order_fee": 1.00, "total": 1.05 },
  "cut_model": null
}

Request fields: stock/order/time_budget_s; response fields: plan/metrics/billing/cut_model. status ∈ solved | unsolvable — unsolvable now means truly unsolvable only (a part cannot fit on any raw sheet; no order fee is charged). Insufficient stock count is no longer unsolvable: the pre-check returns 422 INSUFFICIENT_STOCK_CAPACITY (zero charge, the error carries the estimated required sheet count). Grain parts with grain: "x"|"y" must not be rotated.

2 · Python example

import requests

API, TOKEN = "https://opcut.net/api", "opcut_live_xxxx"

resp = requests.post(
    f"{API}/v1/optimize",
    headers={"Authorization": f"Bearer {TOKEN}"},
    json={
        "stock": [{"id": "ST-1", "w": 2440, "h": 1830, "count": 1}],
        "order": [{"id": "P0", "w": 600, "h": 600, "qty": 2, "grain": "none"}],
        "time_budget_s": 3.0,
    },
    timeout=10,
)
resp.raise_for_status()
plan = resp.json()
print("cut rate:", plan["metrics"])          # metrics contains utilization and other evaluation fields
print("cost of this call:", plan["billing"]["total"])

# Async mode (large batches): submit then poll; Idempotency-Key is required (24h retry window)
job = requests.post(
    f"{API}/v1/jobs",
    headers={"Authorization": f"Bearer {TOKEN}",
             "Idempotency-Key": "order-20261001-001"},   # required
    json={"stock": [{"id": "ST-1", "w": 2440, "h": 1830, "count": 30}],
          "order": [{"id": "P0", "w": 500, "h": 500, "qty": 40, "grain": "none"}],
          "time_budget_s": 3.0},
    timeout=10,
).json()
print(job["status_url"])                  # → /v1/jobs/{job_id}, free polling

The jobs submit response contains job_id/status/estimate_sheets/freeze_amount/call_fee/status_url; the polling response contains sheets_used/result/error.

3 · Node example

const API = "https://opcut.net/api";
const TOKEN = process.env.OPCUT_TOKEN;    // opcut_live_xxxx

const resp = await fetch(\`\${API}/v1/optimize\`, {
  method: "POST",
  headers: { "Authorization": \`Bearer \${TOKEN}\`, "Content-Type": "application/json" },
  body: JSON.stringify({
    stock: [{ id: "ST-1", w: 2440, h: 1830, count: 1 }],
    order: [{ id: "P0", w: 600, h: 600, qty: 2, grain: "none" }],
    time_budget_s: 3.0,
  }),
});
if (resp.status === 402) throw new Error("Insufficient balance: top up in the console");
if (!resp.ok)   throw new Error(\`HTTP \${resp.status}: \${JSON.stringify(await resp.json())}\`);
const plan = await resp.json();
console.log("billing:", plan.billing);    // { call_fee, order_fee, total }

Error envelopes use the unified shape {error:{code,message,details}}; the 402 semantics are in the error-code table.

4 · Bearer Token acquisition and management

  • Get one: sign in / register with your phone number and SMS code (new numbers register automatically and receive trial credit) → create a token under Console · API tokens — the plaintext is shown only once at creation, the server stores only a hash, so a lost token can only be revoked and re-created. In dev mode (OPCUT_DEV_MODE=1, off by default) sign-in skips the SMS code (the response header x-opcut-dev-mode: 1 marks it); production always validates.
  • Calling: all /v1/* endpoints authenticate with Authorization: Bearer opcut_live_<40 hex chars> (the opcut_live_ prefix).
  • Managing: list / revoke any time in the console (revocation takes effect immediately; in-flight old tokens get 401 right away); create multiple tokens to isolate environments.
  • Rate limit: 60 requests/minute per token (the unified MVP tier); exceeding it returns 429 + a Retry-After header.

5 · Error-code table

Error envelopes are uniformly {"error": {"code", "message", "details"}}. The table lists common business error codes; irregular-shape specifics appear in its endpoint section below.

API error-code table
HTTPcodeMeaning and handling
400INVALID_REQUESTInvalid request body/parameters (missing Idempotency-Key, bad month format, etc.) — fix per message and retry
400AMOUNT_OUT_OF_RANGERecharge amount out of range (¥100–¥50,000, engineering defaults)
400NO_BILLABLE_CONSUMPTIONThe invoice month has no billable consumption (invoices follow actual spend)
401INVALID_TOKENToken missing / invalid / revoked — re-create in the console
401INVALID_CODE etc.SMS code wrong / expired / voided (sign-in flow)
402INSUFFICIENT_BALANCEInsufficient balance (details carries balance and required amount) — recharge then retry; retrying with the same idempotency key is not billed twice
402TOKEN_SUSPENDEDAccount overdue (balance < 0); all tokens are suspended automatically — they resume once a recharge is credited
403SCOPE_DENIEDThe token lacks the required scope (details lists required_scope) — re-issue it
403FORBIDDEN / ORG_MISMATCHUnauthorized access to another organization's resources (data is strictly isolated by org)
404NOT_FOUNDResource does not exist (wrong job or order number)
409IDEMPOTENCY_KEY_REUSEDThe idempotency key was used with a different request body — change the key and retry (resending the same body replays the original result instead of 409)
409ORDER_NOT_PENDING / ORDER_NOT_PAYABLE / CHANNEL_MISMATCHRecharge order status/channel mismatch (already paid cannot be paid again, wrong channel, etc.; re-cancelling a closed order is an idempotent 200 and not this error)
409TOKEN_LIMIT_EXCEEDEDActive tokens reached the per-account cap (details.limit is the cap; revoking a token frees the slot immediately for a new one)
422OPCUT_SYNC_LIMIT_EXCEEDED / INSUFFICIENT_STOCK_CAPACITY / ENGINE_LAYOUT_UNFITSync workload exceeds the limit (max 1,000 parts per call; use the async endpoint POST /v1/jobs for bigger orders); or stock capacity insufficient (parts fit individually but not enough sheets — the error carries the estimated required / available sheet counts; add raw sheets or switch to async review); or the layout missed the cut-rate process gate (the engine solved normally but the layout failed the process gate — sync rejects with guidance to adjust part sizes or split orders; an async job with the same parameters ends failed with the engine_layout_unfit error prefix; neither path is charged and sync freezes are fully refunded). The first two are rejected before billing pre-check with zero charge and zero ledger entries
422SPEC_UNSUPPORTEDUnsupported spec name or major version — use opcut-io / 1.0; this error is not charged
429RATE_LIMITEDAbove the 60 requests/minute per-token limit (Retry-After header included) — back off per the header
429SMS_COOLDOWNSMS code send cooldown (60s, Retry-After included)
500ENGINE_ERROR / INTERNALServer-side internal error (engine failures are not charged, freezes are refunded along the original path) — safe to retry (note: layouts that miss the process gate are now routed to 422 ENGINE_LAYOUT_UNFIT and no longer land here)

501 (channel not opened) returns a placeholder notice and has no separate business code.

6 · Rate limiting and billing

Rate limiting and billing rules at a glance
ItemRule
Rate limit60 requests/minute per token; query-type calls (job status, usage) are free but also consume the quota; exceeding it returns 429 — back off per the Retry-After header
Call fee¥0.05/call, charged only on business success
Order feeTiered by sheets actually used: ≤20 sheets ¥1 / ≤100 sheets ¥2 / >100 sheets ¥3; charged only on solved, not on unsolvable (truly unsolvable); insufficient stock capacity is rejected at pre-check with 422 and zero charge
Balance pre-checkMaximum real charge per call ¥3.05 (call fee + top order-fee tier); a lower balance returns 402
Overdue suspensionWhen the balance < 0 all tokens are suspended automatically; they resume once a recharge is credited
Idempotent retryThe async endpoint requires Idempotency-Key (24h window); optional on sync — same key + same body returns the original result without double charging, a different key is an independent request
Big jobs: use asyncFor extra-large tasks (tens of thousands of parts) solving time will clearly exceed time_budget_s (a single layout computation cannot be interrupted) — use POST /v1/jobs instead: it accepts and returns immediately, freezes and settles the balance, and supports polling and webhook notification; the budget promise of the regular sync endpoint is unaffected
Billing and invoicesThe console "Billing & Recharge" page offers per-entry ledger, CSV export and monthly invoice requests (invoices follow actual spend; recharges are not invoiced); when a CSV exceeds 10000 rows and is truncated, the response carries the X-Truncated header (the filtered total row count)

7 · Input/output spec opcut-io 1.0

One JSON input/output specification with two engine profiles: rect (rectangular guillotine cuts) and nest (irregular true shapes). Sizes and coordinates are uniformly in millimetres (mm); optional parameters have defaults. Read / download the spec (Markdown); this page documents how to call the current endpoints.

  • Version handshake: requests carry top-level spec and spec_version; when omitted they default to opcut-io and 1.0. Responses always echo them. Optional engine selects the endpoint profile: rect for rectangular, nest for irregular shapes.
  • Tolerant read, strict write: unknown request fields are ignored and reported in the response warnings; clients must tolerate new response fields. Read the warnings — do not mistake an ignored parameter for an effective one.
  • Geometry naming: irregular outer contours use outline; the legacy request field poly is permanently accepted as an equivalent. Passing both requires exact equality, otherwise 422; during the transition responses output outline/poly together with a deprecation warning.
Version and compatibility discipline
ItemCompatibility rule
MAJOR versionRemoved fields or changed semantics start a new major version; the old one stays available for at least 6 months. Unknown spec names or unsupported majors return 422 SPEC_UNSUPPORTED.
MINOR versionAdds optional fields or enum values only; newer fields surface through warnings. Old clients keep reading known fields.
PATCH revisionCorrections only; the interface contract is unchanged.
Field deprecationDocs and response warnings stay at least one minor-version cycle; deletion happens only in the next major; the request poly alias is permanently accepted.
Engine upgradesAlgorithm or precision upgrades do not change the spec version. The spec carries the algorithm version in engine.version; current endpoint field differences are listed below — spec_version cannot substitute for the algorithm version.
DXF inner drawingsCurrently supported: POST /io/dxf parsing, parts[].inner.dxf_ref references, polyline / true-arc dual formats and SVG download; DXF layout export remains reserved for later.
Spec error codes mapped to current endpoints
HTTPSpec error codeHandling / current endpoint
401AUTH_REQUIREDCheck authentication. API tokens currently return INVALID_TOKEN; sessions may return UNAUTHORIZED.
403SCOPE_DENIEDUse a token that includes the optimize:sync scope.
402INSUFFICIENT_BALANCERecharge and retry; overdue suspension is separate, see TOKEN_SUSPENDED.
422VALIDATION_FAILEDFix the input per the Chinese explanations in error.details; current parameter errors use finer codes such as INVALID_REQUEST.
422SPEC_UNSUPPORTEDSwitch to spec: "opcut-io", spec_version: "1.0".
429RATE_LIMITEDBack off per Retry-After; irregular-shape busy concurrency separately uses NESTING_BUSY.
504SOLVE_TIMEOUTReduce parts or adjust the budget; irregular shapes currently return NESTING_TIMEOUT and are not charged.

Compatibility notes: existing response structures are kept so current clients do not break. Rectangular keeps plan/parts and adds sheets/placements; irregular still returns sheets[].pieces and metrics.poly_util/area_lb. The current irregular response exposes elapsed time and constructor as seconds/constructor and does not yet provide the spec's engine object; the full difference list is recorded in the engineering docs.

DXF inner drawings · inner holes and marks for rectangular parts

Rectangular parts are still nested by width and height, and each part kind may carry an inner drawing; inner holes and mark lines translate and rotate with the part and appear in the online layout and the downloaded SVG. Every part row on the online trial page can upload one. Download the example DXF: outer rectangle + circular hole + slot hole + MARK lines.

Upload and drawing conventions

  • Upload: POST /io/dxf with the DXF R14 / AutoCAD 2000 file in the multipart file field; a raw request body (Content-Type: application/dxf) is also accepted. Parsing is free; one file ≤2 MiB, ≤512 entities after BLOCK / INSERT expansion.
  • Units and curves: sizes and coordinates are uniformly in mm — convert your drawing to millimetres first. Supported: CIRCLE, ARC, ELLIPSE, LWPOLYLINE, POLYLINE and BLOCK / INSERT expansion. Default output_curve: "polyline" flattens curves with a sag error ≤0.1 mm (the maximum deviation between the curve and its polyline); "arc" keeps true arcs. Ellipses are still polyline-approximated; arc mode represents ellipses with line segments.
  • Outer contour and automatic sizing: keep one separate outermost closed contour; its bounding-box lower-left corner is normalized to zero. The upload response's envelope: {w,h} is that outer contour's bounding box size (mm) — inner mark lines are not counted. On the trial page, uploading fills the part width/height automatically. The outer contour is not redrawn as an inner cut line.
  • Uniform outward margin: parts[].inner.margin_mm is the millimetres added on each side, a finite number ≥0, defaulting to 0 when omitted. When provided explicitly, the server recomputes the part size as (envelope.w + 2 × margin_mm) × (envelope.h + 2 × margin_mm). For example, an outline of 120 × 80 mm with a 5 mm margin yields a 130 × 90 mm part. On the trial page, editing the margin updates width/height in real time and the solve uses that size; the inner graphics translate by the same margin and rotate with the whole part. When legacy API requests omit the field, the manually supplied size and the envelope-overflow check are kept.
  • Layer-name convention: put inner cutting contours on the CUT_INNER layer and marks on the MARK layer. Closed rings are recognized as holes and nested islands by containment, keeping the depth level; open lines and explicit MARKs become mark lines, and unknown layers default to MARK. CUT_INNER renders as thin solid lines, MARK as grey dashed lines.
  • Reading hints: the parse response contains dxf_ref / envelope / entities / warnings, where entities counts outer contour, holes and marks; the legacy inner / bounds / entity_count fields are kept for compatibility. Check warnings before solving — unsupported entities are clearly rejected or flagged. Unknown or expired dxf_ref returns 404; parse failures, envelope overflow, invalid output formats or bad margins return 422 with Chinese messages.

Two-step integration · solve with an uploaded drawing

Per spec §8.1, pass {"dxf_ref":"uploaded-id","layers":["CUT_INNER","MARK"],"output_curve":"polyline","margin_mm":5} inside parts[].inner (the legacy order[].inner is accepted too). layers may be omitted (default: all); as an array it outputs only the selected layers. output_curve defaults to polyline and can switch to arc; margin_mm defaults to 0. Parts without a drawing need no inner. The inline snapshot in the upload response is kept only for compatibility; new integrations use dxf_ref.

curl --fail-with-body -X POST https://opcut.net/api/io/dxf \\
  -F "file=@inner-golden.dxf;type=application/dxf"
import os
import time
import requests

api = "https://opcut.net/api"
headers = {"Authorization": "Bearer " + os.environ["OPCUT_TOKEN"]}
with open("inner-golden.dxf", "rb") as drawing:
    uploaded = requests.post(api + "/io/dxf",
                             files={"file": ("inner-golden.dxf", drawing, "application/dxf")}, timeout=20)
uploaded.raise_for_status()
snapshot = uploaded.json()
print("drawing notes:", snapshot["warnings"])
margin_mm = 5  # extend 5 mm on every side; set to 0 when no margin is needed

response = requests.post(api + "/v1/optimize", headers=headers, json={
    "stock": [{"id": "S1", "w": 2440, "h": 1830, "count": 1}],
    "parts": [{"id": "P1", "w": snapshot["envelope"]["w"] + 2 * margin_mm,
               "h": snapshot["envelope"]["h"] + 2 * margin_mm, "qty": 2,
               "inner": {"dxf_ref": snapshot["dxf_ref"],
                         "layers": ["CUT_INNER", "MARK"],
                         "output_curve": "polyline", "margin_mm": margin_mm}}],
    "time_budget_s": 3
}, timeout=30)
response.raise_for_status()
result = response.json()
job_id = result["job_id"]
if response.status_code == 202:
    for _ in range(180):
        status = requests.get(api + result["status_url"], headers=headers, timeout=20)
        status.raise_for_status()
        job = status.json()
        if job["status"] == "succeeded":
            break
        if job["status"] == "failed":
            raise RuntimeError(job["error"])
        time.sleep(1)
    else:
        raise TimeoutError("Query the result later with the job id")
svg = requests.get(api + "/v1/jobs/" + job_id + "/svg?sheet=0",
                   headers=headers, timeout=20)
svg.raise_for_status()
with open("sheet-1.svg", "wb") as output:
    output.write(svg.content)

For parts with drawings, both plan[].parts[].inner_graphics and sheets[].placements[].inner_graphics return layers and geometry. Polyline mode provides points; arc mode provides segments — lines as {type:"line",p1:[x,y],p2:[x,y]}, arcs as {type:"arc",cx,cy,r,a1_deg,a2_deg,ccw}. A full circle is one arc (start and end angles equal or 360° apart), drawn in SVG as two half-circle path A segments.

In both formats the points and circle centres are already transformed to absolute millimetre coordinates on the raw sheet; draw scaled along the same axes without re-applying rotation, translation or y-axis flipping. In polyline mode, close each shape by joining the last point to the first. Inner graphics are for display and exchange only — they take no part in nesting, utilization or kerf computation.

Free download: GET /v1/jobs/{job_id}/svg?sheet=0, sheet starts at 0. It follows the job's read permission — pass the API Token used for the solve (or a token / console session with read rights for that organization's jobs). The SVG always contains the inner graphics; online layer toggles do not change the downloaded content.

Third-party file import · supported formats

Import layout files exported by supported third-party nesting software. The two currently verified formats are .EDI and .OPT exports; sizes and coordinates are normalized to millimetres (mm); inch-based and further vendor formats are not implemented yet. Open the file import page →

Use POST /v1/interop/import, uploading via the multipart file field, one file ≤2 MB. The server detects the format from content and validates the extension, with a cap of 500 raw-sheet records and 5000 parts. Import and preview are free; file errors return a Chinese error.message plus error.code — fix the file per the hint and retry.

curl --fail-with-body -X POST https://opcut.net/api/v1/interop/import \\
  -F "file=@my-order.EDI"

The response contains format / filename / units / records. Each raw-sheet record provides id / stock / parts / payload; stock is the raw-sheet width/height, parts[].poly is the closed part polygon, and source arcs are sampled into polylines at 24 steps per full circle. The preview shows the raw-sheet size and each part's local contour separately and switches between multi-sheet records; do not treat local coordinates as positions in the source layout.

Clicking "Convert into my nesting order" prefills the selected sheet's payload into the irregular-shape trial page. Orders above 60 kinds or 300 parts per run are split automatically with all quantities kept, ready for per-order verification and submission. Conversion never solves on its own; submitted solves follow the existing billing rules with trial credit used first.

The input spec gains the optional meta.import_source (order level and part level), storing format / filename / record_id / part_id provenance; solve audits tag the import source (source=interop:<format>). Keep the original file so contours, units and process parameters can be cross-checked.

8 · Irregular-shape nesting engine API

POST https://opcut.net/api/nesting/optimize submits true-polygon nesting synchronously. Use a console API Token with the Authorization: Bearer $OPCUT_TOKEN header; the token needs the optimize:sync scope, and the per-token 60 requests/minute limit applies. Signed-in console sessions are accepted too.

Request fields

Irregular-shape request fields
FieldRequirement / defaultNotes
spec / spec_versionoptional, opcut-io / 1.0Echoed in the response; engine: "nest" optional.
stockrequired, {w,h}Positive; the single raw-sheet size in mm; the server computes the required sheet count.
partsrequired, 1–60 kinds, ≤300 parts totalEach id is a unique part number (1–80 chars); qty is an integer 1–999.
parts[].outlinerequired outer contour; legacy name poly also accepted[[x,y],…], 3–512 distinct vertices; the closing point does not count toward the cap.
constraintsoptional, default spacing 0, default rotation step 90°kerf_mm kerf, trim_mm edge margin and break_margin_mm breaking allowance are all non-negative; allowed_angles_step must be a positive integer divisor of 360.
optionsoptional, budget default / cap 30 secondstime_budget_s greater than 0 and ≤30; route defaults to wheel_score_break (wheel scoring then breaking). seed is an integer random seed, default 1, useful for reruns.

Legacy flat fields remain accepted: kerf / trim / break_margin / step / budget / route map to the constraint and option fields in the table above. New integrations should use the spec's nested form and avoid mixing both styles.

Geometry rules

  • All sizes / coordinates are in mm; up to three decimal places (0.001 mm) are currently supported; the last point may skip repeating the first — the server closes it automatically.
  • Contours must have positive area and must not self-intersect or have duplicate / degenerate vertices; v1 accepts outer-contour polylines only, without arc segments. Upstream must discretize arcs with a sag error ≤0.1 mm (the maximum deviation between arc and polyline not exceeding 0.1 mm).
  • Response contours are absolute millimetre coordinates on the raw sheet; draw outline directly without re-applying rotation or offsets.

Full curl example · 12 parts

Set the OPCUT_TOKEN environment variable locally first, then submit 12 identical pentagons; the token never goes into the request file or source code. Leave network headroom in your timeout settings.

curl --fail-with-body --max-time 40 \\
  -X POST https://opcut.net/api/nesting/optimize \\
  -H "Authorization: Bearer $OPCUT_TOKEN" \\
  -H "Content-Type: application/json" \\
  -d '{
    "spec": "opcut-io",
    "spec_version": "1.0",
    "engine": "nest",
    "stock": {"w": 2250, "h": 3210},
    "parts": [{
      "id": "P1", "qty": 12,
      "outline": [[0,0],[520,0],[520,180],[260,300],[0,180]]
    }],
    "constraints": {
      "kerf_mm": 0, "trim_mm": 0,
      "break_margin_mm": 0, "allowed_angles_step": 90
    },
    "options": {"time_budget_s": 30, "seed": 1, "route": "wheel_score_break"}
  }'

Response fields · current endpoint

Irregular-shape response fields
FieldMeaning
spec / spec_version / warningsSpec echo and compatibility warnings; log and check notes on unknown fields, alias deprecations, etc.
sheets[]Per raw sheet: sheet_id/w/h plus nested parts in pieces[]. The spec's placements is not used yet.
sheets[].pieces[]id is the input part number; angle the rotation (degrees); outline and transition field poly both return absolute contour coordinates.
sheets[].util_poly / cut_tierThe sheet's true area utilization as a 0–1 ratio; guillotine separability is feasible / infeasible / unknown, a machine hint only.
metricssheets_used actual sheets; poly_util true area utilization (0–1); bbox_util bounding-box reference utilization (may exceed 1); area_lb the area lower bound sheet count. Multiply by 100 for a percentage.
verify / route / seconds / constructor / trimGeometry re-check status/issues, the process route, engine elapsed seconds, the constructor and the edge margin (mm). A successful re-check status is passed.
svg_urlSame-organization authenticated SVG download; kept for 15 minutes, unavailable after restarts / cache eviction (404). Save the JSON result promptly.
billingcall_fee / order_fee / total in yuan; the server's settlement for this call is authoritative.

Billing and failure handling

Rectangular pricing applies: a successful business call costs ¥0.05, and the order fee follows the actual raw-sheet count — ¥1 / ¥2 / ¥3 (≤20 / ≤100 / >100 sheets), e.g. a success on ≤20 sheets totals ¥1.05. The balance pre-check threshold is ¥3.05; the freeze starts from the area lower bound and settles on actual sheets; failures unfreeze in full with no call fee. Queries and SVG downloads are free; final prices follow the billing spec and your bill.

Invalid input returns 422 INVALID_REQUEST; raw sheets that cannot hold the parts return 422 NESTING_UNFIT; busy concurrency returns 429 NESTING_BUSY; timeouts are 504 NESTING_TIMEOUT; engine unavailable / re-check failure returns 503 NESTING_UNAVAILABLE / NESTING_VERIFY_FAILED. Read error.message/details to fix input or back off and retry. The irregular sync endpoint currently has no idempotent replay — repeated successful submissions are billed independently.