REST API
Every function of CutStock can be controlled via REST API – pieces by their code, materials by their short code.
Every function of the user interface is also available via the REST API under /api/ -- the interface itself uses nothing else. All endpoints accept and return JSON. CutStock serves an automatically generated, interactive description of all endpoints at /docs.
For programs, scanners and machines:
- Materials can be addressed by ID or short code (
AP40), pieces by ID or code (AP40-07, from the label), locations by ID or name. - Errors of the stock commands carry a fixed code:
{"detail": {"code": "stueck_reserviert", "message": "...", "projekt": "..."}}. - Every stock booking is written to the stock journal, with source
apiorui.
Authentication
When an API key is configured (Settings page), external requests must include it as a Bearer token:
curl -H "Authorization: Bearer YOUR_API_KEY" http://cutstock:8420/api/materials
Requests from the web UI (same-origin) and from localhost are always allowed without a key.
Endpoints
| Method | Path | Description |
|---|---|---|
| Materials | ||
| GET | /api/materials |
List all materials |
| POST | /api/materials |
Create a material |
| PUT | /api/materials/{id} |
Update a material |
| DELETE | /api/materials/{id} |
Delete a material |
| Stock | ||
| GET | /api/stock?material_id=N |
List stock (optional filter) |
| POST | /api/stock |
Create stock entry |
| PUT | /api/stock/{id} |
Update stock entry |
| DELETE | /api/stock/{id} |
Delete stock entry |
| GET | /api/stock/{id}/pieces |
List individual pieces (code, location, reserved for) |
| GET | /api/pieces/{id or code} |
Look up a piece: material, size, location, reservation |
| PUT | /api/pieces/{id or code} |
Change a piece's note |
| GET/POST/DELETE | /api/materials/{id}/image |
Read, upload, remove material image |
| Stock locations (see below) | ||
| GET | /api/locations |
Locations with number of materials and pieces |
| POST | /api/locations |
Create a location {"name": "Rack 3"} |
| PUT | /api/locations/{id or name} |
Rename a location |
| DELETE | /api/locations/{id or name}?ziel=… |
Delete a location; if in use, ziel (target) is required |
| GET | /api/locations/{id or name}/contents |
What is stored there: materials with pieces |
| PUT | /api/materials/{id or short code}/location |
Assign a material to a location |
| POST | /api/stock/receive |
Receive stock: add pieces, returns the new codes |
| POST | /api/pieces/{id or code}/take |
Take a piece out (with reason) |
| GET | /api/waste |
Contents of the offcut bin |
| POST | /api/waste |
Put offcuts into the bin |
| POST | /api/waste/take |
Take offcuts out of the bin |
| DELETE | /api/waste?material=… |
Empty the bin (all or per material) |
| GET | /api/journal?material=…&seit=… |
Stock journal, newest first |
| Defects | ||
| GET | /api/stock/{id}/defects |
Defects on a stock entry |
| GET | /api/pieces/{id}/defects |
Defects on a piece |
| POST | /api/pieces/{id}/defects |
Add a defect |
| PUT | /api/defects/{id} |
Update a defect |
| DELETE | /api/defects/{id} |
Delete a defect |
| Saw Blades | ||
| GET | /api/blades |
List saw blades |
| POST | /api/blades |
Create a blade |
| PUT | /api/blades/{id} |
Update a blade |
| DELETE | /api/blades/{id} |
Delete a blade |
| Projects | ||
| GET | /api/projects?art= |
List projects; art=sammel combined plans only, art=alle both (without: projects only) |
| GET | /api/projects/{id} |
Get a project |
| POST | /api/projects |
Create a project |
| PUT | /api/projects/{id} |
Update a project (name, number of copies) |
| DELETE | /api/projects/{id} |
Delete a project or combined plan (cut parts stay in the projects) |
| POST | /api/projects/{id}/clone |
Duplicate a project |
| POST | /api/projects/{id}/copy-parts |
Copy parts into a project |
| POST | /api/projects/{id}/restart |
Restart project (progress 0, plans removed, stock unchanged) |
| Parts | ||
| GET | /api/projects/{id}/parts |
List parts of a project |
| POST | /api/projects/{id}/parts |
Add a part |
| PUT | /api/parts/{id} |
Update a part (optional vorrat: set shelf stock) |
| DELETE | /api/parts/{id} |
Delete a part |
| Optimization and Plans | ||
| POST | /api/optimize |
Optimize and save as the project's plan for the material |
| POST | /api/plans/{id}/fill-choice |
Choose the filling of a group of equal bar offcuts (wahl: null = automatic) |
| GET | /api/projects/{id}/plans/{material_id} |
Get the saved plan (null if there is none) |
| POST | /api/plan-pieces/{id} |
Mark a piece as cut / undo ({"gesaegt": true}), updates progress and stock |
| POST | /api/plans/{id}/confirm-all |
Mark all open pieces of the plan as cut |
| DELETE | /api/plans/{id} |
Discard plan (releases reserved pieces) |
| GET | /api/projects/{id}/fill-candidates/{material_id} |
Possible filler parts from other projects |
| GET | /api/sammel/{material_id} |
Combined plan status and eligible projects |
| POST | /api/sammel |
Create a combined plan (material and projects, no cutting plan yet) |
| PUT | /api/sammel/{id or code}/members |
Replace the members of a material's combined plan |
| POST | /api/sammel/optimize |
Create/replace and compute the combined plan |
| GET | /api/parts |
Parts of all projects ("Parts" tab) |
| POST | /api/cut-lists |
Create a cut list from individual parts |
| POST | /api/sammel/compare |
Compare separate vs. combined (not saved) |
| POST | /api/projects/{id}/take-stock/{material_id} |
Take part stock ("shelf") over as cut |
| Export | ||
| POST | /api/pdf |
Generate PDF cut plan |
| POST | /api/labels |
Generate label PDF from ready-made texts |
| POST | /api/labels/pieces |
Labels for stock pieces (select by code, location, material, filters) |
| System | ||
| GET | /api/version |
App version and links |
| GET | /api/license |
License status |
| GET | /api/settings |
Read settings |
| PUT | /api/settings |
Update settings |
| GET | /api/backup |
Download database backup (ZIP) |
| POST | /api/restore |
Restore from backup (ZIP) |
| GET | /api/i18n/{lang} |
Translations for a language |
| POST | /api/seed |
Create sample data (empty database only) |
| GET | /api/is-desktop |
Is CutStock running as the desktop app? |
| POST | /api/shutdown |
Quit the desktop app (desktop app only) |
Optimization request
POST /api/optimize
{
"project_id": 1,
"material_id": 3,
"blade_id": 1,
"algorithm": "nested",
"reste_zuerst": false,
"fuellteile": [{"teil_id": 42, "modus": "auffuellen", "menge": 0}],
"fuell_alle_reste": false
}
Algorithm choices: greedy, nested, ga. reste_zuerst (remnants first), fuellteile (filler parts) and fuell_alle_reste are optional (filler mode auffuellen = fill or fest = fixed quantity; without fuellteile the saved plan's selection is kept).
The response is the saved plan: plan_id, schnittplaene (per board e.g. stueck_code, lagerort, platzierungen with id and gesaegt), fehlende_teile (parts that didn't fit), gesamt_verschnitt_prozent (total waste %), materialbedarf (additional material needed -- empty when everything fits) and the hints veraltet (outdated) and lager_fehlt (stock missing).
Stock commands
Receive stock -- returns the codes for labelling:
POST /api/stock/receive
{"material": "AP40", "laenge": 6000, "anzahl": 4}
→ {"stock_id": 12, "codes": ["AP40-21", "AP40-22", "AP40-23", "AP40-24"], "lagerort": "Rack Alu"}
breite (width) only for panels, ist_rest: true for offcuts (stored at the offcut location).
Take a piece out:
POST /api/pieces/AP40-07/take
{"grund": "verbraucht", "notiz": "prototype"}
grund (reason): verbraucht (used), verkauft (sold), defekt (defective) or abfall (into the offcut bin, if switched on).
If a saved plan reserves the piece, the answer is 409 with code: "stueck_reserviert" and the project; with "trotzdem": true it is taken out anyway. The plan then reports lager_fehlt and fehlende_stuecke.
Labels for stock pieces -- e.g. all pieces on a rack that have no label yet:
POST /api/labels/pieces
{"lagerort": "Wandregal Alu", "nur_ungedruckt": true, "startfeld": 5}
Select with codes (list of codes or IDs) or lagerort (location) and/or material; narrow down with nur_reste (offcuts only), nur_ganze (whole pieces only), nur_ungedruckt (not printed yet). startfeld skips fields already used on an A4 sheet. The answer is the PDF (header X-Label-Count = number of labels); the pieces then count as printed (etikett_gedruckt on the piece = Unix time, 0 = never), unless "markieren": false. No selection gives 400 auswahl_fehlt, nothing matching 404 keine_stuecke.
Assign a material to a location:
PUT /api/materials/AP40/location
{"lagerort": "Rack Alu", "lagerort_reste": "Offcut box Alu"}
lagerort_reste: "" = offcuts stay with the material. When creating/updating a material via /api/materials, an unknown location name is created automatically.
Delete a location that is still in use:
DELETE /api/locations/Rack%203 → 409 {"code": "lagerort_in_benutzung", "materialien": [...]}
DELETE /api/locations/Rack%203?ziel=Stock → 204, the materials are now at "Stock"
Offcut bin (no codes, only material + size + quantity):
POST /api/waste {"material": "AP40", "laenge": 180, "anzahl": 2}
POST /api/waste/take {"material": "AP40", "laenge": 180}
DELETE /api/waste?material=AP40
Stock journal: GET /api/journal?material=AP40&seit=1767225600&limit=100 -- each entry has zeit (Unix time), art (zugang, korrektur_ab, entnahme, plan_verbrauch, plan_zurueck, rest_ein, rest_zurueck, abfall_ein, abfall_zurueck, abfall_ab, abfall_leer, material_lagerort, lagerort_neu, lagerort_umbenannt, lagerort_geloescht), code, sizes, anzahl, quelle (ui/api), notiz.
Combined plans
A combined plan is a project with art: "sammel", sammel_material_id and mitglieder
(member project IDs); it has no parts of its own. GET /api/projects only lists it with
?art=sammel or ?art=alle; member projects name it under sammel ({material_id: name}).
POST /api/sammel
{"material": "AP40", "projekte": ["Workbench", "12"]}
→ 201 {"id": 31, "name": "Sammelplan Alu Profil 40x40", "art": "sammel", "sammel_material_id": 2, "mitglieder": [5, 12], ...}
Material by ID or code, projects by ID or name, name optional. If the material already has
one, you get 409 sammel_vorhanden with sammel_id; unknown references give
404 material_unbekannt or projekt_unbekannt. The plan is calculated with
POST /api/sammel/optimize.
Change members (a calculated plan is then veraltet, i.e. outdated):
PUT /api/sammel/AP40/members
{"projekte": ["Workbench"]}
Rename with PUT /api/projects/{id} (the number of copies stays 1), delete with
DELETE /api/projects/{id}: what has been cut already counts in the member projects
(gesaegt_anzahl), the rest becomes open again and reserved stock pieces are released. Adding,
copying or duplicating parts is not possible for a combined plan (400 sammel_ohne_teile).
Responses (examples)
One example for every endpoint: what request and response look like – generated with the real CutStock and a sample database (sample data in German). Long lists are shortened ("… n weitere" = n more). Numbers are always millimetres, zeit is Unix time. Errors come as {"detail": …}. Click an example to expand it.
System
GET /api/version Version 200
{
"version": "2026.10.74",
"repo": "orohde/CutStock",
"github_url": "https://github.com/orohde/CutStock",
"website_url": "https://cutstock.app/",
"releases_api": "https://api.github.com/repos/orohde/CutStock/releases/latest",
"docker": false
}GET /api/is-desktop Running as desktop app? 200
{
"desktop": false
}GET /api/license License 200
{
"valid": false,
"type": "community",
"holder": "",
"issued": null,
"expires": null,
"error": "no_key"
}GET /api/i18n/de Translations of a language (excerpt) 200
{
"app.title": "CutStock – Verschnittoptimierung",
"app.quit": "Beenden",
"app.quit_confirm": "CutStock wirklich beenden?",
"app.quit_done": "CutStock wurde beendet.",
"…": "491 weitere"
}Settings
GET /api/settings Read settings 200
{
"language": "de",
"unit": "mm",
"theme": "system",
"hotkeys": {},
"label_format": "a4_3x8",
"label_custom_w": 89,
"label_custom_h": 36,
"api_enabled": false,
"api_port": 8421,
"api_key": "",
"license_key": "",
"nr_lock_days": 1,
"reservation": true,
"waste_keep": false,
"waste_min": 50
}PUT /api/settings Change settings (only the given fields) 200
{
"waste_keep": true,
"waste_min": 50
}{
"language": "de",
"unit": "mm",
"theme": "system",
"hotkeys": {},
"label_format": "a4_3x8",
"label_custom_w": 89,
"label_custom_h": 36,
"api_enabled": false,
"api_port": 8421,
"api_key": "",
"license_key": "",
"nr_lock_days": 1,
"reservation": true,
"waste_keep": true,
"waste_min": 50.0
}Materials
POST /api/materials Create material 201
{
"name": "Alu Profil 40x40 Nut 8",
"typ": "Stange",
"querschnitt_breite": 40,
"querschnitt_tiefe": 40,
"rest_min_laenge": 300,
"kuerzel": "AP40",
"lagerort": "Wandregal Alu",
"lagerort_reste": "Restekiste Alu"
}{
"id": 1,
"name": "Alu Profil 40x40 Nut 8",
"typ": "Stange",
"dicke": 0.0,
"querschnitt_breite": 40.0,
"querschnitt_tiefe": 40.0,
"maserung": "keine",
"drehung_erlaubt": true,
"besaeumung": 0.0,
"rest_min_laenge": 300.0,
"rest_min_breite": 0.0,
"hat_bild": false,
"kuerzel": "AP40",
"lagerort": "Wandregal Alu",
"lagerort_reste": "Restekiste Alu"
}GET /api/materials All materials 200
[
{
"id": 1,
"name": "Alu Profil 40x40 Nut 8",
"typ": "Stange",
"dicke": 0.0,
"querschnitt_breite": 40.0,
"querschnitt_tiefe": 40.0,
"maserung": "keine",
"drehung_erlaubt": true,
"besaeumung": 0.0,
"rest_min_laenge": 300.0,
"rest_min_breite": 0.0,
"hat_bild": false,
"kuerzel": "AP40",
"lagerort": "Wandregal Alu",
"lagerort_reste": "Restekiste Alu"
},
{
"id": 2,
"name": "MDF 19 mm",
"typ": "Platte",
"dicke": 19.0,
"querschnitt_breite": 0.0,
"querschnitt_tiefe": 0.0,
"maserung": "keine",
"drehung_erlaubt": true,
"besaeumung": 0.0,
"rest_min_laenge": 200.0,
"rest_min_breite": 200.0,
"hat_bild": false,
"kuerzel": "MDF19",
"lagerort": "Plattenwagen",
"lagerort_reste": ""
}
]PUT /api/materials/1 Change material (omit location = unchanged) 200
{
"name": "Alu Profil 40x40 Nut 8",
"typ": "Stange",
"querschnitt_breite": 40,
"querschnitt_tiefe": 40,
"rest_min_laenge": 250,
"kuerzel": "AP40"
}{
"id": 1,
"name": "Alu Profil 40x40 Nut 8",
"typ": "Stange",
"dicke": 0.0,
"querschnitt_breite": 40.0,
"querschnitt_tiefe": 40.0,
"maserung": "keine",
"drehung_erlaubt": true,
"besaeumung": 0.0,
"rest_min_laenge": 250.0,
"rest_min_breite": 0.0,
"hat_bild": false,
"kuerzel": "AP40",
"lagerort": "Wandregal Alu",
"lagerort_reste": "Restekiste Alu"
}POST /api/materials/1/image Upload image 204
multipart/form-data, field "file" = profil.png (image/png)(no content, status 204)GET /api/materials/1/image Get image 200
File: image/png, 69 BytesDELETE /api/materials/1/image Delete image 204
(no content, status 204)DELETE /api/materials/3 Delete material 204
(no content, status 204)Stock
POST /api/stock/receive Receive: book pieces into stock 201
{
"material": "AP40",
"laenge": 6000,
"anzahl": 4
}{
"stock_id": 1,
"codes": [
"AP40-01",
"AP40-02",
"… 2 weitere"
],
"lagerort": "Wandregal Alu"
}POST /api/stock/receive Receive an offcut 201
{
"material": "AP40",
"laenge": 740,
"anzahl": 1,
"ist_rest": true
}{
"stock_id": 2,
"codes": [
"AP40-05"
],
"lagerort": "Restekiste Alu"
}POST /api/stock Create stock row (as in the material dialog) 201
{
"material_id": 2,
"laenge": 2800,
"breite": 2070,
"stueckzahl": 2
}{
"id": 3,
"material_id": 2,
"laenge": 2800.0,
"breite": 2070.0,
"stueckzahl": 2,
"ist_rest": false
}GET /api/stock?material_id=1 Stock rows of a material 200
[
{
"id": 1,
"material_id": 1,
"laenge": 6000.0,
"breite": 0.0,
"stueckzahl": 4,
"ist_rest": false
},
{
"id": 2,
"material_id": 1,
"laenge": 740.0,
"breite": 0.0,
"stueckzahl": 1,
"ist_rest": true
}
]PUT /api/stock/3 Change stock row (quantity) 200
{
"material_id": 2,
"laenge": 2800,
"breite": 2070,
"stueckzahl": 3
}{
"id": 3,
"material_id": 2,
"laenge": 2800.0,
"breite": 2070.0,
"stueckzahl": 3,
"ist_rest": false
}GET /api/stock/1/pieces Pieces of a stock row 200
[
{
"id": 1,
"lagerstueck_id": 1,
"nr": 1,
"notiz": "",
"defekt_count": 0,
"code": "AP40-01",
"lagerort": "Wandregal Alu",
"lagerort_effektiv": "Wandregal Alu",
"reserviert_fuer": "",
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"laenge": 6000.0,
"breite": 0.0,
"ist_rest": false,
"etikett_gedruckt": 0.0
},
{
"id": 2,
"lagerstueck_id": 1,
"nr": 2,
"notiz": "",
"defekt_count": 0,
"code": "AP40-02",
"lagerort": "Wandregal Alu",
"lagerort_effektiv": "Wandregal Alu",
"reserviert_fuer": "",
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"laenge": 6000.0,
"breite": 0.0,
"ist_rest": false,
"etikett_gedruckt": 0.0
},
"… 2 weitere"
]GET /api/pieces/AP40-02 Piece by its code (or ID) 200
{
"id": 2,
"lagerstueck_id": 1,
"nr": 2,
"notiz": "",
"defekt_count": 0,
"code": "AP40-02",
"lagerort": "Wandregal Alu",
"lagerort_effektiv": "Wandregal Alu",
"reserviert_fuer": "",
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"laenge": 6000.0,
"breite": 0.0,
"ist_rest": false,
"etikett_gedruckt": 0.0
}PUT /api/pieces/AP40-02 Note on a piece 200
{
"notiz": "leicht verkratzt"
}{
"id": 2,
"lagerstueck_id": 1,
"nr": 2,
"notiz": "leicht verkratzt",
"defekt_count": 0,
"code": "AP40-02",
"lagerort": "Wandregal Alu",
"lagerort_effektiv": "Wandregal Alu",
"reserviert_fuer": "",
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"laenge": 6000.0,
"breite": 0.0,
"ist_rest": false,
"etikett_gedruckt": 0.0
}DELETE /api/stock/4 Delete stock row 204
(no content, status 204)POST /api/pieces/AP40-02/take Error: piece is reserved 409
{
"grund": "verbraucht"
}{
"detail": {
"code": "stueck_reserviert",
"message": "AP40-02 is reserved by “Werkstattregal”",
"projekt": "Werkstattregal"
}
}POST /api/pieces/AP40-03/take Take out a piece 200
{
"grund": "verkauft",
"notiz": "an Nachbar"
}{
"code": "AP40-03",
"grund": "verkauft",
"reserviert_fuer": "",
"abfallkiste": false
}Defects
POST /api/pieces/4/defects Add defect (position from the start) 201
{
"position": 1200,
"laenge": 80
}{
"id": 1,
"einzelstueck_id": 4,
"stueck_nr": 4,
"position": 1200.0,
"position_y": 0.0,
"laenge": 80.0,
"breite": 0.0
}GET /api/pieces/4/defects Defects of a piece 200
[
{
"id": 1,
"einzelstueck_id": 4,
"stueck_nr": 4,
"position": 1200.0,
"position_y": 0.0,
"laenge": 80.0,
"breite": 0.0
}
]GET /api/stock/1/defects Defects of a stock row 200
[
{
"id": 1,
"einzelstueck_id": 4,
"stueck_nr": 4,
"position": 1200.0,
"position_y": 0.0,
"laenge": 80.0,
"breite": 0.0
}
]PUT /api/defects/1 Change defect 200
{
"position": 1200,
"laenge": 120
}{
"id": 1,
"einzelstueck_id": 4,
"stueck_nr": 4,
"position": 1200.0,
"position_y": 0.0,
"laenge": 120.0,
"breite": 0.0
}DELETE /api/defects/1 Delete defect 204
(no content, status 204)Locations
GET /api/locations All locations 200
[
{
"id": 4,
"name": "Lager",
"materialien": 0,
"stuecke": 0
},
{
"id": 3,
"name": "Plattenwagen",
"materialien": 1,
"stuecke": 3
},
"… 2 weitere"
]POST /api/locations Create location 201
{
"name": "Regal 3"
}{
"id": 5,
"name": "Regal 3",
"materialien": 0,
"stuecke": 0
}PUT /api/locations/Regal 3 Rename location 200
{
"name": "Regal 3 oben"
}{
"id": 5,
"name": "Regal 3 oben",
"materialien": 0,
"stuecke": 0
}GET /api/locations/Wandregal Alu/contents What is stored at a location 200
[
{
"material_id": 1,
"name": "Alu Profil 40x40 Nut 8",
"kuerzel": "AP40",
"typ": "Stange",
"rolle": "bestand",
"bestand": [
{
"stock_id": 1,
"laenge": 6000.0,
"breite": 0.0,
"stueckzahl": 4,
"ist_rest": false,
"stuecke": [
{
"id": 1,
"lagerstueck_id": 1,
"nr": 1,
"notiz": "",
"defekt_count": 0,
"code": "AP40-01",
"lagerort": "Wandregal Alu",
"lagerort_effektiv": "Wandregal Alu",
"reserviert_fuer": "",
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"laenge": 6000.0,
"breite": 0.0,
"ist_rest": false,
"etikett_gedruckt": 0.0
},
"… 3 weitere"
]
}
]
}
]PUT /api/materials/AP40/location Assign material 200
{
"lagerort": "Wandregal Alu",
"lagerort_reste": "Restekiste Alu"
}{
"id": 1,
"name": "Alu Profil 40x40 Nut 8",
"typ": "Stange",
"dicke": 0.0,
"querschnitt_breite": 40.0,
"querschnitt_tiefe": 40.0,
"maserung": "keine",
"drehung_erlaubt": true,
"besaeumung": 0.0,
"rest_min_laenge": 250.0,
"rest_min_breite": 0.0,
"hat_bild": false,
"kuerzel": "AP40",
"lagerort": "Wandregal Alu",
"lagerort_reste": "Restekiste Alu"
}DELETE /api/locations/Wandregal Alu Error: location still in use 409
{
"detail": {
"code": "lagerort_in_benutzung",
"message": "Location is still used by materials",
"materialien": [
"Alu Profil 40x40 Nut 8"
]
}
}DELETE /api/locations/Regal 3 oben Delete empty location (with ?ziel=… also one in use) 204
(no content, status 204)Saw blades
POST /api/blades Create saw blade 201
{
"name": "Kappsäge 2,8 mm",
"schnittbreite": 2.8
}{
"id": 1,
"name": "Kappsäge 2,8 mm",
"schnittbreite": 2.8
}GET /api/blades All saw blades 200
[
{
"id": 1,
"name": "Kappsäge 2,8 mm",
"schnittbreite": 2.8
}
]PUT /api/blades/2 Change saw blade 200
{
"name": "Japansäge",
"schnittbreite": 0.8
}{
"id": 2,
"name": "Japansäge",
"schnittbreite": 0.8
}DELETE /api/blades/2 Delete saw blade 204
(no content, status 204)Projects
POST /api/projects Create project 201
{
"name": "Werkstattregal",
"anzahl": 1
}{
"id": 1,
"name": "Werkstattregal",
"anzahl": 1,
"teile": [],
"plaene": [],
"sammel": {},
"art": "",
"sammel_material_id": 0,
"mitglieder": []
}POST /api/projects/1/parts Add part 201
{
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1800,
"stueckzahl": 4
}{
"id": 1,
"projekt_id": 1,
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1800.0,
"breite": 0.0,
"stueckzahl": 4,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 4,
"gesamt_anzahl": 4,
"vorrat": 0,
"herkunft_teil_id": 0
}PUT /api/parts/1 Change part 200
{
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1750,
"stueckzahl": 4
}{
"id": 1,
"projekt_id": 1,
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1750.0,
"breite": 0.0,
"stueckzahl": 4,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 4,
"gesamt_anzahl": 4,
"vorrat": 0,
"herkunft_teil_id": 0
}GET /api/projects/1/parts Parts of a project 200
[
{
"id": 1,
"projekt_id": 1,
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1750.0,
"breite": 0.0,
"stueckzahl": 4,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 4,
"gesamt_anzahl": 4,
"vorrat": 0,
"herkunft_teil_id": 0
},
{
"id": 2,
"projekt_id": 1,
"label": "Riegel",
"typ": "Stange",
"material_id": 1,
"laenge": 760.0,
"breite": 0.0,
"stueckzahl": 6,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 6,
"gesamt_anzahl": 6,
"vorrat": 0,
"herkunft_teil_id": 0
}
]PUT /api/projects/1 Change project (2 copies) 200
{
"name": "Werkstattregal",
"anzahl": 2
}{
"id": 1,
"name": "Werkstattregal",
"anzahl": 2,
"teile": [
{
"id": 1,
"projekt_id": 1,
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1750.0,
"breite": 0.0,
"stueckzahl": 4,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 8,
"gesamt_anzahl": 8,
"vorrat": 0,
"herkunft_teil_id": 0
},
{
"id": 2,
"projekt_id": 1,
"label": "Riegel",
"typ": "Stange",
"material_id": 1,
"laenge": 760.0,
"breite": 0.0,
"stueckzahl": 6,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 12,
"gesamt_anzahl": 12,
"vorrat": 0,
"herkunft_teil_id": 0
}
],
"plaene": [],
"sammel": {},
"art": "",
"sammel_material_id": 0,
"mitglieder": []
}GET /api/projects/1 A project with parts and plans 200
{
"id": 1,
"name": "Werkstattregal",
"anzahl": 1,
"teile": [
{
"id": 1,
"projekt_id": 1,
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1750.0,
"breite": 0.0,
"stueckzahl": 4,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 4,
"gesamt_anzahl": 4,
"vorrat": 0,
"herkunft_teil_id": 0
},
{
"id": 2,
"projekt_id": 1,
"label": "Riegel",
"typ": "Stange",
"material_id": 1,
"laenge": 760.0,
"breite": 0.0,
"stueckzahl": 6,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 6,
"gesamt_anzahl": 6,
"vorrat": 0,
"herkunft_teil_id": 0
}
],
"plaene": [],
"sammel": {},
"art": "",
"sammel_material_id": 0,
"mitglieder": []
}POST /api/projects/1/clone Copy project 201
{
"id": 2,
"name": "Werkstattregal (Kopie)",
"anzahl": 1,
"teile": [
{
"id": 3,
"projekt_id": 2,
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1750.0,
"breite": 0.0,
"stueckzahl": 4,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 4,
"gesamt_anzahl": 4,
"vorrat": 0,
"herkunft_teil_id": 0
},
{
"id": 4,
"projekt_id": 2,
"label": "Riegel",
"typ": "Stange",
"material_id": 1,
"laenge": 760.0,
"breite": 0.0,
"stueckzahl": 6,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 6,
"gesamt_anzahl": 6,
"vorrat": 0,
"herkunft_teil_id": 0
}
],
"plaene": [],
"sammel": {},
"art": "",
"sammel_material_id": 0,
"mitglieder": []
}POST /api/projects/3/copy-parts Copy parts into another project 201
{
"part_ids": [
1
]
}[
{
"id": 5,
"projekt_id": 3,
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1750.0,
"breite": 0.0,
"stueckzahl": 4,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 4,
"gesamt_anzahl": 4,
"vorrat": 0,
"herkunft_teil_id": 0
}
]DELETE /api/parts/7 Delete part 204
(no content, status 204)DELETE /api/projects/2 Delete project 204
(no content, status 204)GET /api/projects All projects 200
[
{
"id": 3,
"name": "Schubladenschrank",
"anzahl": 1,
"teile": [
{
"id": 6,
"projekt_id": 3,
"label": "Leiste",
"typ": "Stange",
"material_id": 1,
"laenge": 200.0,
"breite": 0.0,
"stueckzahl": 10,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 10,
"gesamt_anzahl": 10,
"vorrat": 0,
"herkunft_teil_id": 0
},
"… 1 weitere"
],
"plaene": [],
"sammel": {},
"art": "",
"sammel_material_id": 0,
"mitglieder": []
},
"… 1 weitere"
]Parts and cut lists
GET /api/parts Parts of all projects (Parts tab) 200
[
{
"teil_id": 6,
"label": "Leiste",
"projekt_id": 3,
"projekt": "Schubladenschrank",
"material_id": 1,
"laenge": 200.0,
"breite": 0.0,
"offen": 10,
"gesamt": 10,
"vorrat": 0
},
{
"teil_id": 5,
"label": "Pfosten",
"projekt_id": 3,
"projekt": "Schubladenschrank",
"material_id": 1,
"laenge": 1750.0,
"breite": 0.0,
"offen": 4,
"gesamt": 4,
"vorrat": 0
},
"… 2 weitere"
]POST /api/cut-lists Create a cut list from parts 201
{
"name": "Nachschub Leisten",
"eintraege": [
{
"teil_id": 6,
"menge": 4
}
]
}{
"id": 4,
"name": "Nachschub Leisten",
"anzahl": 1,
"teile": [
{
"id": 8,
"projekt_id": 4,
"label": "Leiste (Schubladenschrank)",
"typ": "Stange",
"material_id": 1,
"laenge": 200.0,
"breite": 0.0,
"stueckzahl": 4,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 4,
"gesamt_anzahl": 4,
"vorrat": 0,
"herkunft_teil_id": 6
}
],
"plaene": [],
"sammel": {},
"art": "liste",
"sammel_material_id": 0,
"mitglieder": []
}Optimization
GET /api/projects/1/fill-candidates/1 Possible filler parts from other projects 200
[
{
"teil_id": 6,
"label": "Leiste",
"projekt": "Schubladenschrank",
"projekt_id": 3,
"laenge": 200.0,
"breite": 0.0,
"offen": 10,
"vorrat": 0
},
{
"teil_id": 5,
"label": "Pfosten",
"projekt": "Schubladenschrank",
"projekt_id": 3,
"laenge": 1750.0,
"breite": 0.0,
"offen": 4,
"vorrat": 0
}
]POST /api/optimize Optimize (response = saved plan) 200
{
"project_id": 1,
"material_id": 1,
"blade_id": 1,
"algorithm": "nested",
"fuellteile": [
{
"teil_id": 6,
"modus": "auffuellen",
"menge": 0
}
]
}{
"schnittplaene": [
{
"lagerstueck_id": 1,
"lager_laenge": 6000.0,
"lager_breite": 0.0,
"stueck_nr": 1,
"platzierungen": [
{
"teil_label": "Pfosten",
"laenge": 1750.0,
"breite": 0.0,
"x": 0.0,
"y": 0.0,
"gedreht": false,
"id": 1,
"gesaegt": false,
"ist_fuellteil": false
},
"… 5 weitere"
],
"reste": [
[
3.1999999999989086
]
],
"verschnitt_mm": 6.0,
"verschnitt_prozent": 0.1,
"defekte": [],
"id": 1,
"verbraucht": false,
"stueck_code": "AP40-01",
"lagerort": "Wandregal Alu",
"ist_rest": false,
"reste_stuecke": [],
"reste_ziel": [
"weg"
]
},
"… 1 weitere"
],
"fehlende_teile": [],
"gesamt_verschnitt_prozent": 0.3,
"materialbedarf": [],
"plan_id": 1,
"projekt_id": 1,
"material_id": 1,
"saegeblatt_id": 1,
"algorithmus": "nested",
"erstellt": 1793530040.890228,
"version": 1,
"veraltet": false,
"lager_fehlt": false,
"fehlende_stuecke": [],
"fuellteile": [
{
"teil_id": 6,
"modus": "auffuellen",
"menge": 0
}
],
"fuell_alle_reste": false,
"vorrat_verfuegbar": [],
"max_pro_stange": 0,
"fuell_vorschlaege": [
{
"gruppe": "206.0",
"rest": 206.0,
"stangen": 2,
"vorschlaege": [
{
"teile": [
{
"teil_id": 6,
"label": "Leiste → Schubladenschrank",
"laenge": 200.0,
"anzahl": 1
}
],
"rest": 3.2
}
],
"gewaehlt": 0,
"manuell": false,
"automatisch": 0
}
]
}POST /api/plans/1/fill-choice Choose the filling of an offcut group (null = automatic) 200
{
"gruppe": "206.0",
"wahl": null
}{
"schnittplaene": [
{
"lagerstueck_id": 1,
"lager_laenge": 6000.0,
"lager_breite": 0.0,
"stueck_nr": 1,
"platzierungen": [
{
"teil_label": "Pfosten",
"laenge": 1750.0,
"breite": 0.0,
"x": 0.0,
"y": 0.0,
"gedreht": false,
"id": 13,
"gesaegt": false,
"ist_fuellteil": false
},
"… 5 weitere"
],
"reste": [
[
3.1999999999989086
]
],
"verschnitt_mm": 6.0,
"verschnitt_prozent": 0.1,
"defekte": [],
"id": 3,
"verbraucht": false,
"stueck_code": "AP40-01",
"lagerort": "Wandregal Alu",
"ist_rest": false,
"reste_stuecke": [],
"reste_ziel": [
"weg"
]
},
"… 1 weitere"
],
"fehlende_teile": [],
"gesamt_verschnitt_prozent": 0.3,
"materialbedarf": [],
"plan_id": 2,
"projekt_id": 1,
"material_id": 1,
"saegeblatt_id": 1,
"algorithmus": "nested",
"erstellt": 1793530040.890228,
"version": 2,
"veraltet": false,
"lager_fehlt": false,
"fehlende_stuecke": [],
"fuellteile": [
{
"teil_id": 6,
"modus": "auffuellen",
"menge": 0
}
],
"fuell_alle_reste": false,
"vorrat_verfuegbar": [],
"max_pro_stange": 0,
"fuell_vorschlaege": [
{
"gruppe": "206.0",
"rest": 206.0,
"stangen": 2,
"vorschlaege": [
{
"teile": [
{
"teil_id": 6,
"label": "Leiste → Schubladenschrank",
"laenge": 200.0,
"anzahl": 1
}
],
"rest": 3.2
}
],
"gewaehlt": 0,
"manuell": false,
"automatisch": 0
}
]
}GET /api/projects/1/plans/1 Get saved plan 200
{
"schnittplaene": [
{
"lagerstueck_id": 1,
"lager_laenge": 6000.0,
"lager_breite": 0.0,
"stueck_nr": 1,
"platzierungen": [
{
"teil_label": "Pfosten",
"laenge": 1750.0,
"breite": 0.0,
"x": 0.0,
"y": 0.0,
"gedreht": false,
"id": 13,
"gesaegt": false,
"ist_fuellteil": false
},
"… 5 weitere"
],
"reste": [
[
3.1999999999989086
]
],
"verschnitt_mm": 6.0,
"verschnitt_prozent": 0.1,
"defekte": [],
"id": 3,
"verbraucht": false,
"stueck_code": "AP40-01",
"lagerort": "Wandregal Alu",
"ist_rest": false,
"reste_stuecke": [],
"reste_ziel": [
"weg"
]
},
"… 1 weitere"
],
"fehlende_teile": [],
"gesamt_verschnitt_prozent": 0.3,
"materialbedarf": [],
"plan_id": 2,
"projekt_id": 1,
"material_id": 1,
"saegeblatt_id": 1,
"algorithmus": "nested",
"erstellt": 1793530040.890228,
"version": 2,
"veraltet": false,
"lager_fehlt": false,
"fehlende_stuecke": [],
"fuellteile": [
{
"teil_id": 6,
"modus": "auffuellen",
"menge": 0
}
],
"fuell_alle_reste": false,
"vorrat_verfuegbar": [],
"max_pro_stange": 0,
"fuell_vorschlaege": [
{
"gruppe": "206.0",
"rest": 206.0,
"stangen": 2,
"vorschlaege": [
{
"teile": [
{
"teil_id": 6,
"label": "Leiste → Schubladenschrank",
"laenge": 200.0,
"anzahl": 1
}
],
"rest": 3.2
}
],
"gewaehlt": 0,
"manuell": false,
"automatisch": 0
}
]
}POST /api/plan-pieces/18 Tick off a piece as cut 200
{
"gesaegt": true
}{
"plan": {
"schnittplaene": [
{
"lagerstueck_id": 1,
"lager_laenge": 6000.0,
"lager_breite": 0.0,
"stueck_nr": 1,
"platzierungen": [
{
"teil_label": "Pfosten",
"laenge": 1750.0,
"breite": 0.0,
"x": 0.0,
"y": 0.0,
"gedreht": false,
"id": 13,
"gesaegt": false,
"ist_fuellteil": false
},
"… 5 weitere"
],
"reste": [
[
3.1999999999989086
]
],
"verschnitt_mm": 6.0,
"verschnitt_prozent": 0.1,
"defekte": [],
"id": 3,
"verbraucht": true,
"stueck_code": "AP40-01",
"lagerort": "Wandregal Alu",
"ist_rest": false,
"reste_stuecke": [
{
"code": "AP40-01",
"laenge": 5797.2,
"breite": 0.0,
"lagerort": "Restekiste Alu"
}
],
"reste_ziel": [
"weg"
]
},
"… 1 weitere"
],
"fehlende_teile": [],
"gesamt_verschnitt_prozent": 0.3,
"materialbedarf": [],
"plan_id": 2,
"projekt_id": 1,
"material_id": 1,
"saegeblatt_id": 1,
"algorithmus": "nested",
"erstellt": 1793530040.890228,
"version": 3,
"veraltet": false,
"lager_fehlt": false,
"fehlende_stuecke": [],
"fuellteile": [
{
"teil_id": 6,
"modus": "auffuellen",
"menge": 0
}
],
"fuell_alle_reste": false,
"vorrat_verfuegbar": [],
"max_pro_stange": 0,
"fuell_vorschlaege": [
{
"gruppe": "206.0",
"rest": 206.0,
"stangen": 2,
"vorschlaege": [
{
"teile": [
{
"teil_id": 6,
"label": "Leiste → Schubladenschrank",
"laenge": 200.0,
"anzahl": 1
}
],
"rest": 3.2
}
],
"gewaehlt": 0,
"manuell": false,
"automatisch": 0
}
]
},
"teil": {
"id": 6,
"projekt_id": 3,
"label": "Leiste",
"typ": "Stange",
"material_id": 1,
"laenge": 200.0,
"breite": 0.0,
"stueckzahl": 10,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 10,
"gesamt_anzahl": 10,
"vorrat": 1,
"herkunft_teil_id": 0
}
}POST /api/pdf Cut plan as PDF 200
{
"ergebnis": {
"schnittplaene": [
{
"lagerstueck_id": 1,
"lager_laenge": 6000.0,
"lager_breite": 0.0,
"stueck_nr": 1,
"platzierungen": [
{
"teil_label": "Pfosten",
"laenge": 1750.0,
"breite": 0.0,
"x": 0.0,
"y": 0.0,
"gedreht": false,
"id": 13,
"gesaegt": false,
"ist_fuellteil": false
},
"… 5 weitere"
],
"reste": [
[
3.1999999999989086
]
],
"verschnitt_mm": 6.0,
"verschnitt_prozent": 0.1,
"defekte": [],
"id": 3,
"verbraucht": false,
"stueck_code": "AP40-01",
"lagerort": "Wandregal Alu",
"ist_rest": false,
"reste_stuecke": [],
"reste_ziel": [
"weg"
]
},
"… 1 weitere"
],
"fehlende_teile": [],
"gesamt_verschnitt_prozent": 0.3,
"materialbedarf": [],
"plan_id": 2,
"projekt_id": 1,
"material_id": 1,
"saegeblatt_id": 1,
"algorithmus": "nested",
"erstellt": 1793530040.890228,
"version": 2,
"veraltet": false,
"lager_fehlt": false,
"fehlende_stuecke": [],
"fuellteile": [
{
"teil_id": 6,
"modus": "auffuellen",
"menge": 0
}
],
"fuell_alle_reste": false,
"vorrat_verfuegbar": [],
"max_pro_stange": 0,
"fuell_vorschlaege": [
{
"gruppe": "206.0",
"rest": 206.0,
"stangen": 2,
"vorschlaege": [
{
"teile": [
{
"teil_id": 6,
"label": "Leiste → Schubladenschrank",
"laenge": 200.0,
"anzahl": 1
}
],
"rest": 3.2
}
],
"gewaehlt": 0,
"manuell": false,
"automatisch": 0
}
]
},
"projekt_name": "Werkstattregal",
"material_name": "Alu Profil 40x40 Nut 8",
"material_id": 1,
"saegeblatt_name": "Kappsäge 2,8 mm",
"is_1d": true
}File: application/pdf, 2,788 Bytes
Content-Disposition: attachment; filename="CutStock-Werkstattregal.pdf"POST /api/plans/2/confirm-all Mark all pieces as cut 200
{
"schnittplaene": [
{
"lagerstueck_id": 1,
"lager_laenge": 6000.0,
"lager_breite": 0.0,
"stueck_nr": 1,
"platzierungen": [
{
"teil_label": "Pfosten",
"laenge": 1750.0,
"breite": 0.0,
"x": 0.0,
"y": 0.0,
"gedreht": false,
"id": 13,
"gesaegt": true,
"ist_fuellteil": false
},
"… 5 weitere"
],
"reste": [
[
3.1999999999989086
]
],
"verschnitt_mm": 6.0,
"verschnitt_prozent": 0.1,
"defekte": [],
"id": 3,
"verbraucht": true,
"stueck_code": "AP40-01",
"lagerort": "Wandregal Alu",
"ist_rest": false,
"reste_stuecke": [],
"reste_ziel": [
"weg"
]
},
"… 1 weitere"
],
"fehlende_teile": [],
"gesamt_verschnitt_prozent": 0.3,
"materialbedarf": [],
"plan_id": 2,
"projekt_id": 1,
"material_id": 1,
"saegeblatt_id": 1,
"algorithmus": "nested",
"erstellt": 1793530040.890228,
"version": 4,
"veraltet": false,
"lager_fehlt": false,
"fehlende_stuecke": [],
"fuellteile": [
{
"teil_id": 6,
"modus": "auffuellen",
"menge": 0
}
],
"fuell_alle_reste": false,
"vorrat_verfuegbar": [],
"max_pro_stange": 0,
"fuell_vorschlaege": [
{
"gruppe": "206.0",
"rest": 206.0,
"stangen": 2,
"vorschlaege": [
{
"teile": [
{
"teil_id": 6,
"label": "Leiste → Schubladenschrank",
"laenge": 200.0,
"anzahl": 1
}
],
"rest": 3.2
}
],
"gewaehlt": 0,
"manuell": false,
"automatisch": 0
}
]
}POST /api/projects/3/take-stock/1 Take over the parts shelf stock as cut 200
{
"id": 3,
"name": "Schubladenschrank",
"anzahl": 1,
"teile": [
{
"id": 6,
"projekt_id": 3,
"label": "Leiste",
"typ": "Stange",
"material_id": 1,
"laenge": 200.0,
"breite": 0.0,
"stueckzahl": 10,
"gesaegt_anzahl": 2,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 8,
"gesamt_anzahl": 10,
"vorrat": 0,
"herkunft_teil_id": 0
},
"… 1 weitere"
],
"plaene": [],
"sammel": {},
"art": "",
"sammel_material_id": 0,
"mitglieder": []
}POST /api/projects/1/restart Cut the project again 200
{
"id": 1,
"name": "Werkstattregal",
"anzahl": 1,
"teile": [
{
"id": 1,
"projekt_id": 1,
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1750.0,
"breite": 0.0,
"stueckzahl": 4,
"gesaegt_anzahl": 0,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 4,
"gesamt_anzahl": 4,
"vorrat": 0,
"herkunft_teil_id": 0
},
"… 1 weitere"
],
"plaene": [],
"sammel": {},
"art": "",
"sammel_material_id": 0,
"mitglieder": []
}DELETE /api/plans/3 Discard plan 204
(no content, status 204)Offcut bin
POST /api/waste Put scrap pieces in 201
{
"material": "AP40",
"laenge": 180,
"anzahl": 2
}[
{
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"kuerzel": "AP40",
"typ": "Stange",
"laenge": 180.0,
"breite": 0.0,
"anzahl": 2
}
]GET /api/waste Contents of the offcut bin 200
[
{
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"kuerzel": "AP40",
"typ": "Stange",
"laenge": 180.0,
"breite": 0.0,
"anzahl": 2
}
]POST /api/waste/take Take one scrap piece out 200
{
"material": "AP40",
"laenge": 180
}[
{
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"kuerzel": "AP40",
"typ": "Stange",
"laenge": 180.0,
"breite": 0.0,
"anzahl": 1
}
]DELETE /api/waste?material=AP40 Empty the bin (without ?material= completely) 204
(no content, status 204)Journal
GET /api/journal?material=AP40&limit=3 Stock journal (?material, ?seit, ?limit) 200
[
{
"id": 23,
"zeit": 1793530040.950529,
"art": "abfall_leer",
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"code": "",
"laenge": 0.0,
"breite": 0.0,
"anzahl": 1,
"quelle": "api",
"notiz": ""
},
{
"id": 22,
"zeit": 1793530040.94593,
"art": "abfall_ab",
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"code": "",
"laenge": 180.0,
"breite": 0.0,
"anzahl": 1,
"quelle": "api",
"notiz": ""
},
{
"id": 21,
"zeit": 1793530040.937907,
"art": "abfall_ein",
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"code": "",
"laenge": 180.0,
"breite": 0.0,
"anzahl": 2,
"quelle": "api",
"notiz": ""
}
]Combined plan
GET /api/sammel/1 Combined plan status and candidates 200
{
"sammel_id": 0,
"name": "",
"kandidaten": [
{
"projekt_id": 4,
"name": "Nachschub Leisten",
"offen": 4,
"bedarf": 800.0,
"gesperrt": 0,
"einzelplan": "",
"mitglied": false
},
{
"projekt_id": 3,
"name": "Schubladenschrank",
"offen": 12,
"bedarf": 8600.0,
"gesperrt": 0,
"einzelplan": "",
"mitglied": false
},
"… 1 weitere"
]
}POST /api/sammel/compare Separate or combined? (comparison) 200
{
"material_id": 1,
"projekt_ids": [
1,
3
],
"blade_id": 1
}{
"getrennt_bretter": 2,
"gemeinsam_bretter": 2,
"getrennt_menge": 6740.0,
"gemeinsam_menge": 6740.0,
"fehlende_getrennt": 14,
"fehlende_gemeinsam": 13,
"getrennt_reste": 1,
"gemeinsam_reste": 1
}POST /api/sammel Create a combined plan (material by code, projects by name) 201
{
"material": "AP40",
"projekte": [
"Werkstattregal"
]
}{
"id": 5,
"name": "Sammelplan Alu Profil 40x40 Nut 8",
"anzahl": 1,
"teile": [],
"plaene": [],
"sammel": {},
"art": "sammel",
"sammel_material_id": 1,
"mitglieder": [
1
]
}POST /api/sammel Error: material already has a combined plan 409
{
"material": "AP40",
"projekte": [
"Werkstattregal"
]
}{
"detail": {
"code": "sammel_vorhanden",
"message": "“Alu Profil 40x40 Nut 8” already has a combined plan",
"sammel_id": 5
}
}PUT /api/sammel/AP40/members Change members 200
{
"projekte": [
"Werkstattregal",
"Schubladenschrank"
]
}{
"id": 5,
"name": "Sammelplan Alu Profil 40x40 Nut 8",
"anzahl": 1,
"teile": [],
"plaene": [],
"sammel": {},
"art": "sammel",
"sammel_material_id": 1,
"mitglieder": [
1,
"… 1 weitere"
]
}POST /api/sammel/optimize Optimize together 200
{
"material_id": 1,
"projekt_ids": [
1,
3
],
"blade_id": 1
}{
"schnittplaene": [
{
"lagerstueck_id": 1,
"lager_laenge": 6000.0,
"lager_breite": 0.0,
"stueck_nr": 4,
"platzierungen": [
{
"teil_label": "Pfosten → Werkstattregal",
"laenge": 1750.0,
"breite": 0.0,
"x": 0.0,
"y": 0.0,
"gedreht": false,
"id": 30,
"gesaegt": false,
"ist_fuellteil": false
},
"… 5 weitere"
],
"reste": [
[
5.9999999999990905,
"… 1 weitere"
]
],
"verschnitt_mm": 6.0,
"verschnitt_prozent": 0.1,
"defekte": [],
"id": 6,
"verbraucht": false,
"stueck_code": "AP40-04",
"lagerort": "Wandregal Alu",
"ist_rest": false,
"reste_stuecke": [],
"reste_ziel": [
"weg"
]
},
"… 1 weitere"
],
"fehlende_teile": [
"Pfosten → Werkstattregal",
"… 12 weitere"
],
"gesamt_verschnitt_prozent": 2.4,
"materialbedarf": [
{
"laenge": 6000.0,
"breite": 0.0,
"anzahl": 3
}
],
"plan_id": 4,
"projekt_id": 5,
"material_id": 1,
"saegeblatt_id": 1,
"algorithmus": "greedy",
"erstellt": 1793530041.27601,
"version": 1,
"veraltet": false,
"lager_fehlt": false,
"fehlende_stuecke": [],
"fuellteile": [],
"fuell_alle_reste": false,
"vorrat_verfuegbar": [],
"max_pro_stange": 0,
"fuell_vorschlaege": []
}GET /api/projects?art=sammel List combined plans (?art=sammel|alle) 200
[
{
"id": 5,
"name": "Sammelplan Alu Profil 40x40 Nut 8",
"anzahl": 1,
"teile": [],
"plaene": [
{
"plan_id": 4,
"material_id": 1,
"erstellt": 1793530041.27601,
"gesamt": 9,
"gesaegt": 0,
"angefangen": false
}
],
"sammel": {},
"art": "sammel",
"sammel_material_id": 1,
"mitglieder": [
1,
"… 1 weitere"
]
}
]DELETE /api/projects/6 Delete a combined plan (cut parts stay in the projects) 204
(no content, status 204)Labels
POST /api/labels Labels as PDF 200
{
"labels": [
{
"title": "Alu Profil 40x40",
"line1": "6000 mm",
"line2": "Wandregal Alu",
"id_text": "AP40-01"
}
],
"filename": "Etiketten"
}File: application/pdf, 1,692 Bytes
Content-Disposition: attachment; filename="CutStock-Etiketten.pdf"POST /api/labels/pieces Labels for pieces without a label yet 200
{
"material": "AP40",
"nur_ungedruckt": true,
"startfeld": 5
}File: application/pdf, 1,776 Bytes
Content-Disposition: attachment; filename="CutStock-Alu Profil 40x40 Nut 8.pdf"POST /api/labels/pieces Error: everything already labelled 404
{
"material": "AP40",
"nur_ungedruckt": true
}{
"detail": {
"code": "keine_stuecke",
"message": "No pieces match the selection"
}
}Backup
GET /api/backup Download backup (ZIP) 200
File: application/zip, 6,711 Bytes
Content-Disposition: attachment; filename="cutstock_backup_2026-11-01.zip"POST /api/restore Restore backup (replaces all data) 200
multipart/form-data, field "file" = cutstock_backup.zip (application/zip){
"status": "ok"
}POST /api/seed Create sample data (empty database only) 201
{
"status": "ok",
"message": "Example data created"
}