Barcut ProMATERIAL OPTIMIZATIONOPEN APP
MENU
DEVELOPER API

Integrate Barcut Pro optimisation into your software.

Send parts and stock as JSON. Get back the bars to cut, the cut sequence on every bar, the yield — and an explicit list of anything that could not be placed. The same 1D engine the product runs on, callable from your ERP, quoting tool or production system.
STATUS
Live
PROTOCOL
HTTPS · JSON
AVAILABLE
1D linear
DETERMINISTIC
Same input, same plan
AVAILABLE NOW

The Engine API is live. Create a key from your account, send parts and stock as JSON, and get the cutting plan back in one call.

HOW IT WORKS

One request, one cutting plan.

No session, no state to manage, no polling. A single synchronous call returns the complete plan.

  1. Your software
    ERP · quoting · production
  2. JSON request
    parts + stock + settings
  3. Barcut Pro engine
    column generation · ALNS
  4. JSON response
    bars + cuts + unplaced
  5. Your system
    cut list · costing · schedule
REFERENCE

API reference

Getting started

The Barcut Pro Engine API exposes the same 1D linear cutting optimiser that powers the web app. You post the parts you need and the stock you hold; you get back which bars to cut, how to cut each one, and what — if anything — could not be made.

There is nothing to install and no state to manage. Each call is independent and synchronous, so a quote screen can call it inline and render the result.

To start: create an API key on your account page, then POST to the endpoint below. A free account is all that is required.

Availability: 1D linear optimisation is the live engine in the product today. 2D sheet nesting is in development and is not part of this API. Nothing here returns 2D results.

Authentication

Requests authenticate with a secret API key sent as a bearer token. Keys belong to your account and can be rotated or revoked without changing your integration code.

HTTP
Authorization: Bearer bcp_live_...

Create and revoke keys on your account page. A key is shown exactly once, at creation — we store only a hash of it, so it cannot be recovered. If you lose one, revoke it and create another. Revocation takes effect immediately.

KEEP KEYS SERVER-SIDE

A secret key in browser code is a public key — anyone can read it from the network tab. Call this API from your server and pass the result to your front end.

Optimise 1D

POST/api/v1/optimize/1d
LIVE

Solves a one-dimensional cutting-stock problem: given required lengths with quantities and the stock bars available, return the cutting plan that uses the least stock, honouring saw kerf, end trim, minimum useful offcut and per-length availability.

CURL
curl https://app.barcutpro.com/api/v1/optimize/1d \
  -H "Authorization: Bearer $BARCUTPRO_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"parts":[{"length":2400,"quantity":4,"label":"Head"}],"stock":[{"length":6200,"quantity":10,"priority":1}]}'

Request format

All lengths are millimetres. parts and stock are required; settings is optional and falls back to the product’s own defaults.

FieldTypeDescription
parts[].lengthnumberFinished length in mm. Must be greater than 0.
parts[].quantityintegerHow many of this length are required. Minimum 1.
parts[].labelstring?Free text, echoed back on every placed cut. Optional.
stock[].lengthnumberStock bar length in mm. Must be greater than 0.
stock[].quantityinteger | null?Bars available at this length. Omit or null for unlimited.
stock[].priorityinteger?Relative preference, 1 = use first. Omit for no preference.
settings.kerfnumber?Saw blade width lost at every cut, in mm.
settings.endTrimnumber?Material lost off both ends of every bar, in mm.
settings.minOffcutnumber?Leftover at or above this counts as a reusable offcut, not scrap.
settings.trailingCutboolean?Default true: the last piece on a bar needs its own cut.
REQUEST · JSON
{
  "parts": [
    {
      "length": 2400,
      "quantity": 4,
      "label": "Head"
    },
    {
      "length": 1485,
      "quantity": 6,
      "label": "Jamb"
    },
    {
      "length": 865,
      "quantity": 3,
      "label": "Transom"
    }
  ],
  "stock": [
    {
      "length": 6200,
      "quantity": 10,
      "priority": 1
    },
    {
      "length": 4000,
      "quantity": null,
      "priority": 2
    }
  ],
  "settings": {
    "kerf": 5,
    "endTrim": 15,
    "minOffcut": 300,
    "trailingCut": true
  }
}

