Lescut

Developers

Lescut REST API

Nest from your own software: your ERP, CAD, scanner station, a script or an AI agent uploads the parts, plus the hides, the roll or the sheets, starts the job, follows it, and downloads cutting files with the original layers and curves. The same engine, rules and limits as the app. Included in Textile Pro, Textile Enterprise, Leather Pro, Leather Enterprise, Sheet Pro, Sheet Enterprise.

Quick start

  1. Be on a plan with the API. Textile Pro, Textile Enterprise, Leather Pro, Leather Enterprise, Sheet Pro, Sheet Enterprise include it, and so does their 7-day free trial. Start a trial, or move to one of these plans on the Billing page.
  2. Create a key. As an owner or admin, open the account menu, choose API keys, name the key after what will use it, and copy it. It is shown only once.
  3. Keep it out of your code. Put it in an environment variable or your secret store:
    export LESCUT_KEY=lsk_...
  4. Make your first request. It returns your plan, its limits and the engine minutes left:
    curl "https://lescut.com/api/v1/usage" -H "Authorization: Bearer $LESCUT_KEY"
    # → {"organization": {"name": "…", "industry": "leather"}, "plan": {"name": "Leather Pro", …},
    #    "quotaMinutes": 900, "usedMinutes": 12.5, "maxJobSeconds": 600, "requestsPerMinute": 60, …}
  5. Nest something. Send the outlines with a job, or upload your CAD files first, as below.

The base URL is https://lescut.com/api/v1. Requests and responses are JSON, except file uploads and downloads. A 401 means the key is missing or wrong; a 403 means the plan has no API or the subscription is not active.

Option 1: send the outlines with the job

The simplest way, and the one to use when nothing of your patterns should stay with us: no upload, no part library, no batch. Send each part as a list of [x, y] points in millimetres, start the job, download the files, and delete the job. See Confidentiality for what is kept and for how long.

Textile

# 1. start a job: 20 sets of two parts on a 1500 mm roll, 2 minutes
curl "https://lescut.com/api/v1/jobs" -H "Authorization: Bearer $LESCUT_KEY" -H "Content-Type: application/json" -d '{
  "sets": 20,
  "parts": [
    {"name": "Front", "outline": [[0,0],[420,0],[420,610],[0,610]], "quantity": 1,
     "rotation": {"mode": "discrete", "angles": [0, 180]}},
    {"name": "Pocket", "outline": [[0,0],[160,0],[160,140],[80,180],[0,140]], "quantity": 2}
  ],
  "settings": {"fabricWidth": 1500, "timeLimitS": 120}
}'
# → {"id": "job_…", "status": "queued", ...}

# 2. poll until status is complete, partial, failed or cancelled
curl "https://lescut.com/api/v1/jobs/job_…" -H "Authorization: Bearer $LESCUT_KEY"

# 3. download the cutting files, then delete the job: its parts go with it
curl "https://lescut.com/api/v1/jobs/job_…/files/all.zip" -H "Authorization: Bearer $LESCUT_KEY" -o marker.zip
curl -X DELETE "https://lescut.com/api/v1/jobs/job_…" -H "Authorization: Bearer $LESCUT_KEY"

Leather

# the hides go with the job too: outline, holes, defects and grade zones (unmarked leather is prime, A)
curl "https://lescut.com/api/v1/jobs" -H "Authorization: Bearer $LESCUT_KEY" -H "Content-Type: application/json" -d '{
  "sets": 5,
  "parts": [{"name": "Seat", "outline": [[0,0],[520,0],[520,480],[0,480]], "grade": "C"}],
  "hides": [{
    "id": "H-0412",
    "outline": [[0,0],[2400,0],[2600,1500],[0,1700]],
    "defects": [[[800,600],[850,600],[850,650],[800,650]]],
    "zones": [{"grade": "C", "polygon": [[0,1300],[2500,1300],[2580,1500],[0,1700]]}],
    "cost": 48.5
  }],
  "settings": {"setsMode": "prefer", "timeLimitS": 300}
}'

Sheet

# the sheets are settings, like the roll: 10 alike sheets of 2440 × 1220 mm; the job uses as few as it can
curl "https://lescut.com/api/v1/jobs" -H "Authorization: Bearer $LESCUT_KEY" -H "Content-Type: application/json" -d '{
  "sets": 4,
  "parts": [
    {"name": "Side", "outline": [[0,0],[720,0],[720,560],[0,560]], "quantity": 2},
    {"name": "Shelf", "outline": [[0,0],[680,0],[680,300],[0,300]], "quantity": 3,
     "rotation": {"mode": "discrete", "angles": [0, 180]}}
  ],
  "settings": {"sheets": [{"width": 2440, "height": 1220, "count": 10}], "setsMode": "prefer", "timeLimitS": 120}
}'

