API-Referenz
Basis-URL https://api.cutoptim.com · Contract-Version v1
Erstellen Sie einen Schlüssel in Ihrem Dashboard und senden Sie ihn dann als Authorization: Bearer <key>.
OpenAPI-Spezifikation: /engine/openapi.json
Endpoints
| POST | /v1/optimize/2d | 2D-Plattenoptimierung |
| POST | /v1/optimize/1d | 1D-/Linearoptimierung (Stäbe, Profile, Rohre) |
| GET | /v1/usage | Verbrauch und Kontingent des aufrufenden Schlüssels im laufenden Monat |
| GET | /v1/health | Liveness — kein Schlüssel, kein Rate-Limit, keine Datenbank |
Einheiten
Die API ist einheitenneutral. Wählen Sie eine Einheit — Millimeter, Zoll, was auch immer —, verwenden Sie sie für jede Zahl, die Sie senden, und jede Zahl, die Sie zurückbekommen, steht in derselben Einheit. Serverseitig wird nichts umgerechnet, und kein Feldname legt eine Einheit fest.
Das gilt eingehend für die Maße von Teilen und Ausgangsmaterial, kerf, tolerance, trim und minOffcut und ausgehend für jede Koordinate, jede Position, jede Restlänge, jedes Reststück und jede Schnittlänge. Einheiten innerhalb einer Anfrage zu mischen ergibt einen Plan, der die Validierung besteht und physikalisch falsch ist — und der Server kann das nicht erkennen.
Koordinatensystem
Der Ursprung ist die linke obere Ecke der Platte: x wächst nach rechts entlang der Plattenbreite, y wächst nach unten entlang der Plattenhöhe. x und y eines Teils bezeichnen seine linke obere Ecke, und w und h sind die Maße wie platziert — bei rotated true bereits getauscht —, sodass das Rechteck x, y, w, h ohne weitere Rechnung die Grundfläche auf der Platte ist. Für die Rechtecke der Reststücke gilt dasselbe System.
Die Besäumung verschiebt Platzierungen: trim.left schiebt jedes Teil nach rechts und trim.top jedes Teil nach unten, weil die Teile innerhalb der nutzbaren Fläche verschachtelt und anschließend auf die volle Platte zurückversetzt werden. trim.right und trim.bottom verkleinern die nutzbare Fläche, ohne den Ursprung zu verschieben. w und h einer Platte sind immer die vollen Maße des Ausgangsmaterials, Besäumung eingeschlossen — deshalb zählt die Besäumung in yieldPct auch als Abfall.
2D — Request
POST /v1/optimize/2d
Authorization: Bearer co_live_…
Content-Type: application/json
{
"parts": [{ "name": "Door", "w": 600, "h": 400, "qty": 4, "rotatable": true }],
"stock": [{ "w": 2440, "h": 1220, "qty": 10, "price": 42 }],
"options": { "kerf": 3, "trim": { "left": 10, "top": 10 } },
"engine": "heuristic",
"include": ["cutPlan", "offcuts"]
} Felder eines Teils
| w, h | erforderlich — Maße des Teils |
| qty | Standard 1 — wird serverseitig aufgelöst; zählt auf das Limit von 2,000 Teilen |
| name | optionale Bezeichnung, wird bei jeder Platzierung mit zurückgegeben |
| rotatable | Standard true — darf das Teil um 90° gedreht werden |
| grainGroup | Mitglieder einer Gruppe bleiben auf einer Platte (Fasergruppen) |
| priority | Muss-Teil: erhält bei begrenztem Ausgangsmaterial den Vorrang auf der Platte (mit respectStock) |
Felder des Ausgangsmaterials
w und h sind erforderlich. qty ist standardmäßig 1 und wirkt nur mit respectStock als hartes Limit. price gilt pro Platte und bestimmt totalPrice und den Kostenmodus.
Optionen
| kerf | Sägeblattdicke (Standard 0) |
| tolerance | Schnitte akzeptieren, die um maximal diesen Wert überschreiten |
| trim | Besäumung je Seite: left, right, top, bottom |
| firstCut | 'auto' | 'horizontal' | 'vertical' (Standard 'auto') |
| minimizeCost | Kandidaten nach dem Gesamtmaterialpreis statt nach der Plattenanzahl bewerten |
| respectStock | qty jeder Materialzeile als hartes Limit behandeln |
| minOffcut | nur Reststücke ausgeben, deren kurze Seite mindestens diesen Wert hat |
| maxCutStages | Stufenbegrenzung der Plattensäge — eine Anzahl von Schnittstufen, nicht die reine Baumtiefe |
| minimizeRotations | Layouts bevorzugen, die weniger Teile drehen |
include beschneidet die Antwort: übergeben Sie ["cutPlan","offcuts"] (Standard sind beide) oder lassen Sie einen der beiden weg, um ihn nicht mitzuliefern.
2D — Response
{
"engine": "heuristic",
"engineVersion": "1.0.0+10e0c941",
"contractVersion": "1",
"deterministic": true,
"sheets": [
{
"w": 2440,
"h": 1220,
"price": 42,
"parts": [
{ "name": "Door", "x": 10, "y": 10, "w": 400, "h": 600, "rotated": true },
{ "name": "Door", "x": 413, "y": 10, "w": 400, "h": 600, "rotated": true },
{ "name": "Door", "x": 816, "y": 10, "w": 400, "h": 600, "rotated": true },
{ "name": "Door", "x": 1219, "y": 10, "w": 400, "h": 600, "rotated": true }
],
"offcuts": [
{ "x": 1622, "y": 10, "w": 818, "h": 600 },
{ "x": 10, "y": 613, "w": 2430, "h": 607 }
]
}
],
"metrics": {
"sheetCount": 1,
"yieldPct": 32.25,
"placed": 4,
"total": 4,
"cutLines": 5,
"sawPasses": 5,
"cutLength": 4830,
"totalPrice": 42
},
"unplaced": [],
"warnings": [],
"guillotineValid": true,
"timing": { "solveMs": 3.13 },
"cutPlan": [
{ "sheet": 0, "step": 1, "axis": "h", "pos": 610, "length": 2430, "stage": 1 },
{ "sheet": 0, "step": 2, "axis": "v", "pos": 410, "length": 600, "stage": 2 },
{ "sheet": 0, "step": 3, "axis": "v", "pos": 813, "length": 600, "stage": 2 },
{ "sheet": 0, "step": 4, "axis": "v", "pos": 1216, "length": 600, "stage": 2 },
{ "sheet": 0, "step": 5, "axis": "v", "pos": 1619, "length": 600, "stage": 2 }
]
} - cutLines vs sawPasses — cutLines fasst kollineare Schnitte zusammen (eine Anschlagseinstellung); sawPasses zählt jeden Durchgang. Zwei ehrliche Maße für denselben Plan — nicht der Anspruch, mit der Zählweise irgendeines Wettbewerbers übereinzustimmen.
- guillotineValid / cutPlan — Lässt sich ein Layout nicht von Kante zu Kante schneiden, ist guillotineValid false und cutPlan null. Das ist eine echte Information — der Plan ist auf einer Plattensäge nicht herstellbar — und kein Fehler.
- unplaced + warnings — Ein nicht erfüllbarer Auftrag antwortet mit 200, die betroffenen Teile stehen in unplaced und ein Hinweis in warnings. Ein Plan, mit dem Sie arbeiten können, ist mehr wert als ein Statuscode.
Felder der Antwort
Feldnamen und Typen sind der Vertrag, deshalb bleiben die folgenden Tabellen in jeder Sprache englisch — ein übersetzter Feldname würde eine API dokumentieren, die es nicht gibt.
POST /v1/optimize/2d — top level
| engine | string | Which engine ran: "heuristic" or "balanced". |
| engineVersion | string | Algorithm identity — package version + a content hash of the algorithm source. Moves automatically on any packer change, and independently per engine. |
| contractVersion | string | Shape version, matching the /v1/ in the path. Currently "1". |
| deterministic | boolean | Always true. Present so a client can assert the guarantee it relies on. |
| sheets | array | One entry per sheet used, in cutting order. |
| metrics | object | Aggregate numbers for the whole job. |
| cutPlan | array | null | The sawing plan. null when guillotineValid is false; absent when excluded via include. |
| unplaced | array | Parts that did not fit, aggregated by name + size. Empty when everything fitted. |
| warnings | string[] | Free-text notes about the plan. Do not parse — branch on unplaced, guillotineValid and metrics. |
| guillotineValid | boolean | True when the layout is producible with edge-to-edge cuts, i.e. on a panel saw. |
| timing.solveMs | number | Milliseconds inside the packer, 2 decimals. Excludes parsing, auth and queueing. |
sheets[]
| w, h | number | FULL stock dimensions, trim included — not the usable area. |
| price | number | null | Price of the stock row this sheet came from, or null if none was given. |
| parts | array | Placements on this sheet. |
| offcuts | array | Usable leftover rectangles, filtered by options.minOffcut. Absent (not empty) when excluded via include. |
sheets[].parts[]
| name | string | The requested name, or the generated default "Part <row>". |
| x, y | number | Top-left corner of the part, from the top-left corner of the sheet. |
| w, h | number | Dimensions AS PLACED — already swapped when rotated is true. No client-side swap needed. |
| rotated | boolean | True if the part was turned 90° from the requested w×h. Informational only. |
sheets[].offcuts[]
| x, y, w, h | number | A leftover rectangle, in the same coordinate system as the placements. |
metrics (2D)
| sheetCount | integer | Sheets used. Equals sheets.length. |
| yieldPct | number | Placed part area ÷ total FULL sheet area × 100, 2 decimals. Trim and kerf count as waste. |
| placed | integer | Pieces placed, after qty expansion. |
| total | integer | Pieces requested, after qty expansion. |
| cutLines | integer | Collinear cuts merged: same axis, same coordinate, same stage counted once — one fence setting. |
| sawPasses | integer | Every cut, one per strip crossed — how many separate passes the saw makes. |
| cutLength | number | Total distance sawn, 3 decimals. Same under either counting convention. In your unit. |
| totalPrice | number | Sum of the used sheets’ prices, 2 decimals. 0 when no stock row carried a price. |
unplaced[] (2D)
| name | string | The part name. |
| w, h | number | As requested. |
| qty | integer | How many of this part could not be placed. |
cutPlan[]
| sheet | integer | 0-based index into sheets (2D) or rods (1D). Named sheet in both. |
| step | integer | 1-based order within THIS sheet — it restarts at 1 on every sheet. |
| axis | "h" | "v" | "h": blade travels along x, separating top from bottom. "v": along y, separating left from right. |
| pos | number | The blade’s LOW-COORDINATE edge — the y value for "h", the x value for "v" — not its centre line. The kerf occupies pos to pos + kerf. |
| length | number | Distance the blade travels on this cut: the extent of the region crossed. Always 0 in 1D. |
| stage | integer | 1-based machine pass. Increments only when the axis flips relative to the parent cut. |
cutPlan — was ein step physisch bedeutet
Ein step ist eine Bewegung des Sägeblatts, und die Liste steht in der Reihenfolge, in der Sie tatsächlich sägen können: ein übergeordneter Schnitt vor den Schnitten innerhalb des Stücks, das er erzeugt hat — denn Sie können einen Streifen nicht ablängen, bevor Sie ihn längs herausgetrennt haben. axis "h" bedeutet, das Sägeblatt bewegt sich entlang x und trennt oben von unten; axis "v" bedeutet, es bewegt sich entlang y und trennt links von rechts. pos ist die Kante des Sägeblatts mit der NIEDRIGEREN Koordinate — der Wert y bei "h", der Wert x bei "v" —, nicht seine Mittellinie: kerf belegt pos bis pos + kerf, das Sägeblatt frisst also in die Richtung, in die die Koordinate wächst — bei "h" nach unten, bei "v" nach rechts. Das Material auf der niedrigen Seite der Linie — bei "h" darüber, bei "v" links davon — ist das Stück, das dieser Schnitt freigibt. length ist die Strecke, die das Sägeblatt bei diesem einen Schnitt zurücklegt: die Ausdehnung des Bereichs, den es durchquert, nicht die Breite der ganzen Platte.
stage ist ein Maschinendurchgang. Der Wert beginnt bei 1 und erhöht sich nur, wenn axis gegenüber dem übergeordneten Schnitt umschlägt — eine Platte längs in sechs Streifen aufzutrennen ist also eine Stufe, und diese Streifen abzulängen die nächste. Das ist der Plattensägen-Sinn von „dreistufigem Zuschnitt“, nicht die Tiefe des Schnittbaums, und genau das begrenzt maxCutStages. sheet ist ein 0-basierter Index in sheets, und step beginnt auf jeder Platte wieder bei 1, statt über den gesamten Auftrag durchzulaufen.
cutPlan ist null — nicht fehlend, nicht leer —, sobald guillotineValid false ist: ein Layout, das sich nicht von Kante zu Kante schneiden lässt, hat keine Schnittsequenz, die zurückgegeben werden könnte. Ganz aus der Antwort verschwindet es nur, wenn Sie es in include weggelassen haben.
1D — Linear
POST /v1/optimize/1d
{
"parts": [{ "name": "Rail", "length": 1200, "qty": 6 }],
"stock": [{ "length": 3000, "qty": 5, "price": 12.5 }],
"options": { "kerf": 3, "trim": { "start": 10, "end": 0 } }
} Teile erhalten length (dazu qty, name, priority); das Ausgangsmaterial erhält length, qty und price. Die Optionen sind kerf, tolerance, trim.start / trim.end, minimizeCost, respectStock und minOffcut. Die Antwort liefert rods statt sheets — jeweils mit den zugehörigen parts, der Restlänge und den offcuts.
1D — Response
{
"engine": "heuristic",
"engineVersion": "1.0.0+10e0c941",
"contractVersion": "1",
"deterministic": true,
"rods": [
{
"length": 3000,
"price": 12.5,
"remaining": 587,
"parts": [
{ "name": "Rail", "pos": 10, "length": 1200 },
{ "name": "Rail", "pos": 1213, "length": 1200 }
],
"offcuts": [587]
},
{
"length": 3000,
"price": 12.5,
"remaining": 587,
"parts": [
{ "name": "Rail", "pos": 10, "length": 1200 },
{ "name": "Rail", "pos": 1213, "length": 1200 }
],
"offcuts": [587]
},
{
"length": 3000,
"price": 12.5,
"remaining": 587,
"parts": [
{ "name": "Rail", "pos": 10, "length": 1200 },
{ "name": "Rail", "pos": 1213, "length": 1200 }
],
"offcuts": [587]
}
],
"metrics": {
"rodCount": 3,
"yieldPct": 80,
"placed": 6,
"total": 6,
"cuts": 6,
"totalPrice": 37.5,
"toleranceAcceptedCount": 0
},
"unplaced": [],
"warnings": [],
"timing": { "solveMs": 2.19 },
"cutPlan": [
{ "sheet": 0, "step": 1, "axis": "v", "pos": 1210, "length": 0, "stage": 1 },
{ "sheet": 0, "step": 2, "axis": "v", "pos": 2413, "length": 0, "stage": 1 },
{ "sheet": 1, "step": 1, "axis": "v", "pos": 1210, "length": 0, "stage": 1 },
{ "sheet": 1, "step": 2, "axis": "v", "pos": 2413, "length": 0, "stage": 1 },
{ "sheet": 2, "step": 1, "axis": "v", "pos": 1210, "length": 0, "stage": 1 },
{ "sheet": 2, "step": 2, "axis": "v", "pos": 2413, "length": 0, "stage": 1 }
]
} rods ersetzt sheets, und guillotineValid gibt es nicht, weil ein Linearschnitt immer herstellbar ist. pos eines Teils ist der Abstand seines vorderen Endes von jenem Stabende, das trim.start abschneidet — das erste Teil beginnt also genau bei trim.start, und jedes folgende pos kommt um ein kerf weiter. remaining wird für jeden Stab ausgegeben, auch wenn der Wert unter minOffcut liegt: minOffcut filtert nur das Array offcuts, das höchstens einen Eintrag enthält. Im Schnittplan ist sheet der Index des Stabs, axis immer "v", stage immer 1 und length immer 0 — ein Ablängschnitt am Stab hat keine Verfahrstrecke zu melden, und genau deshalb enthalten die metrics in 1D cuts, aber kein cutLength.
rods[]
| length | number | FULL bar length as supplied in stock. |
| price | number | null | Price of the stock row, or null. |
| remaining | number | Unused length left on this bar, 3 decimals. Reported even when below minOffcut. |
| parts | array | Placements, in cutting order along the bar. |
| offcuts | number[] | At most one entry: [remaining] when it is > 0 and ≥ minOffcut, else []. Absent when excluded via include. |
rods[].parts[]
| name | string | The requested name, or the generated default. |
| pos | number | Offset of the part’s NEAR end from the bar end that trim.start trims. |
| length | number | The part length, as requested. |
metrics (1D)
| rodCount | integer | Bars used. Equals rods.length. |
| yieldPct | number | Placed length ÷ total FULL bar length × 100, 2 decimals. Trim and kerf count as waste. |
| placed | integer | Pieces placed, after qty expansion. |
| total | integer | Pieces requested, after qty expansion. |
| cuts | integer | Total crosscuts across all used bars — one per placed piece. |
| totalPrice | number | Sum of used bar prices, 2 decimals. |
| toleranceAcceptedCount | integer | Pieces that fitted only because options.tolerance allowed an overshoot. |
unplaced[] (1D)
| name | string | The part name. |
| length | number | As requested. |
| qty | integer | How many could not be placed. |
Engines
- heuristic (Standard) — der Guillotine-Packer mit mehreren Strategien. Höchste Auslastung, jedes Layout auf der Säge schneidbar, immer ein vollständiger cutPlan.
- balanced — ein MaxRects-Packer mit freier Verschachtelung. Bei großen Aufträgen deutlich schneller (gemessen ~25× bei 2.000 Teilen) bei geringem Verlust an Auslastung, und seine Layouts sind häufig nicht guillotine-schneidbar (guillotineValid: false, cutPlan: null). Er berücksichtigt tolerance, minimizeCost, grainGroup, maxCutStages und minimizeRotations nicht — wird eine davon gesetzt, weist eine Warnung darauf hin, dass sie ignoriert wurde.
Determinismus & Versionierung
Jede Antwort enthält engineVersion. Der Algorithmus ist deterministisch, deshalb ändert eine Verbesserung die Ausgabe für dieselbe Eingabe — was ein Breaking Change ist, wenn Sie cachen. Legen Sie das Verhalten fest, indem Sie engine explizit senden und engineVersion beobachten; die Pfadversion /v1/ ändert sich nur, wenn sich die Struktur der Antwort ändert.
Jede Engine wird unabhängig versioniert — eine Änderung an der einen verschiebt die Version der anderen nie.
Fehler
| 400 | invalid_request | Schema-Fehler. details.path verweist auf das betreffende Feld. |
| 401 | unauthorized | API-Schlüssel fehlt oder ist unbekannt. |
| 402 | quota_exceeded | Monatskontingent erreicht. Retry-After gibt die Sekunden bis zum Monatswechsel an. |
| 403 | key_revoked | Der Schlüssel existiert, wurde aber widerrufen. |
| 404 | not_found | Diese Route gibt es nicht — dasselbe erhalten Sie für den richtigen Pfad mit der falschen Methode. |
| 413 | too_large | Eingabe über einem Limit (siehe Limits). |
| 429 | busy | Kapazität momentan ausgeschöpft. Retry-After in Sekunden — das zählt nie auf Ihr Kontingent. |
| 500 | internal | Unerwarteter Fehler, oder das Auth-Backend ist nicht erreichbar (Anfragen werden dann abgewiesen — fail closed). |
| 504 | solve_timeout | Die Berechnung hat die harte Zeitgrenze überschritten (durchgesetzt im Proxy). |
Fehler-Body
Jeder Fehler, den die Engine selbst erzeugt, verwendet denselben Umschlag. Verzweigen Sie über error — ein stabiler Code —, niemals über message, dessen Wortlaut sich zwischen Releases ändern kann. details ist bei invalid_request vorhanden, wo path das betreffende Feld benennt, und bei too_large, wo max und got das Limit und Ihren gesendeten Wert angeben.
HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
{
"error": "invalid_request",
"message": "parts[0].h: must be greater than 0",
"details": { "path": "parts[0].h" }
} HTTP/1.1 402 Payment Required
Retry-After: 41400
Content-Type: application/json; charset=utf-8
{ "error": "quota_exceeded", "message": "monthly quota exhausted" } - 402 und 429 tragen beide einen Retry-After-Header in Sekunden. Bei 402 zählt er bis zum Zurücksetzen des Kontingents am 1. des Folgemonats um 00:00 UTC herunter; bei 429 ist es ein kurzer Back-off, und ein 429 verbraucht nie Kontingent — der reservierte Aufruf wird zurückgegeben.
- Ein Routing-Fehler antwortet mit not_found — ein Code, der bewusst außerhalb der Liste oben steht, weil ihn der Router erzeugt und nicht der API-Vertrag. Sie erhalten 404 und nicht 405, wenn der Pfad stimmt, die Methode aber nicht: beide Optimierungs-Endpoints nehmen ausschließlich POST.
- 504 kommt vom Reverse-Proxy, nicht von der Engine — der Body ist deshalb der des Proxys und nicht dieser JSON-Umschlag. Innerhalb der unten genannten Eingabelimits sollte er unerreichbar sein.
Rate-Limit-Header
Ein erfolgreicher Optimierungsaufruf liefert X-RateLimit-Limit (das Monatslimit des Schlüssels) und X-RateLimit-Remaining (die in diesem Monat verbleibenden Aufrufe, diesen eingerechnet). Gesendet werden sie ausschließlich von den Optimierungs-Endpoints: der Zähler wird als Teil der Freigabe einer Berechnung reserviert, deshalb haben /v1/usage und /v1/health nichts zu melden.
| X-RateLimit-Limit | integer | The key’s monthly quota. |
| X-RateLimit-Remaining | integer | Calls left this month, after this one. |
| Retry-After | integer | Seconds to wait. Sent with 402 and 429 only. |
GET /v1/usage
{
"plan": "studio",
"used": 137,
"limit": 10000,
"remaining": 9863,
"periodEnd": "2026-08-01",
"keyPrefix": "co_live_ab12",
"contractVersion": "1"
} Nur lesend: der Aufruf verbraucht kein Kontingent und sendet keine Rate-Limit-Header. plan und limit sind Paket und Limit, die bei der Erstellung auf dem Schlüssel festgeschrieben wurden — eine spätere Neubepreisung des Produkts schreibt einen aktiven Schlüssel also nie um. used zählt den laufenden UTC-Kalendermonat, remaining ist limit minus used und wird nie negativ, periodEnd ist der Tag des Zurücksetzens als einfaches Datum in der Form YYYY-MM-DD, und keyPrefix ist das nicht geheime Anzeigepräfix des Schlüssels, mit dem Sie aufgerufen haben. Der Schlüssel selbst wird von keinem Endpoint jemals zurückgegeben — gespeichert ist nur sein Hash, ein verlorener Schlüssel wird deshalb ersetzt, nicht wiederhergestellt.
| plan | string | Tier slug frozen onto the key when it was created. |
| used | integer | Calls counted in the current UTC calendar month. |
| limit | integer | The monthly cap frozen onto the key. |
| remaining | integer | limit − used, never negative. |
| periodEnd | string | Reset day as YYYY-MM-DD — a date, not a timestamp. |
| keyPrefix | string | Non-secret display prefix of the calling key. |
| contractVersion | string | Shape version. Currently "1". |
GET /v1/health
{
"status": "healthy",
"service": "cutoptim-engine",
"contractVersion": "1",
"engineVersion": "1.0.0+10e0c941",
"engines": ["heuristic", "balanced"],
"uptimeSec": 16
} Kein Schlüssel, kein Kontingent, keine Datenbank. Der Endpoint berührt bewusst nichts Zustandsbehaftetes, damit ein Ausfall des Schlüsselspeichers den Dienst für einen Orchestrator nicht tot aussehen lässt. engines listet die ids, die diese Installation in engine akzeptiert, und engineVersion ist die Version der Standard-Engine.
| status | string | Always "healthy" when the process answers. |
| service | string | Always "cutoptim-engine". |
| contractVersion | string | Shape version. Currently "1". |
| engineVersion | string | The DEFAULT engine’s version, not a per-engine list. |
| engines | string[] | Engine ids this deployment accepts in engine. |
| uptimeSec | integer | Whole seconds since process start. |
Limits
- 2,000 Teile pro Anfrage (Gesamtmenge, nach Auflösung von qty)
- 50 Materialzeilen · Request-Body bis 1 MB
- 10 aktive Schlüssel pro Konto
- die Parallelität ist serverseitig begrenzt — ein Burst erhält 429, niemals eine langsame Warteschlange
OpenAPI-Spezifikation
Ein maschinenlesbares OpenAPI 3.1-Dokument beschreibt alle vier Endpoints, beide Request-Bodies, jede Antwortstruktur und jeden Fehler. Richten Sie Ihren Client-Generator darauf, statt diese Seite abzuschreiben. Das Dokument selbst ist ausschließlich englisch: es besteht aus Contract-Tokens, und OpenAPI besitzt keinen Lokalisierungsmechanismus.
curl -s https://cutoptim.com/engine/openapi.json > cutoptim-engine.json