On priority: preference is ranked by the distinct values you send, not by their magnitude. If every entry carries the same number — or none carries one — the plan is identical to sending no priority at all. Priority only changes the result when two entries genuinely differ.

Response format

bars lists every physical bar to cut, in order, each with its own cut sequence. cuts[].sequence is the order to cut in — a position, not a distance along the bar.

FieldTypeDescription
versionstringThe contract version that produced this body.
validbooleanfalse when the engine could not satisfy every constraint.
summary.partsRequestedintegerAlways equals partsPlaced + partsUnplaced.
summary.partsPlacedintegerCuts the plan actually makes.
summary.partsUnplacedintegerParts the plan does not make.
summary.barsUsedintegerStock bars consumed.
summary.yieldPercentnumberPlaced material as a percentage of stock consumed.
summary.largestOffcutnumberLongest single remnant, in mm.
summary.solveTimeMsintegerEngine time. The only field that varies between identical calls.
bars[].barNumberinteger1-based, continuous across the job.
bars[].cuts[]arraysequence, length and label for each cut on that bar.
bars[].used / remnantnumberSum of cuts, and the physical leftover.
unplaced[]arrayALWAYS present. Empty means everything was placed.
issues[]string[]Engine diagnostics explaining a partial or invalid result.
RESPONSE · JSON (bars truncated)
{
  "version": "v1",
  "valid": true,
  "summary": {
    "partsRequested": 13,
    "partsPlaced": 13,
    "partsUnplaced": 0,
    "barsUsed": 4,
    "yieldPercent": 94.2,
    "totalStockLength": 24800,
    "totalPartLength": 23365,
    "largestOffcut": 950,
    "solveTimeMs": 61
  },
  "bars": [
    {
      "barNumber": 1,
      "stockLength": 6200,
      "cuts": [
        {
          "sequence": 1,
          "length": 2400,
          "label": "Head"
        },
        {
          "sequence": 2,
          "length": 2400,
          "label": "Head"
        },
        {
          "sequence": 3,
          "length": 1355,
          "label": ""
        }
      ],
      "used": 6155,
      "remnant": 45
    },
    {
      "barNumber": 2,
      "stockLength": 6200,
      "cuts": [
        {
          "sequence": 1,
          "length": 1485,
          "label": "Jamb"
        },
        {
          "sequence": 2,
          "length": 1485,
          "label": "Jamb"
        },
        {
          "sequence": 3,
          "length": 1485,
          "label": "Jamb"
        },
        {
          "sequence": 4,
          "length": 1485,
          "label": "Jamb"
        }
      ],
      "used": 5940,
      "remnant": 260
    }
  ],
  "unplaced": [],
  "issues": []
}

Deterministic. The same request returns the same plan, every time — so responses are safe to cache, and a regression in your integration is distinguishable from a change in ours. Only summary.solveTimeMs varies.

Unplaced parts

READ THIS BEFORE YOU SHIP

A cutting plan can be partial. If you render bars without checking unplaced, your users will cut a job they believe is complete and discover the shortfall on the shop floor. Check summary.partsUnplaced on every response.

unplaced is always present. An empty array is a positive assertion that everything was placed — it is never omitted when empty, so absence can never be mistaken for success.

Each entry carries a reason, because the two causes need opposite fixes:

FieldTypeDescription
too_long_for_any_stockreasonLonger than any stock length allows once kerf and end trim are taken off. No quantity of stock will ever place it — a longer bar is required.
insufficient_stockreasonThe part fits, but the available bars ran out. More bars of an existing length will place it.
PARTIAL RESPONSE · JSON
{
  "version": "v1",
  "valid": false,
  "summary": {
    "partsRequested": 279,
    "partsPlaced": 266,
    "partsUnplaced": 13,
    "barsUsed": 104,
    "yieldPercent": 91.8,
    "totalStockLength": 644800,
    "totalPartLength": 591926,
    "largestOffcut": 1180,
    "solveTimeMs": 412
  },
  "bars": [],
  "unplaced": [
    {
      "length": 7400,
      "quantity": 2,
      "reason": "too_long_for_any_stock"
    },
    {
      "length": 2400,
      "quantity": 11,
      "reason": "insufficient_stock"
    }
  ],
  "issues": [
    "Stock cannot cover all demand: a proven maximum of 266 piece(s) can be produced with the available stock.",
    "1 part length(s) are longer than the longest available stock (6165mm) and cannot be cut: 7400mm x 2."
  ]
}