Rings may be open or closed and run either way round; they must not cross themselves, and holes must lie inside their outline. A part takes the same rules as in a CAD file: quantity per set, grade, rotation, tilt, flip. The cutting files draw each part as its outline and holes on a CUT layer. Large jobs can be sent gzipped (Content-Encoding: gzip). You can also mix: stored hides with parts sent as outlines, or the other way round.

Option 2: upload your CAD files

Upload DXF or SVG files once and reuse them in later jobs. The cutting files then keep everything in the drawing: layers, curves, notches, drill holes and text, as in the app.

Each file is read with an import profile, which lists the layers to leave out, such as drawing frames or dimensions (GET /import-profiles). Every other layer is imported and read by its name: drill, notch, mark, stitch, text, engrave, etch, score, pen and draw layers travel with their part, every other layer gives outlines and cut-outs. Uploads use the organization's default profile; name another with importProfileId, or pass none to import every layer. A library is not read again: to use another profile, delete it and upload the file again.

Textile

# 1. upload the pattern with the default import profile (gzip keeps large files under the request limit)
gzip -c shirt.dxf | curl "https://lescut.com/api/v1/libraries?filename=shirt.dxf" \
  -H "Authorization: Bearer $LESCUT_KEY" \
  -H "Content-Type: application/dxf" -H "Content-Encoding: gzip" --data-binary @-
# → {"id": "lib_…", "parts": [...], ...}

# 2. start a job: 50 sets on a 1600 mm folded roll for 5 minutes;
#    every other setting comes from the default nesting profile
curl "https://lescut.com/api/v1/jobs" -H "Authorization: Bearer $LESCUT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"libraryId": "lib_…", "sets": 50, "settings": {"fabricWidth": 1600, "fabricFold": "edge", "timeLimitS": 300}}'
# → {"id": "job_…", "status": "queued", ...}

# 3. poll until status is complete, partial, failed or cancelled
curl "https://lescut.com/api/v1/jobs/job_…" -H "Authorization: Bearer $LESCUT_KEY"

# 4. download every cutting file and the report
curl "https://lescut.com/api/v1/jobs/job_…/files/all.zip" -H "Authorization: Bearer $LESCUT_KEY" -o marker.zip

Leather

# 1. upload the parts as above, then a batch read with one of your scanner profiles (GET /scanner-profiles)
curl "https://lescut.com/api/v1/batches" -H "Authorization: Bearer $LESCUT_KEY" \
  -H "Content-Type: application/json" -d '{"name": "Lot 0412", "scannerProfileId": "sp_…"}'

