REST-API
Jede Funktion von CutStock ist per REST-API steuerbar – Stücke über ihren Code, Materialien über ihr Kürzel.
Jede Funktion der Oberflaeche ist auch per REST-API unter /api/ erreichbar -- die Oberflaeche selbst nutzt nichts anderes. Alle Endpoints akzeptieren und liefern JSON. Eine automatisch erzeugte, ausprobierbare Beschreibung aller Endpoints liefert CutStock unter /docs.
Fuer Programme, Scanner und Maschinen gilt:
- Materialien gehen ueber ID oder Kuerzel (
AP40), Stuecke ueber ID oder Code (AP40-07, vom Etikett), Lagerorte ueber ID oder Namen. - Fehler der Lager-Befehle haben einen festen Code:
{"detail": {"code": "stueck_reserviert", "message": "...", "projekt": "..."}}. - Jede Lagerbuchung landet im Lagerjournal, mit Quelle
apioderui.
Authentifizierung
Wenn ein API-Key konfiguriert ist (Einstellungen-Seite), muessen externe Anfragen ihn als Bearer-Token mitschicken:
curl -H "Authorization: Bearer DEIN_API_KEY" http://cutstock:8420/api/materials
Anfragen von der eigenen Web-Oberflaeche (Same-Origin) und von localhost sind immer ohne Key erlaubt.
Endpoints
| Methode | Pfad | Beschreibung |
|---|---|---|
| Materialien | ||
| GET | /api/materials |
Alle Materialien auflisten |
| POST | /api/materials |
Material anlegen |
| PUT | /api/materials/{id} |
Material aktualisieren |
| DELETE | /api/materials/{id} |
Material loeschen |
| Lagerbestand | ||
| GET | /api/stock?material_id=N |
Lagerbestand auflisten (optionaler Filter) |
| POST | /api/stock |
Lagereintrag anlegen |
| PUT | /api/stock/{id} |
Lagereintrag aktualisieren |
| DELETE | /api/stock/{id} |
Lagereintrag loeschen |
| GET | /api/stock/{id}/pieces |
Einzelstuecke auflisten (Code, Lagerort, reserviert fuer) |
| GET | /api/pieces/{id oder Code} |
Stueck suchen: Material, Mass, Lagerort, Reservierung |
| PUT | /api/pieces/{id oder Code} |
Notiz eines Stuecks aendern |
| GET/POST/DELETE | /api/materials/{id}/image |
Materialbild lesen, hochladen, entfernen |
| Lager (siehe unten) | ||
| GET | /api/locations |
Lagerorte mit Anzahl Materialien und Stuecke |
| POST | /api/locations |
Lagerort anlegen {"name": "Regal 3"} |
| PUT | /api/locations/{id oder Name} |
Lagerort umbenennen |
| DELETE | /api/locations/{id oder Name}?ziel=… |
Lagerort loeschen; genutzt → ziel noetig |
| GET | /api/locations/{id oder Name}/contents |
Was dort liegt: Materialien mit Stuecken |
| PUT | /api/materials/{id oder Kuerzel}/location |
Material einem Lagerort zuweisen |
| POST | /api/stock/receive |
Zugang: Stuecke einbuchen, liefert die neuen Codes |
| POST | /api/pieces/{id oder Code}/take |
Stueck entnehmen (mit Grund) |
| GET | /api/waste |
Inhalt der Abfallkiste |
| POST | /api/waste |
Abschnitte in die Abfallkiste legen |
| POST | /api/waste/take |
Abschnitte aus der Abfallkiste nehmen |
| DELETE | /api/waste?material=… |
Abfallkiste leeren (ganz oder je Material) |
| GET | /api/journal?material=…&seit=… |
Lagerjournal, neueste Buchung zuerst |
| Defekte | ||
| GET | /api/stock/{id}/defects |
Defekte eines Lagerstuecks |
| GET | /api/pieces/{id}/defects |
Defekte eines Einzelstuecks |
| POST | /api/pieces/{id}/defects |
Defekt hinzufuegen |
| PUT | /api/defects/{id} |
Defekt aktualisieren |
| DELETE | /api/defects/{id} |
Defekt loeschen |
| Saegeblaetter | ||
| GET | /api/blades |
Saegeblaetter auflisten |
| POST | /api/blades |
Saegeblatt anlegen |
| PUT | /api/blades/{id} |
Saegeblatt aktualisieren |
| DELETE | /api/blades/{id} |
Saegeblatt loeschen |
| Projekte | ||
| GET | /api/projects |
Projekte auflisten |
| GET | /api/projects/{id} |
Projekt abrufen |
| POST | /api/projects |
Projekt anlegen |
| PUT | /api/projects/{id} |
Projekt aktualisieren (Name, Anzahl Exemplare) |
| DELETE | /api/projects/{id} |
Projekt loeschen |
| POST | /api/projects/{id}/clone |
Projekt kopieren |
| POST | /api/projects/{id}/copy-parts |
Teile in ein Projekt kopieren |
| POST | /api/projects/{id}/restart |
Projekt neu starten (Fortschritt 0, Plaene weg, Lager bleibt) |
| Teile | ||
| GET | /api/projects/{id}/parts |
Teile eines Projekts auflisten |
| POST | /api/projects/{id}/parts |
Teil hinzufuegen |
| PUT | /api/parts/{id} |
Teil aktualisieren (optional vorrat: Regal-Bestand setzen) |
| DELETE | /api/parts/{id} |
Teil loeschen |
| Optimierung und Plaene | ||
| POST | /api/optimize |
Optimieren und als Plan des Projekts fuer das Material speichern |
| GET | /api/projects/{id}/plans/{material_id} |
Gespeicherten Plan abrufen (null, wenn keiner existiert) |
| POST | /api/plan-pieces/{id} |
Stueck als gesaegt markieren/zuruecknehmen ({"gesaegt": true}), aktualisiert Fortschritt und Lager |
| POST | /api/plans/{id}/confirm-all |
Alle offenen Stuecke des Plans als gesaegt markieren |
| DELETE | /api/plans/{id} |
Plan verwerfen (gibt reservierte Stuecke frei) |
| GET | /api/projects/{id}/fill-candidates/{material_id} |
Moegliche Fuellteile aus anderen Projekten |
| GET | /api/sammel/{material_id} |
Sammelplan-Status und moegliche Projekte |
| POST | /api/sammel/optimize |
Sammelplan anlegen/ersetzen und berechnen |
| GET | /api/parts |
Teile aller Projekte (Reiter "Teile") |
| POST | /api/cut-lists |
Schnittliste aus einzelnen Teilen anlegen |
| POST | /api/sammel/compare |
Vergleich getrennt/gemeinsam (ohne Speichern) |
| POST | /api/projects/{id}/take-stock/{material_id} |
Teile-Vorrat ("Regal") als gesaegt uebernehmen |
| Export | ||
| POST | /api/pdf |
PDF-Schnittplan erzeugen |
| POST | /api/labels |
Etiketten-PDF erzeugen |
| System | ||
| GET | /api/version |
App-Version und Links |
| GET | /api/license |
Lizenzstatus |
| GET | /api/settings |
Einstellungen lesen |
| PUT | /api/settings |
Einstellungen aendern |
| GET | /api/backup |
Datenbank-Backup herunterladen (ZIP) |
| POST | /api/restore |
Aus Backup wiederherstellen (ZIP) |
| GET | /api/i18n/{lang} |
Uebersetzungen fuer eine Sprache |
Optimierungsanfrage
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
}
Algorithmus-Optionen: greedy, nested, ga. reste_zuerst, fuellteile und fuell_alle_reste sind optional (Fuellteil-Modus auffuellen oder fest; ohne fuellteile bleibt die Auswahl des gespeicherten Plans).
Die Antwort ist der gespeicherte Plan: plan_id, schnittplaene (je Brett u. a. stueck_code, lagerort, platzierungen mit id und gesaegt), fehlende_teile, gesamt_verschnitt_prozent, materialbedarf (zusaetzlich benoetigtes Material -- leer wenn alles passt) sowie Hinweise veraltet und lager_fehlt.
Lager-Befehle
Zugang -- liefert die Codes fuers Beschriften:
POST /api/stock/receive
{"material": "AP40", "laenge": 6000, "anzahl": 4}
→ {"stock_id": 12, "codes": ["AP40-21", "AP40-22", "AP40-23", "AP40-24"], "lagerort": "Wandregal Alu"}
breite nur bei Platten, ist_rest: true fuer Reststuecke (liegen dann am Reste-Ort).
Stueck entnehmen:
POST /api/pieces/AP40-07/take
{"grund": "verbraucht", "notiz": "Musterbau"}
grund: verbraucht, verkauft, defekt oder abfall (= in die Abfallkiste, wenn eingeschaltet).
Ist das Stueck von einem gespeicherten Plan reserviert, kommt 409 mit code: "stueck_reserviert" und dem Projekt; mit "trotzdem": true wird es dennoch entnommen. Der Plan zeigt danach lager_fehlt und fehlende_stuecke.
Material einem Lagerort zuweisen:
PUT /api/materials/AP40/location
{"lagerort": "Wandregal Alu", "lagerort_reste": "Restekiste Alu"}
lagerort_reste: "" = Reste liegen beim Material. Beim Anlegen/Aendern eines Materials ueber /api/materials wird ein unbekannter Lagerort-Name automatisch angelegt.
Lagerort loeschen, der noch genutzt wird:
DELETE /api/locations/Regal%203 → 409 {"code": "lagerort_in_benutzung", "materialien": [...]}
DELETE /api/locations/Regal%203?ziel=Lager → 204, die Materialien liegen jetzt in "Lager"
Abfallkiste (ohne Codes, nur Material + Mass + Anzahl):
POST /api/waste {"material": "AP40", "laenge": 180, "anzahl": 2}
POST /api/waste/take {"material": "AP40", "laenge": 180}
DELETE /api/waste?material=AP40
Lagerjournal: GET /api/journal?material=AP40&seit=1767225600&limit=100 -- je Eintrag zeit (Unix-Zeit), 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, Masse, anzahl, quelle (ui/api), notiz.
Antworten (Beispiele)
So sehen die Antworten aus – erzeugt mit dem echten CutStock und einer Beispiel-Datenbank. Lange Listen sind gekürzt („… n weitere“). Zahlen sind immer Millimeter, zeit ist Unix-Zeit. Ein Klick klappt ein Beispiel auf.
Materialien
GET /api/materials Alle Materialien 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": ""
}
]Lager
POST /api/stock/receive Zugang: Stücke einbuchen 201
{
"material": "AP40",
"laenge": 6000,
"anzahl": 4
}{
"stock_id": 1,
"codes": [
"AP40-01",
"AP40-02",
"… 2 weitere"
],
"lagerort": "Wandregal Alu"
}POST /api/stock/receive Zugang eines Rests 201
{
"material": "AP40",
"laenge": 740,
"anzahl": 1,
"ist_rest": true
}{
"stock_id": 2,
"codes": [
"AP40-05"
],
"lagerort": "Restekiste Alu"
}GET /api/stock?material_id=1 Lagerzeilen eines Materials 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
}
]GET /api/stock/1/pieces Einzelstücke einer Lagerzeile 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
},
{
"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
},
"… 2 weitere"
]GET /api/pieces/AP40-02 Stück über seinen Code 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
}Lagerorte
GET /api/locations Alle Lagerorte 200
[
{
"id": 3,
"name": "Plattenwagen",
"materialien": 1,
"stuecke": 0
},
{
"id": 2,
"name": "Restekiste Alu",
"materialien": 1,
"stuecke": 1
},
"… 1 weitere"
]POST /api/locations Lagerort anlegen 201
{
"name": "Regal 3"
}{
"id": 4,
"name": "Regal 3",
"materialien": 0,
"stuecke": 0
}GET /api/locations/Wandregal Alu/contents Was an einem Ort liegt 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
},
"… 3 weitere"
]
}
]
}
]PUT /api/materials/AP40/location Material zuweisen 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": 300.0,
"rest_min_breite": 0.0,
"hat_bild": false,
"kuerzel": "AP40",
"lagerort": "Wandregal Alu",
"lagerort_reste": "Restekiste Alu"
}DELETE /api/locations/Wandregal Alu Fehler: Lagerort noch genutzt 409
{
"detail": {
"code": "lagerort_in_benutzung",
"message": "Location is still used by materials",
"materialien": [
"Alu Profil 40x40 Nut 8"
]
}
}Projekte
POST /api/projects Projekt anlegen 201
{
"name": "Werkstattregal",
"anzahl": 1
}{
"id": 1,
"name": "Werkstattregal",
"anzahl": 1,
"teile": [],
"plaene": [],
"sammel": {},
"art": ""
}POST /api/projects/1/parts Teil hinzufügen 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
}Optimierung
POST /api/optimize Optimieren (Antwort = gespeicherter Plan) 200
{
"project_id": 1,
"material_id": 1,
"blade_id": 1,
"algorithm": "nested"
}{
"schnittplaene": [
{
"lagerstueck_id": 1,
"lager_laenge": 6000.0,
"lager_breite": 0.0,
"stueck_nr": 1,
"platzierungen": [
{
"teil_label": "Pfosten",
"laenge": 1800.0,
"breite": 0.0,
"x": 0.0,
"y": 0.0,
"gedreht": false,
"id": 1,
"gesaegt": false,
"ist_fuellteil": false
},
"… 4 weitere"
],
"reste": [
[
108.79999999999927,
"… 1 weitere"
]
],
"verschnitt_mm": 108.8,
"verschnitt_prozent": 1.8,
"defekte": [],
"id": 1,
"verbraucht": false,
"stueck_code": "AP40-01",
"lagerort": "Wandregal Alu",
"ist_rest": false,
"reste_stuecke": [],
"reste_ziel": [
"abfallkiste"
]
},
"… 1 weitere"
],
"fehlende_teile": [],
"gesamt_verschnitt_prozent": 2.0,
"materialbedarf": [],
"plan_id": 1,
"projekt_id": 1,
"material_id": 1,
"saegeblatt_id": 1,
"algorithmus": "nested",
"erstellt": 1793471343.0604548,
"version": 1,
"veraltet": false,
"lager_fehlt": false,
"fehlende_stuecke": [],
"fuellteile": [],
"fuell_alle_reste": false,
"vorrat_verfuegbar": [],
"max_pro_stange": 0,
"fuell_vorschlaege": []
}POST /api/plan-pieces/1 Stück als gesägt abhaken 200
{
"gesaegt": true
}{
"plan": {
"schnittplaene": [
{
"lagerstueck_id": 1,
"lager_laenge": 6000.0,
"lager_breite": 0.0,
"stueck_nr": 1,
"platzierungen": [
{
"teil_label": "Pfosten",
"laenge": 1800.0,
"breite": 0.0,
"x": 0.0,
"y": 0.0,
"gedreht": false,
"id": 1,
"gesaegt": true,
"ist_fuellteil": false
},
"… 4 weitere"
],
"reste": [
[
108.79999999999927,
"… 1 weitere"
]
],
"verschnitt_mm": 108.8,
"verschnitt_prozent": 1.8,
"defekte": [],
"id": 1,
"verbraucht": true,
"stueck_code": "AP40-01",
"lagerort": "Wandregal Alu",
"ist_rest": false,
"reste_stuecke": [
{
"code": "AP40-01",
"laenge": 4197.2,
"breite": 0.0,
"lagerort": "Restekiste Alu"
}
],
"reste_ziel": [
"abfallkiste"
]
},
"… 1 weitere"
],
"fehlende_teile": [],
"gesamt_verschnitt_prozent": 2.0,
"materialbedarf": [],
"plan_id": 1,
"projekt_id": 1,
"material_id": 1,
"saegeblatt_id": 1,
"algorithmus": "nested",
"erstellt": 1793471343.0604548,
"version": 2,
"veraltet": false,
"lager_fehlt": false,
"fehlende_stuecke": [],
"fuellteile": [],
"fuell_alle_reste": false,
"vorrat_verfuegbar": [],
"max_pro_stange": 0,
"fuell_vorschlaege": []
},
"teil": {
"id": 1,
"projekt_id": 1,
"label": "Pfosten",
"typ": "Stange",
"material_id": 1,
"laenge": 1800.0,
"breite": 0.0,
"stueckzahl": 4,
"gesaegt_anzahl": 1,
"maserung": "egal",
"status": "offen",
"offen_anzahl": 3,
"gesamt_anzahl": 4,
"vorrat": 0,
"herkunft_teil_id": 0
}
}Lager
POST /api/pieces/AP40-02/take Fehler: Stück ist reserviert 409
{
"grund": "verbraucht"
}{
"detail": {
"code": "stueck_reserviert",
"message": "AP40-02 is reserved by “Werkstattregal”",
"projekt": "Werkstattregal"
}
}POST /api/pieces/AP40-05/take Stück entnehmen 200
{
"grund": "verkauft",
"notiz": "an Nachbar"
}{
"code": "AP40-05",
"grund": "verkauft",
"reserviert_fuer": "",
"abfallkiste": false
}Abfallkiste
POST /api/waste Abschnitte hineinlegen 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 Inhalt der Abfallkiste 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 Einen Abschnitt herausnehmen 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
}
]Journal
GET /api/journal?material=AP40&limit=3 Lagerjournal 200
[
{
"id": 14,
"zeit": 1793471343.102995,
"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": 13,
"zeit": 1793471343.089545,
"art": "abfall_ein",
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"code": "",
"laenge": 180.0,
"breite": 0.0,
"anzahl": 2,
"quelle": "api",
"notiz": ""
},
{
"id": 12,
"zeit": 1793471343.084173,
"art": "entnahme",
"material_id": 1,
"material": "Alu Profil 40x40 Nut 8",
"code": "AP40-05",
"laenge": 740.0,
"breite": 0.0,
"anzahl": 1,
"quelle": "api",
"notiz": "verkauft · an Nachbar"
}
]System
GET /api/version Version 200
{
"version": "2026.10.70",
"repo": "orohde/CutStock",
"github_url": "https://github.com/orohde/CutStock",
"website_url": "https://worldgate.de/cutstock/",
"releases_api": "https://api.github.com/repos/orohde/CutStock/releases/latest",
"docker": false
}