Engine API
If you just cut panels, you do not need this page. The optimizer inside CutOptim already does everything described here. The Engine API is the same engine with no screen attached, for when other software needs cutting plans without a person opening the app.
Everything CutOptim does in your browser starts with one calculation: given these parts and this stock, what is the best way to cut them? The Engine API exposes exactly that calculation over the internet, so a different program can ask the question and get the answer back — no browser, no clicking, nobody logged in.
That is the whole idea. It is not a new optimizer, not a better optimizer, and not a bigger plan. It is the same engine, reachable by software instead of by a person.
Is this for me?
For the overwhelming majority of CutOptim users, the honest answer is no. If your working day involves opening CutOptim, typing in parts and stock, and printing or exporting the plan, the app is the product and this page is irrelevant to you.
The Engine API is for one situation: cutting plans need to appear inside software you already use, without anyone visiting CutOptim. In practice that means one of two people:
- You already run software that the cutting plan belongs inside. An ERP that holds your orders, a quoting tool that prices jobs, or a machine’s own software. Instead of an operator re-typing the same part list into CutOptim, that program asks the engine directly and shows the plan where the work already happens.
- You are having software built for you. By an in-house developer, a local software company, or the supplier of your machine. The Engine API is what they connect to.
Using the Engine API means somebody writes software against it. There is no interface, no spreadsheet to fill in and nothing to install — it is a service for programs, and the work is done by whoever writes that program. If nobody on your side is writing code, the app is what you want.
If you are not sure which side of that line you are on, a good test: could the plan appear without anyone asking for it? If yes, the API is relevant. If a person always decides to make a cutting plan, the app is already the right tool.
What it does
Your software sends the same two things you would type into the app — a list of parts and a list of stock — as JSON. The engine sends back a complete answer:
- The full layout. Every part, positioned on a specific sheet or bar, including whether it was rotated.
- A cut plan. Not just a picture of rectangles: the actual guillotine cut sequence, in order, so the plan can be executed on a panel saw.
- The numbers. How many sheets or bars the job takes, the yield percentage, how many cuts are needed, and the total price of the material used. For panels you also get the two honest cut counts — cut lines, which merges cuts that share one fence setting, and saw passes, which counts every pass — plus the total length sawn.
- Optionally, the drawing. Ask for
include: ["svg","csv","dxf"]and the response also carries the layout as a ready-made file — a self-contained 2D SVG drawing, an R12/AC1009 DXF, or a CSV cut list — inline in the JSON, with no storage and no second call. (SVG is 2D only.) 2D requests can also ask forlabels(per-piece label data),planHash(a fingerprint of the plan),pdf(the cutting-plan PDF) andlabelsPdf(the label PDF) — the PDFs base64-encoded, drawn by the same code as the app. - Your own identifiers, returned. Attach a
metaobject — an article number, an order line, a customer reference — to any part or stock row, and it comes back unchanged on every placed piece and every sheet or bar, so the plan lines up with your own system. This is the field to use when you want to send all your boards, tag each with your own code, and read back which board the optimizer chose — put the code instock[].metaand it is echoed onsheets[].meta. It never affects placement. Do not use thematerialfield for identification:materialis a hard partition (a part is only cut from stock of the same material), so tagging every stock row with a material while leaving the parts untagged makes every part come backunmatchedwith an empty result — see the material-aware section below.
There are four optimization modes, all matching the app — one for 2D panels, one for 1D linear material such as bars, profiles and tube, one for timber, where the material has a cross-section, and true-shape nesting (POST /v1/optimize/nest), which packs arbitrary polygons for laser, plasma and waterjet cutting (the app’s Nesting mode). Each optimize endpoint also has a free validation endpoint that checks a request without solving it (see below).
Endpoints
The base URL is https://api.cutoptim.com. The optimize and usage endpoints carry a key in an Authorization: Bearer <key> header; the validate and health endpoints need no key.
| Endpoint | What it does |
|---|---|
POST /v1/optimize/2d |
2D panel optimization |
POST /v1/optimize/1d |
1D / linear optimization — bars, profiles, tube |
POST /v1/optimize/wood |
Timber optimization — 1D with cross-section matching |
POST /v1/optimize/nest |
True-shape nesting — irregular polygons for laser, plasma and waterjet |
POST /v1/validate/2d · /1d · /wood · /nest |
Validate a request without solving — free, no key, no quota |
GET /v1/jobs/{id} |
Poll an asynchronous max-engine job — your own jobs only, no quota |
POST /v1/import/nest |
Read part outlines out of an SVG or DXF file — needs a key, spends no quota |
GET /v1/usage |
The ACCOUNT’s usage and quota for the current month (all keys share one) |
GET /v1/health |
Liveness check — needs no key |
Most calls are answered directly with the finished result. The one exception is the asynchronous max engine (see Three engines below): a max submission returns a job id, and you poll GET /v1/jobs/{id} until the plan is ready.
The validate endpoints take the same body as the matching optimize endpoint and check it without running the solve: a malformed request comes back as a 400 naming the exact bad field, and a well-formed one returns valid: true plus feasibility warnings (for example, a part that fits no stock). They cost nothing and need no key, so you can validate your payloads while you build the integration — before you even have a key — and confirm a request will not be rejected without spending one of your monthly calls.
Grain direction on panels
Plywood and veneered boards have a grain, and a visible part must be cut with it running the right way. On POST /v1/optimize/2d, send grain on a stock row — "w" or "h", the side of the sheet the grain runs along — and grain on a part, the side of the part it must run along. Every such part is placed with its grain along its sheet’s: turned 90° when that is what it takes (rotated: true on the placement, as for any turn), and rotatable is ignored for it. A part without grain still rotates freely, also on a stock row without grain (MDF, melamine); a part WITH a grain keeps the orientation the grained rows give it on such a row too — send material to keep grained and plain boards apart. A part that would fit only turned against the grain is reported in unplaced in the size you sent, with a warnings line naming it, and POST /v1/validate/2d reports it before a call is spent.
Stock rows whose grain runs different ways can sit in one request — a 2440 × 1220 with "w" and a 1220 × 2440 with "h" — and each sheet comes back in its row’s own orientation with grain echoed on sheets[]. With material, a part is only held to a grain when its own material has a grained board. A part’s grain with no grained stock in the request has no effect, and a line in warnings says so. options.trim and a forced options.firstCut apply to each sheet in its own row’s orientation, also on a row whose grain runs the other way. The CutOptim app’s Grain direction mode runs the same code. (Since 2026-10-02.)
Grain matching on panels (matched sets)
Grain direction keeps each part’s grain the right way. Some fronts need more than that: a drawer front over two doors whose figure runs on from one to the next, as if the three were still one board. On POST /v1/optimize/2d, give those part rows an id and name them in matchedSets:
{
"parts": [
{ "id": "DF", "name": "Drawer front", "w": 48.125, "h": 6 },
{ "id": "DOOR", "name": "Door", "w": 24, "h": 30, "qty": 2 },
{ "name": "Shelf", "w": 22.5, "h": 11.25, "qty": 4 }
],
"stock": [{ "w": 96, "h": 48, "qty": 2, "grain": "w" }],
"options": { "kerf": 0.125 },
"matchedSets": [
{ "id": "base-1", "name": "Base cabinet fronts", "grain": "w",
"layout": { "dir": "rows", "lines": [["DF"], ["DOOR", "DOOR"]] } }
]
}
- What a set is.
layout.lineslists the members byparts[].id. Withdir: "rows"each line is a row of members side by side and the rows stack top to bottom, so[["DF"], ["DOOR", "DOOR"]]is a drawer front over two doors; with"cols"each line is a column and the columns stand side by side. A part may appear more than once in one set (Door, Door), but it can belong to one set only.qty(default 1) is how many instances you need — three identical cabinets areqty: 3— and the parts’ ownqtymust cover every instance. A set has at least 2 and at most 24 members, and one request takes at most 50 sets. - How it is cut. The set goes through the optimizer as one rectangle — the block — and comes back as its members, exactly one kerf apart, out of one piece of board. The set’s
grain("w"or"h", along the block’s width or height) decides the direction of every member; a member’s owngrainis not used inside the set, and a warning names any member it overrides. Without a setgrain, the block may turn when every member isrotatable.installGapis the gap between the members on the cabinet (default: the kerf); on the sheet they still stand one kerf apart. - What comes back. Every placed member carries
matchedSet: the set’sid, whichinstance, and itspositionin reading order on the cabinet front — top to bottom, left to right, from 1 — plus ahintsuch as"left"or"right"where that is unambiguous. The response adds amatchedSetsrollup, one entry per set: itsletter(A, B … in request order), how many instances were cut as one block (asBlock), split or left unplaced, the block size and the set’swarnings. When a block fits no stock sheet,onMiss: "split"(the default) places the members one by one with a warning, andonMiss: "unplaced"lists them inunplaced. - Warnings, not errors. A size that does not line up is a warning on the set, not a refusal. Each line starts with its code —
align:(the lines are not flush),gap:(the kerf is wider thaninstallGap),grain:,split:,unplaced:,unmatched:(no stock of the set’s material),priority:andgrainGroup:— andalignandgapare judged at 1/64 of your unit or a quarter kerf, whichever is smaller. A set that cannot be laid out at all — a member id no part carries, a referenced id on two parts, fewer than two members, too few parts forqty— is a400naming the exact field, before a call is charged. parts[].idis safe to send. Only an id a set refers to is checked (a non-empty printable string of at most 64 characters, or an integer, on exactly one part). Every other id is ignored, whatever it holds, so your ERP article numbers can stay on the rows. And nothing changes for a request without sets: only a non-emptymatchedSetsarray switches the feature on — absent,null,[]or any value that is not an array is answered exactly as before.- Exports. With sets,
csvgainsSetandPositioncolumns at the end,svgprints the position code (A1, A2 …) in the part labels and draws each set’s frame in a separatematched-setsgroup —options.setOutlines: falseleaves that group out — anddxfcarries the position code on the LABELS layer.pdfandlabelsPdfdo not mark sets: they draw the members as ordinary parts, andwarningssays so; the set marks are insheets[].parts[].matchedSet,csvandsvg. - Engines and endpoints. The
heuristicandbalancedengines lay out sets (balanceddoes not rankpriority, so underrespectStocka set can lose its place there, with apriority:warning). Themaxengine refuses a non-emptymatchedSetswith a400whosedetails.unsupportednames it, before a call is charged; an empty one is an ordinarymaxjob.matchedSetsandparts[].idare 2D only: the 1D, wood and nest endpoints ignore them. grainGroupis deprecated. It is still accepted and still works exactly as before: parts that share a group are kept on one sheet. It never placed them next to each other, so it gives no continuous grain across them — for that, usematchedSets.
The CutOptim app’s Grain matching lays out sets with the same core.
Why timber has its own endpoint
Linear material knows one dimension, its length, so any bar can serve any part. Timber does not work that way: a 50×150 part cannot come out of a 50×100 bar, however much length is left in it. The wood endpoint therefore takes both cross-section sides on every part and every stock row, splits the job by cross-section, matches each section to its own stock, and returns the sections separately — each with its own bars and its own totals, alongside the figures for the whole job.
Two details worth knowing before you integrate it:
- The two cross-section sides can be sent in either order. 50×100 and 100×50 are the same bar turned over, so they are matched as one section. That means a difference in how your data happens to be entered cannot make material disappear.
- “No stock of this cross-section” and “did not fit” are reported separately. The first is a missing-material report, the second is a capacity problem, and they have different fixes — so mixing them into one list would send your user looking in the wrong place.
You could approximate this with several 1d calls of your own, grouping the parts yourself. It would cost one request from your quota per cross-section instead of one for the job, put the stock matching in your code, and produce a job total you would have to assemble yourself — one that would not necessarily agree with what the CutOptim app shows for the same job.
Parts from a CAD file
The nesting endpoint does not only take coordinates. A part may carry a source instead — an SVG or DXF document — and the server reads its outline, and any holes in it, out of the file. It is the same reader the app uses when you drop a drawing onto its Nesting mode, so a part library that already exists as CAD files does not have to be converted into coordinate lists first.
The file replaces only the geometry. Quantity, material, allowed rotations and your own metadata stay ordinary fields on the part row, exactly as they are when you send coordinates. One file describes one part; when a single drawing holds several separate components, POST /v1/import/nest splits it into ready-made part rows first — and that call needs a key but does not spend a request from your monthly quota.
Nothing is stored. The file exists only as the request itself: it is read in memory and gone by the time the answer is written. No copy is kept on disk or in a database, nothing is written to a log, and there is nothing to delete afterwards. Units are treated the same way — a DXF may declare millimetres or inches and we report what it said, but coordinates are never converted, because no value in this API carries a unit.
The same answer every time
The engine is deterministic: the same input always produces the same output. There is no randomness and no clock inside the algorithm.
This sounds academic but it is the practical reason to build on it. It means results can be cached — if you already asked about this exact job, you can safely reuse the answer instead of asking again. It also means the integration can be tested: a plan can be compared against a known-good result, and a difference is a real difference, not noise. Software that quotes a customer a price on Monday will quote the same price on Friday.
There is one opt-in exception, and it says so: the nesting max engine (below) spends a fixed time budget searching, so how far it gets depends on how much work fits into that time. Its responses carry deterministic: false. Every other engine — including nesting’s default, lbf — is deterministic.
Retrying safely. If a connection drops, you cannot tell whether your request arrived. Send an Idempotency-Key header — a value you choose per job — and retry with the same key: within 24 hours the same request returns the stored answer, marked Idempotency-Replayed: true, and is not charged a second time. A max job is not queued twice either. The public demo key ignores this header. (Since 2026-10-15.)
Webhooks for async jobs. Instead of polling GET /v1/jobs/{id}, register an endpoint (an https:// URL) on your dashboard and get a signed POST — job.succeeded or job.failed — when an async max job finishes. Check the CutOptim-Signature header with the secret you received, answer within 10 seconds, and read the plan with GET /v1/jobs/{id}. A delivery that fails is retried, 8 attempts over 24 hours. (Since 2026-10-19.)
Official SDKs. Open-source clients for TypeScript, Python and PHP — npm install @cutoptim/engine, pip install cutoptim, composer require cutoptim/engine — give you typed requests, retries that are never charged twice (every call carries an Idempotency-Key), a wait helper for async jobs and webhook verification. Source on GitHub; details in the SDK guide (English).
MCP server for AI assistants. Claude Code, Cursor, VS Code and Claude Desktop can call the engine as tools through the hosted Model Context Protocol server at https://api.cutoptim.com/mcp, with the same API key (Authorization: Bearer …) and the same quota: one tool call is one API call, and the validate_* tools are free. It also runs locally as the package npx -y @cutoptim/mcp (key in CUTOPTIM_API_KEY), and the official MCP Registry lists it as io.github.cutoptim/engine. Client configurations are in the MCP guide (English).
Three engines
The API offers a choice of engine. The first two run synchronously; the third is asynchronous. The difference is not quality — it is a trade-off between speed, whether the result can be cut on a panel saw, and how close it gets to the theoretical minimum.
heuristicis the default and the same one the app uses. It produces guillotine layouts: highest yield, every layout cuttable on a panel saw, and always a full cut plan. Synchronous.balancedis opt-in. It uses free nesting instead, which is dramatically faster on very large jobs — measured roughly 25× faster on a 2,000-part job — at the cost of slightly lower yield. The important catch: its layouts often cannot be cut edge-to-edge, so for those it returns no cut plan at all. Synchronous.maxis opt-in, for 2D panels (and, separately, for true-shape nesting — see below). On 2D it is a server-side tree search that reaches the proven optimum on far more jobs than the default, and its layouts are still guillotine-cuttable. The cost is time: amaxsolve takes seconds to a minute, so it does not answer in the response. Instead,POST /v1/optimize/2dwithengine: "max"returns a job id, and you pollGET /v1/jobs/{id}until it is ready. It is still deterministic. Reach for it when a large, valuable job is worth the wait for the last few boards. It models one stock format at full sheet size with unlimited supply, so a request that also carries a second stock format, per-sidetrim, limited stock (respectStock), materials, grain groups or a non-emptymatchedSetsis refused up front with a400naming exactly what it cannot do — before a call is charged. Send those jobs toheuristic, which models all of them.
Nesting: lbf and max
True-shape nesting has two engines of its own. lbf is the default: instant, synchronous and deterministic. max is opt-in and asynchronous, exactly like the 2D max — POST /v1/optimize/nest with engine: "max" returns a job id, and you poll GET /v1/jobs/{id}.
The nesting max runs the lbf pass first, so it always has a valid answer in hand. Then it spends a time budget (options.timeBudgetMs, 60 seconds by default, between 10 and 180) searching for a layout with fewer sheets, filling one sheet at a time. It never returns more sheets than lbf: when the search finds no improvement, you get the lbf layout and a warning that says so. Because the search is time-limited, its responses carry deterministic: false. It accepts several sheet sizes per material and respectStock, and ranks its result with the same rule as lbf (below): it replaces the lbf plan only when it is better — never just because it has fewer sheets. A polygon sheet or exclusion zones, which its strip search cannot model, are served by the lbf pass inside the job, with a warning — never refused. The older name sparrow is accepted as an alias of max.
Rectangular parts get an exact grid (since 2026-09-28). lbf finds positions by sampling, which almost never lands on a zero-slack grid: three 500 mm parts across a 1500 mm sheet at zero spacing is an exact fit, and a sampling search places two. So both engines also try two alternatives and keep whichever ranks best by the same rule: the parts restricted to the right angles they allow, and the parts’ bounding boxes packed by an exact rectangle packer (boxes that do not overlap cannot hold overlapping parts, so this is valid for any outline). On a tie — same sheets, same sheet area — the plan that fills its sheets front to back wins, then the one with fewer parts turned off the right angles. Nothing to switch on, and a result can only get better by the ranking; a plan that the rectangles bring to the fewest sheets the part area allows also makes max answer at once instead of searching.
A job of a few dozen parts usually needs about 15 seconds of budget (timeBudgetMs: 15000); the job finishes roughly one to two seconds after the budget, plus any time spent in the queue. Poll GET /v1/jobs/{id} every two to three seconds — polling costs no quota. No budget can promise a particular number of sheets.
Nesting: edge clearance, sheet sizes and what each sheet cost
- Edge clearance.
options.minSeparationis the spacing between parts, and on its own it also applies at the sheet edge.options.edgeClearancesets the edge separately — for example 5 between parts and 0 or 10 at the edge. Leave it out and the edge keepsminSeparation, exactly as before. It works on both engines, needs rectangular sheets (wandh), and exclusion zones keepminSeparationlike parts. A polygon sheet, or a value that leaves no room on a sheet, is refused with a400before a call is charged. - Choosing a sheet size. When a material has two or more stock rows,
lbftries them in your order, each size on its own and a cheapest-first order, and keeps the best — so the result no longer depends on the order you list the rows in. The ranking is: most must-cut parts placed (see below), then most parts placed, then the least total sheet area (material), then the fewest sheets. Withoptions.minimizeCostit ranks by total price instead (price, orcostfor a row without a price) — but only when every row of that material carries one; otherwise it falls back to area and says so inwarnings. - Mixing sizes. After that, each sheet — the least-filled first — is re-nested on its own onto the cheaper or smaller rows, and replaced when the whole plan ranks better. So sizes can be mixed within one material: typically two large sheets and a smaller last one, where the half-empty last sheet used to be a large one. With
options.respectStock, no row is used more often than itsqty. Themaxengine applies the same ranking. - Must-cut parts.
parts[].priority: truemarks a part that must be cut. It matters withoptions.respectStock— a capped supply, where not every part may fit: must-cut parts are then placed first, so a plain part never takes the sheet space a must-cut part needs, and every ranking above puts the plan that keeps more of them ahead, even if it places fewer parts in total. Both engines honour it (maxkeeps thelbflayout whenever anything is left unplaced). WithoutrespectStockevery part that fits is placed anyway, so the flag changes nothing. A must-cut part that fits no sheet at all is still listed inunplaced. (Since 2026-09-22 — before that the nesting engine accepted the field but ignored it.) - A capped supply that leaves parts over. With
options.respectStock, when the plan still leaves parts unplaced, the engine also fills the sheets one at a time with the parts’ bounding boxes — each sheet as full as it can, and a part that fits only one sheet size goes there first (a tall side on the one big sheet, not on an offcut strip). That plan is taken only when it places more parts (must-cut first) or uses less material; a job that places everything comes back exactly as before. It respects every part’s allowed rotations,minSeparationandedgeClearance. (Since 2026-10-14.) - What each sheet cost. Every sheet carries
stock— the index of the request’s stock row it was cut from — and the response addsstockUsage: one entry per stock row used, with its sheet count, total price and density. The unit is the stock row, not the size: two rows with the same dimensions but a different price ormetaare two entries. - Cut length and pierces. Every sheet carries
cutLength— the summed perimeter of its placed parts’ outlines and holes, in your unit — andpierces, one per closed contour;metricstotals both. It is the geometric contour length: no common-line cutting, lead-ins or micro-joints — your CAM adds those. - Used area, for partial-sheet charging. Every sheet carries
usedWidthandusedHeight— the farthest point any placed part reaches from the sheet’s origin corner, in your unit, on the real sheet (withedgeClearancethe edge gap is included) — andusedArea, their product;stockUsage[].usedAreatotals it per stock row. That is what an ERP needs to charge a horizontal cut (usedHeight× sheet width), a vertical cut (usedWidth× sheet height) or the bounding box (usedArea) instead of the full sheet — the charging rule stays yours. To make those numbers small, both engines compact the least-filled sheet of each material by default: its parts are re-nested on the same sheet with rotation subsets of what each part allows — 0/180, 0/90/180/270 and one orientation for every copy, intersected with a discreteallowedRotations, never a new angle — and the layout with the smallest used area is kept. Withengine: "max"that sheet is then also re-packed by the search itself — along the sheet’s height and, on a one-sheet plan, also along its width — and the tighter layout is kept under the same rule. Sheet count, sizes, price and placed parts never change;options.compact: falsereturns the uncompacted layout. If you charge a strip rather than the bounding box, setoptions.compactForto match:"horizontal"keeps the layout with the lowestusedHeight(a strip across the full sheet width),"vertical"the one with the smallestusedWidth(a strip over the full sheet height); the default"box"keeps the smallestusedArea. The strip modes also try every copy turned a quarter (90° or 270°) whereallowedRotationspermits. How small the used area gets depends on the parts. (Since 2026-09-22.)
balanced is not “better results”. It is faster and yields slightly less, and when its layout is not guillotine-cuttable there is no cut plan to hand to a saw operator. Choose it only when speed on a very large job matters more than a saw-ready plan. If you are unsure, stay on the default.
Limits
Each request is bounded, so a runaway job fails clearly instead of hanging:
| Limit | Value |
|---|---|
| Parts per request | 2,000 |
| Stock rows per request | 50 |
| Request body size | 1 MB |
| Request body size — nest paths that may carry a drawing | 10 MB |
| Active keys per account | 10 |
Keyless validate calls per address |
120 / minute |
| Unknown API keys per address | 30 / minute — then 429 for the rest of the minute |
| Wait for a free solve slot (synchronous paths) | about 8 s — then 429, nothing charged |
max jobs queued or running per account |
5 |
Nesting max search budget per job |
10–180 s (default 60 s) |
Idempotency-Key kept for |
24 hours |
| Webhook endpoints per account | 5 |
| Webhook delivery attempts | 8 over 24 hours |
Getting access
Start on the Engine API page. The Engine API is billed separately from the app plans, and no plan upgrade turns it on.
- Try it, then subscribe — or ask. You can call the API before paying: the Run this example buttons on the API reference use a public demo key, and any signed-in account can create free test keys on the dashboard (their own small monthly allowance, never billed, no subscription needed). When you are ready, subscribing on the Engine API page is the fastest route — the entry plan, Studio, starts with a free trial, and access lands on your account by itself. If you would rather describe your integration first, or you need a volume beyond the standard plan, get in touch instead and say what you want to connect and roughly how many cutting plans a month it will need.
- Access appears on your account. Nothing else about your account changes.
- Create a key. The API keys card is on your dashboard for every signed-in account: without a subscription it creates test keys, with API access live keys too. You create, name and delete keys there yourself.
- Copy the key immediately. The full key is shown exactly once, at the moment you create it.
A key is displayed only once. CutOptim stores only a sha256 hash of it, never the key itself — so it cannot be read back out later, by you or by us. Copy it straight into your software’s configuration when you create it. If you lose a key, delete it and create a new one; if a key is ever exposed, delete it and the calls stop working immediately.
Treat a key like a password: it belongs in your software’s configuration, not in an email, a spreadsheet or a screenshot.
What the API keys card on your dashboard does
Four controls, and it is worth being precise about what each one changes — especially the last one, which people expect to touch billing and does not.
- Plan, Switch and Billing. Shows which plan the account is on (Studio or Scale). Switch to Scale (or Switch to Studio) changes the plan after a confirmation, and the account’s monthly quota changes straight away. Billing opens your billing settings, where you update the card, see invoices or cancel.
- Create key. Generates a new key and shows it once, right there. You give it a name (
ERP integration,staging) purely so you can tell your keys apart later. Up to 10 active keys per account. Test keys are created the same way and counted separately: they have their own small monthly allowance and are never billed. - The usage bar. Two numbers: your account’s total for the calendar month against the monthly quota, and per key, how much that key has spent. The per-key figure is there to answer which integration is eating the allowance — it is not a separate budget.
- Revoke. Stops that one key working, from the next call onwards. The row stays visible so its history is not lost.
Revoking a key has nothing to do with your subscription. It does not cancel anything, does not refund anything and does not free up quota — the plan keeps running and the allowance keeps standing, you simply no longer hold that particular key. Revoke when a key has been exposed or an integration is retired. To stop being billed, cancel the subscription instead; access then continues to the end of the period you already paid for, and after that even existing keys stop working.
One quota for the account, not one per key
Every active key on your account draws on the same monthly allowance. Creating a second key does not create a second quota — keys exist so you can separate staging from production, give each integration its own credential, and revoke one without disturbing the others.
GET /v1/usage reports the account’s position (used, limit, remaining), so it answers the question you actually have — how much is left before calls start failing — regardless of which key you asked with. When the allowance runs out, every key returns 402, not just the one that spent it.
Plans, price and quota
The Engine API is billed separately from the app plans, monthly, in EUR, with two plans:
| Plan | What differs |
|---|---|
| Studio | The entry plan, with a free trial |
| Scale | A larger monthly request quota, no trial — you switch to it from Studio |
Everything else is the same on both: the same engines, all four modes, every option, the same answers. Every request counts the same — a nesting job costs one request, exactly like a 2D or 1D one.
- Hard cap, no overage. When the monthly quota is used up, calls return
402until it resets on the first day of the next month. There is no surprise invoice. - Two emails, so the cap does not surprise you. We email the account at 90% of the quota and again when it is used up.
- Switching plans is one button on the API keys card of your dashboard (Switch to Scale / Switch to Studio); the new quota applies straight away. An upgrade is charged at once for the rest of the period; a downgrade is not refunded, and the lower price applies from the next renewal. Switching is available once the trial is over.
- Larger volumes than the Scale plan are priced individually — get in touch.
The current prices and quotas are listed on the Engine API page — that page reads them from our pricing configuration, so it is always the accurate figure.
For the technical detail — the exact request and response shape, every option, all error codes and how versioning works — see the API reference.
Open-source components
CutOptim’s engines build on open-source libraries, used unmodified on our servers:
- jagua-rs — the geometric collision engine behind true-shape nesting (MPL-2.0).
- sparrow — the search behind the nesting
maxengine (MIT, KU Leuven). - PackingSolver — the tree search behind the 2D
maxengine (MIT).
Everything around them — the API, the mapping between the contract and these engines, the rectangular packers and the app itself — is CutOptim’s own code.
What this does not change
Worth stating plainly, because the Engine API is easy to misread as a change to the product:
- The optimizer in the app is unchanged. It still runs in your browser, exactly as before.
- Free, Pro and Workshop are unaffected. They still include the in-app optimizer, with the same limits as before. Nothing was moved behind the API.
- Nothing you do in the app consumes API requests. The monthly API quota is only touched by calls your own software makes.
The Engine API is an addition for people integrating CutOptim into other software. If that is not you, nothing changed.