# 2. one request per hide, as the scanner exports it
for f in scans/*.dxf; do
  curl "https://lescut.com/api/v1/batches/hb_…/hides?filename=$(basename "$f")" \
    -H "Authorization: Bearer $LESCUT_KEY" -H "Content-Type: application/dxf" --data-binary @"$f"
done

# 3. nest 20 whole sets for 10 minutes, then poll and download as for textile
curl "https://lescut.com/api/v1/jobs" -H "Authorization: Bearer $LESCUT_KEY" -H "Content-Type: application/json" \
  -d '{"libraryId": "lib_…", "batchIds": ["hb_…"], "sets": 20, "settings": {"setsMode": "strict", "timeLimitS": 600}}'

Sheet

# 1. upload the parts with an import profile that leaves out the drawing frame and dimensions
curl "https://lescut.com/api/v1/libraries?filename=cabinet.dxf&importProfileId=ip_…" \
  -H "Authorization: Bearer $LESCUT_KEY" -H "Content-Type: application/dxf" --data-binary @cabinet.dxf

# 2. nest 12 kits for 5 minutes on the sheet size of the default nesting profile, with 30 sheets this time,
#    then poll and download as for textile
curl "https://lescut.com/api/v1/jobs" -H "Authorization: Bearer $LESCUT_KEY" -H "Content-Type: application/json" \
  -d '{"libraryId": "lib_…", "sets": 12, "settings": {"sheets": [{"width": 2440, "height": 1220, "count": 30}], "timeLimitS": 300}}'

Jobs and polling

A job is queued until its machine starts, then running. It ends as complete (every part placed), partial (not every part fits), failed (see error) or cancelled. A job searches for its full time limit unless no better nest can exist.

  • Poll GET /jobs/<id> every 5 to 10 seconds. While it runs, progress holds the best nest so far: parts placed, hides or sheets used or the marker length, yield.
  • When it ends, result has the statistics, the unplaced parts, and per hide, sheet or marker the placements and links to its DXF and SVG.
  • POST /jobs/<id>/cancel stops it early; it keeps the best nest found so far as its result.
  • Placements are in each part's drawing coordinates: mirror y when reflected, rotate counter-clockwise by rotationDeg about the origin, then translate. Or just download the files, which already have this applied.
  • Settings start from a nesting profile: nestingProfileId, or your default profile, or the built-in defaults. Override any of them in settings (search time, gap, tilt; leather goal, sets mode and batch rules; textile roll width, fold and stripe repeat; sheet size, sheet count and sets mode) and the rest stays as the profile has it. A setting of another industry, an unknown field or a value out of range is refused with a 400 naming it. The settings in the job's response are what it ran with. Part rules (grain, rotation, tilt, flip, grade) come from the library.

Confidentiality

Your patterns stay yours. When the parts and hides come with the job (option 1):

  • No file is stored. The outlines are kept in our database as part of the job only, and never appear in your part libraries or hide batches.
  • Deleting the job (DELETE /jobs/<id>) deletes them at once, with its result. A job you leave is deleted automatically about 24 hours after it ends, so download its files in time.
  • What stays is the record of the engine time used, for billing: no geometry, no names.
  • The machines that run jobs receive the job encrypted, and its result and log go back encrypted; our compute provider keeps only that encrypted copy, for up to 30 days. Encrypted database backups are overwritten within 30 days (see the Privacy Policy).
  • Everything travels over HTTPS, and only keys of your organization can read your jobs.

Uploaded files (option 2) are kept until you delete their library or batch, and are never shared with other organizations.

Authentication

Send the key in the Authorization header: Authorization: Bearer lsk_…. Keys never go in URLs or cookies.

  • A key acts for its whole organization: every library, batch and job, within the plan's limits. Jobs it starts are listed in the app as started by the key's creator.
  • The key is shown once when it is created. We store only a hash of it, so we cannot show it again; revoke a lost key and create a new one.
  • A key stops working when it is revoked, when the person who created it leaves the organization, or when the plan no longer includes the API.
  • Keep keys on servers. The API sends no CORS headers, so a web page on another site cannot call it with a key.
  • Each key shows when it was last used, on the API keys page. An organization can hold up to 20 keys; use one per integration so you can revoke them separately.

Limits

API jobs count against the same engine minutes as jobs started in the app, and follow the same plan limits. Queued and running jobs hold the rest of their time limit, so a client cannot queue more work than the minutes left. During the trial, jobs run one at a time and for up to 10 minutes on leather, 5 on textile or 5 on sheet.

PlanRequests / minuteEngine minutes / monthLongest jobJobs at a time
Textile Pro6018010 min2
Textile Enterprise30090020 min10
Leather Pro6090010 min2
Leather Enterprise3004,50060 min10
Sheet Pro6018010 min2
Sheet Enterprise30090030 min10
  • The request rate counts every request of the organization, all keys together, per clock minute. Each response carries RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds); a 429 adds Retry-After.
  • A request body is at most 4 MB. Send larger CAD files gzipped (Content-Encoding: gzip); DXF files shrink 5 to 10 times. Unpacked, a file may be up to 200 MB.
  • A batch holds up to 2000 hides. A job nests up to 1000 sets.
  • Need more? hello@lescut.com. Offering Lescut to your own customers through the API runs on partner terms.

Errors

Errors are JSON with a stable code and a message that says what to change:

{"error": {"code": "job_rejected", "message": "This job has 2400 pieces; the Textile Pro plan nests up to …"}}
StatusCodeMeaning
400invalid_requestA missing or malformed field, parameter or body. The message names it.
401unauthorizedNo key, or a key that is wrong, revoked, or whose creator left the organization.
403plan_without_apiThe organization's plan does not include the API.
403subscription_inactiveThe subscription has ended or is not active yet.
404not_foundNo such library, batch, job or file in your organization.
409job_liveThe job is still queued or running; cancel it first.
410goneThe job's part library or a hide batch was deleted, so its files cannot be written.
413payload_too_largeThe file is larger than 200 MB unpacked.
422unreadable_fileNo closed part outline or hide could be read from the file, or contours cross themselves.
422job_rejectedThe job cannot start: engine minutes used up or held by running jobs, a plan limit, a part wider than the roll.
429rate_limitedToo many requests this minute. Wait for the Retry-After seconds.
500internalOur fault. Retry with backoff; contact us if it persists.

Agents and OpenAPI

The API is described in an OpenAPI 3 document at https://lescut.com/api/v1/openapi.json. Load it into an agent framework, a custom GPT action or a client generator, and give the agent a key. Agents work within the same limits as any client: the quota and the request rate hold however the calls are made. Tell your agent to read error messages: they explain how to fix the request.

Endpoint reference

Paths are relative to https://lescut.com/api/v1. Endpoints marked leather exist for leather organizations only.

Jobs

GET/jobs

List jobs

Newest first. Continue with `before` set to the `next` of the previous page.

  • limit (query): 1 to 100, default 25.
  • before (query): A job id: list the jobs created before it.
  • status (query): queued, running, complete, partial, failed or cancelled.
curl "https://lescut.com/api/v1/jobs" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a JobPage.

POST/jobs

Start a nesting job

Send the parts as `parts` (outlines in mm; nothing is stored apart from the job, which you delete when done) or name a stored `libraryId`. Leather also needs `hides` (outlines with defects and grade zones) or stored `batchIds`; the two can be mixed. Settings start from the nesting profile (`nestingProfileId`, else the organization's default, else the built-in defaults); `settings` overrides any of them, within the organization's industry, and the search time is capped at the plan's job length. Send large bodies gzipped (Content-Encoding: gzip). The job is refused (422) when the plan's engine minutes are used up or held by queued and running jobs, or when the order breaks a plan limit.

curl -X POST "https://lescut.com/api/v1/jobs" \
  -H "Authorization: Bearer $LESCUT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"parts":[{"name":"Front","outline":[[0,0],[420,0],[420,300],[0,300]],"quantity":2,"grade":"B"},{"name":"Strap","outline":[[0,0],[600,0],[600,40],[0,40]],"holes":[[[20,15],[30,15],[30,25],[20,25]]],"quantity":4}],"hides":[{"id":"H-0412","outline":[[0,0],[2400,0],[2600,1500],[0,1700]],"defects":[[[800,600],[850,600],[850,650],[800,650]]],"zones":[{"grade":"C","polygon":[[0,1300],[2500,1300],[2580,1500],[0,1700]]}]}],"sets":10,"settings":{"timeLimitS":300}}'

Returns 201 with a Job.

GET/jobs/{id}

Get a job: status, progress, result

Poll every few seconds while the job is queued or running; `result` and its file links appear when it ends.

  • id (path, required): Job id.
curl "https://lescut.com/api/v1/jobs/<id>" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a Job.

DELETE/jobs/{id}

Delete a finished job

Cancel a queued or running job first (409). Its engine time stays counted.

  • id (path, required): Job id.
curl -X DELETE "https://lescut.com/api/v1/jobs/<id>" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 204, no body.

POST/jobs/{id}/cancel

Stop a job

A stopped job keeps the best nest found so far as its result.

  • id (path, required): Job id.
curl -X POST "https://lescut.com/api/v1/jobs/<id>/cancel" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a Job.

GET/jobs/{id}/files/{file}

Download a cutting file or report

`<binId>.dxf` and `<binId>.svg` per hide, sheet or marker (the links in the result's layouts), `report.csv`, `batches.csv` (leather) and `all.zip`. Files keep your original layers and curves.

  • id (path, required): Job id.
  • file (path, required): <binId>.dxf, <binId>.svg, report.csv, batches.csv or all.zip (URL-encoded).
  • cutter (query): A cutter profile id, or `none`; the default profile when absent.
  • labels (query): 1: add part labels to DXF and SVG files.
curl "https://lescut.com/api/v1/jobs/<id>/files/<file>" \
  -H "Authorization: Bearer $LESCUT_KEY" \
  -o out.dxf

Returns 200: application/dxf, image/svg+xml, text/csv or application/zip.

Part libraries

GET/libraries

List part libraries

curl "https://lescut.com/api/v1/libraries" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a Library list.

POST/libraries

Upload a part library

The body is the DXF (plain or AAMA/ASTM) or SVG file itself, as it would be uploaded in the app; gzip it with Content-Encoding: gzip to stay under the 4 MB request limit. Parts, quantities and NAME/QTY/GRADE/ROT/TILT/FLIP options are read from the file (in SVG also from data-name or data-piece, data-size, data-quantity, data-grade, data-rotation, data-tilt and data-flip on an outline).

  • filename (query, required): The file's name, ending in .dxf or .svg.
  • name (query): Library name; the file name without extension when absent.
  • importProfileId (query): Import profile for the file's layers; the organization's default when absent, `none` to import every layer.
curl -X POST "https://lescut.com/api/v1/libraries?filename=parts.dxf" \
  -H "Authorization: Bearer $LESCUT_KEY" \
  -H "Content-Type: application/dxf" --data-binary @parts.dxf

Returns 201 with a Library.

GET/libraries/{id}

Get a part library with its parts

  • id (path, required): Part library id.
curl "https://lescut.com/api/v1/libraries/<id>" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a Library.

DELETE/libraries/{id}

Delete a part library

Refused with 409 library_in_use while a job uses it: delete those jobs first.

  • id (path, required): Part library id.
curl -X DELETE "https://lescut.com/api/v1/libraries/<id>" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 204, no body.

PATCH/libraries/{id}/parts/{partId}

Change a part

Quantity per set, grade, rotation, tilt, flip, part-in-part and stripe matching. Fields left out stay as they are.

  • id (path, required): Part library id.
  • partId (path, required): Part id within the library.
curl -X PATCH "https://lescut.com/api/v1/libraries/<id>/parts/<partId>" \
  -H "Authorization: Bearer $LESCUT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"quantity":4,"grade":"B","rotation":{"mode":"stepped","step":90},"tilt":3}'

Returns 200 with a Part.

Hide batches

GET/batches

List hide batches · leather

curl "https://lescut.com/api/v1/batches" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a Batch list.

POST/batches

Create an empty hide batch · leather

Add hides to it one scanner file at a time.

curl -X POST "https://lescut.com/api/v1/batches" \
  -H "Authorization: Bearer $LESCUT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"name":"Lot 2026-10-24","scannerProfileId":"sp_1a2b3c4d5e6f"}'

Returns 201 with a Batch.

GET/batches/{id}

Get a hide batch with its hides · leather

  • id (path, required): Hide batch id.
curl "https://lescut.com/api/v1/batches/<id>" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a Batch.

DELETE/batches/{id}

Delete a hide batch · leather

  • id (path, required): Hide batch id.
curl -X DELETE "https://lescut.com/api/v1/batches/<id>" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 204, no body.

POST/batches/{id}/hides

Add a scanned hide · leather

The body is one hide as your scanner exports it (DXF or SVG, gzip allowed), read with the batch's scanner profile. A repeated hide id gets a suffix (-2, -3).

  • id (path, required): Hide batch id.
  • filename (query, required): The file's name, ending in .dxf or .svg.
curl -X POST "https://lescut.com/api/v1/batches/<id>/hides?filename=parts.dxf" \
  -H "Authorization: Bearer $LESCUT_KEY" \
  -H "Content-Type: application/dxf" --data-binary @parts.dxf

Returns 201 with a Batch.

PATCH/batches/{id}/hides/{hideId}

Set a hide's price or shade group · leather

`cost` is the hide's own price, for the lowest-cost goal. `group` is its matching group (shade lot) for strict sets; an empty string clears it.

  • id (path, required): Hide batch id.
  • hideId (path, required): Hide id within the batch.
curl -X PATCH "https://lescut.com/api/v1/batches/<id>/hides/<hideId>" \
  -H "Authorization: Bearer $LESCUT_KEY" \
  -H "Content-Type: application/json" \
  -d '{"cost":42.5,"group":"Lot A"}'

Returns 200 with a Hide.

Profiles

GET/nesting-profiles

List nesting profiles

Saved search settings. A job takes every setting from its profile except the search time.

curl "https://lescut.com/api/v1/nesting-profiles" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a NestingProfile list.

GET/import-profiles

List import profiles

Layers of part files to leave out (frames, dimensions, notes); every other layer is imported and read by its name. One is the default for uploads.

curl "https://lescut.com/api/v1/import-profiles" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a ImportProfile list.

GET/scanner-profiles

List scanner profiles · leather

curl "https://lescut.com/api/v1/scanner-profiles" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a ScannerProfile list.

GET/cutter-profiles

List cutter profiles

Layer mappings for cutting files; pass one as `cutter` when downloading.

curl "https://lescut.com/api/v1/cutter-profiles" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a CutterProfile list.

Account

GET/usage

Plan limits and engine minutes used

Engine minutes used in the current period (the trial, or the calendar month), minutes held by queued and running jobs, and the plan's limits.

curl "https://lescut.com/api/v1/usage" \
  -H "Authorization: Bearer $LESCUT_KEY"

Returns 200 with a Usage.

Questions, or an integration we should know about? Contact us.