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
- 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.
- 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.
- Keep it out of your code. Put it in an environment variable or your secret store:
export LESCUT_KEY=lsk_... - 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, …} - 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.zipLeather
# 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,progressholds the best nest so far: parts placed, hides or sheets used or the marker length, yield. - When it ends,
resulthas the statistics, the unplaced parts, and per hide, sheet or marker the placements and links to its DXF and SVG. POST /jobs/<id>/cancelstops 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 byrotationDegabout 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 insettings(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. Thesettingsin 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.
| Plan | Requests / minute | Engine minutes / month | Longest job | Jobs at a time |
|---|---|---|---|---|
| Textile Pro | 60 | 180 | 10 min | 2 |
| Textile Enterprise | 300 | 900 | 20 min | 10 |
| Leather Pro | 60 | 900 | 10 min | 2 |
| Leather Enterprise | 300 | 4,500 | 60 min | 10 |
| Sheet Pro | 60 | 180 | 10 min | 2 |
| Sheet Enterprise | 300 | 900 | 30 min | 10 |
- The request rate counts every request of the organization, all keys together, per clock minute. Each response carries
RateLimit-Limit,RateLimit-RemainingandRateLimit-Reset(seconds); a 429 addsRetry-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 …"}}| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_request | A missing or malformed field, parameter or body. The message names it. |
| 401 | unauthorized | No key, or a key that is wrong, revoked, or whose creator left the organization. |
| 403 | plan_without_api | The organization's plan does not include the API. |
| 403 | subscription_inactive | The subscription has ended or is not active yet. |
| 404 | not_found | No such library, batch, job or file in your organization. |
| 409 | job_live | The job is still queued or running; cancel it first. |
| 410 | gone | The job's part library or a hide batch was deleted, so its files cannot be written. |
| 413 | payload_too_large | The file is larger than 200 MB unpacked. |
| 422 | unreadable_file | No closed part outline or hide could be read from the file, or contours cross themselves. |
| 422 | job_rejected | The job cannot start: engine minutes used up or held by running jobs, a plan limit, a part wider than the roll. |
| 429 | rate_limited | Too many requests this minute. Wait for the Retry-After seconds. |
| 500 | internal | Our 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.dxfReturns 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.dxfReturns 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.dxfReturns 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.