Errors

Errors return a non-2xx status and a single error object. A 4xx means the request needs changing; a 5xx is safe to retry — a failed solve is never partially applied.

Note the distinction: a job the engine could not fully cut is a 200 with a populated unplaced array, not an error. Only a malformed or unauthorised request is an error.

ERROR · JSON
{
  "error": {
    "code": "invalid_part",
    "message": "parts[2].length must be greater than 0.",
    "field": "parts[2].length"
  }
}
StatusCodeMeaning
400invalid_requestThe body is not valid JSON, or a top-level field is missing or the wrong type.
400invalid_partA parts[] entry has a non-positive length, a quantity below 1, or a bad label.
400invalid_stockA stock[] entry has a non-positive length, a negative quantity, or a bad priority.
400demand_too_largeTotal requested piece count exceeds the per-request limit. Split the job by material.
401unauthenticatedMissing, malformed, unknown or revoked API key.
405method_not_allowedThe endpoint accepts POST only.
429rate_limitedRate limit exceeded. Retry after the interval in the Retry-After header.
500solver_errorThe engine failed. Safe to retry; the request was not partially applied.
504solver_timeoutThe solve exceeded the time limit. Split the job into smaller batches.

Integration patterns

Three shapes cover almost every integration we have been asked about:

PatternWhere it runsWhat it does
Quote-time costingServer, inlineOptimise at quote time to price the real bar count rather than an estimate. Sub-second on typical orders.
Works-order releaseServer, on releaseOptimise when the order is released, store the plan against the job, and drive picking and cutting from it.
Nightly batchScheduled jobConsolidate a day of orders into one optimisation so identical lengths share bars across jobs.

For the saw itself, the plan is normally converted to whatever your machine reads. The product already exports a documented, versioned CSV for exactly that — see exporting a cutting plan.

Rate limits

60 requests per minute, per key. Exceeding it returns 429 rate_limited with a Retry-After header. Limits are per key, not per IP, so one noisy integration cannot exhaust another’s budget.

Per-request size limits, all returning a 400:

LimitValueError code
Part rows5000invalid_request
Stock rows500invalid_request
Total pieces100,000demand_too_large
Solve time30 ssolver_timeout (504)

Split genuinely enormous jobs by material. A solve is deterministic, so batching is safe: the same subset always returns the same plan.

Versioning

The version is in the path (/v1/) and echoed in every response body. Within a version the response is append-only: new fields may be added, existing fields are never renamed, removed or given a new meaning. Parse defensively — ignore fields you do not recognise — and a new field will never break you.

Anything breaking ships as a new path segment. Optimiser improvements that change a plan for the better are not breaking changes and will land within a version; if you depend on byte-identical plans over time, store the response you acted on.

Roadmap

CapabilityStatusNotes
1D linear optimisationLiveBars, profiles, pipe, timber, rebar. The endpoint documented above.
2D sheet nestingIn developmentNot available in the product or the API. Nothing here returns 2D results.
WebhooksNot planned yetSolves are fast and synchronous, so there is nothing to call back about.
SDKsNot planned yetOne JSON endpoint needs no client library. HTTP is the SDK.
Machine file generationUnder researchLinear cutting has no universal CNC format; a documented CSV plus a per-machine conversion step is the real integration route.
GET STARTED

Create your first key.

A free Barcut Pro account is all you need. Keys are created, listed and revoked from your account page.

  1. 01
    Sign in
    Open your account page. Create a free account first if you do not have one.
  2. 02
    Create a key
    Name it after where it will run (“Production ERP”), so you can revoke the right one later.
  3. 03
    Copy it once
    The secret is shown a single time. We store only a hash, so it cannot be recovered — save it in your secret manager immediately.
  4. 04
    Call the endpoint
    Send it as Authorization: Bearer <key> from your server.
GO DEEPER

Related reading

TRY THE ENGINE FIRST

Run a real cutting list through it.

START OPTIMIZING
NO SIGNUP TO OPTIMIZE · WORKS IN THE BROWSER