Przejdź do głównej treści
← CutOptim Engine API

Dokumentacja API

Bazowy URL https://api.cutoptim.com · wersja kontraktu v1

Utwórz klucz w swoim panelu, a następnie przesyłaj go jako Authorization: Bearer <key>.

Specyfikacja OpenAPI: /engine/openapi.json

Endpointy

POST /v1/optimize/2d optymalizacja cięcia płyt 2D
POST /v1/optimize/1d optymalizacja 1D / liniowa (pręty, profile, rury)
POST /v1/optimize/wood optymalizacja drewna — 1D z dopasowaniem przekroju
GET /v1/usage zużycie i limit KONTA w bieżącym miesiącu
GET /v1/health liveness — bez klucza, bez limitu zapytań, bez bazy danych

Jednostki

API nie narzuca jednostek. Wybierz jedną jednostkę — milimetry, cale, cokolwiek — używaj jej w każdej przesyłanej liczbie, a każda liczba, którą otrzymasz z powrotem, będzie w tej samej jednostce. Nic nie jest przeliczane po stronie serwera i żadna nazwa pola nie zakłada konkretnej jednostki.

Dotyczy to wymiarów elementów i materiału bazowego, kerf, tolerance, trim oraz minOffcut na wejściu, a także każdej współrzędnej, pozycji, pozostałej długości, resztki i długości cięcia na wyjściu. Mieszanie jednostek w jednym zapytaniu daje plan, który przechodzi walidację, a fizycznie jest błędny — i serwer nie jest w stanie tego wykryć.

Układ współrzędnych

Początek układu to lewy górny narożnik płyty: x rośnie w prawo wzdłuż szerokości płyty, y rośnie w dół wzdłuż wysokości płyty. Pola x i y elementu wskazują jego lewy górny narożnik, a w i h to wymiary w takim ułożeniu, w jakim element został rozmieszczony — już zamienione, gdy rotated ma wartość true — więc prostokąt x, y, w, h to gotowy obrys na płycie, bez żadnych dalszych obliczeń. Prostokąty resztek korzystają z tego samego układu.

Okrawanie przesuwa rozmieszczenia: trim.left przesuwa każdy element w prawo, a trim.top przesuwa każdy element w dół, ponieważ elementy są rozmieszczane wewnątrz obszaru użytkowego, a następnie przesuwane z powrotem na całą płytę. trim.right i trim.bottom zmniejszają obszar użytkowy, nie przesuwając początku układu. Pola w i h płyty to zawsze pełne wymiary materiału bazowego, razem z okrawaniem — i właśnie dlatego okrawanie liczy się jako odpad w yieldPct.

2D — zapytanie

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"]
}

Pola elementu

w, hwymagane — wymiary elementu
qtydomyślnie 1 — rozwijane po stronie serwera; liczy się do limitu 2,000 elementów
nameopcjonalna etykieta, zwracana przy każdym rozmieszczeniu
rotatabledomyślnie true — czy element można obrócić o 90°
grainGroupelementy jednej grupy pozostają na tej samej płycie (dopasowanie słojów)
priorityelement obowiązkowy: wygrywa miejsce na płycie, gdy zapas jest ograniczony (przy respectStock)

Pola materiału bazowego

w oraz h są wymagane. qty domyślnie wynosi 1 i jest twardym limitem tylko przy respectStock. price dotyczy jednej płyty i zasila totalPrice oraz tryb kosztowy.

Opcje

kerfszerokość rzazu piły (domyślnie 0)
toleranceakceptuj cięcia przekraczające materiał o nie więcej niż tę wartość
trimokrawanie każdej krawędzi osobno: left, right, top, bottom
firstCut'auto' | 'horizontal' | 'vertical' (domyślnie 'auto')
minimizeCostoceniaj warianty według łącznej ceny materiału, a nie liczby płyt
respectStocktraktuj qty każdego wiersza materiału jako twardy limit
minOffcutraportuj tylko resztki, których krótszy bok jest nie mniejszy niż ta wartość
maxCutStageslimit etapów piły panelowej — liczba faz, a nie surowa głębokość drzewa
minimizeRotationspreferuj układy obracające mniej elementów

include przycina odpowiedź: przekaż ["cutPlan","offcuts"] (domyślnie oba), albo pomiń jedno z nich, aby nie trafiło do odpowiedzi.

2D — odpowiedź

{
  "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 }
  ]
}

Pola odpowiedzi

Nazwy i typy pól są częścią kontraktu, dlatego poniższe tabele pozostają po angielsku we wszystkich językach — przetłumaczona nazwa pola dokumentowałaby API, które nie istnieje.

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 — co fizycznie oznacza jeden krok

Jeden step to jeden ruch piły, a lista jest w takiej kolejności, w jakiej faktycznie da się ciąć: cięcie nadrzędne przed cięciami wewnątrz kawałka, który z niego powstał, bo paska nie przetniesz w poprzek, dopóki go nie odetniesz. axis "h" oznacza, że piła przesuwa się wzdłuż x i oddziela górę od dołu; axis "v" oznacza, że przesuwa się wzdłuż y i oddziela lewą stronę od prawej. pos to krawędź rzazu o NIŻSZEJ współrzędnej — wartość y dla "h", wartość x dla "v" — a nie jego linia środkowa: rzaz zajmuje przedział od pos do pos + kerf, więc piła zabiera materiał w kierunku rosnącej współrzędnej, czyli w dół dla "h" i w prawo dla "v". Materiał po stronie niższej współrzędnej — nad linią dla "h", po jej lewej stronie dla "v" — to kawałek, który uwalnia to cięcie. length to droga, jaką piła przebywa w tym jednym cięciu: rozpiętość obszaru, przez który przechodzi, a nie szerokość całej płyty.

stage to jedno przejście maszyny. Zaczyna się od 1 i zwiększa się tylko wtedy, gdy axis zmienia się względem cięcia nadrzędnego, więc rozcięcie płyty na sześć pasków to jeden etap, a ich poprzeczne przecięcie to następny. To właśnie tak rozumie się „cięcie trzyetapowe” na pile panelowej — nie jako głębokość drzewa cięć — i właśnie to ogranicza opcja maxCutStages. sheet to liczony od 0 indeks w sheets, a step zaczyna się od 1 na każdej płycie, zamiast biec przez całe zlecenie.

cutPlan ma wartość null — nie brakuje go i nie jest pusty — zawsze wtedy, gdy guillotineValid ma wartość false: układ, którego nie da się pociąć od krawędzi do krawędzi, nie ma sekwencji cięcia do zwrócenia. W odpowiedzi nie ma go w ogóle, jeśli pominięto go w include.

1D — liniowe

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 } }
}

Elementy przyjmują length (oraz qty, name, priority); materiał bazowy przyjmuje length, qty i price. Dostępne opcje to kerf, tolerance, trim.start / trim.end, minimizeCost, respectStock i minOffcut. Odpowiedź zwraca rods zamiast sheets, każdy z przypisanymi elementami, pozostałą długością i resztkami.

1D — odpowiedź

{
  "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 zastępuje sheets i nie ma pola guillotineValid, ponieważ cięcie liniowe zawsze da się wykonać. Pole pos każdego elementu to odległość jego bliższego końca od tego końca pręta, który okrawa trim.start, więc pierwszy element zaczyna się dokładnie na trim.start, a każde kolejne pos dodaje jedną szerokość rzazu. remaining jest raportowane dla każdego pręta, nawet gdy jest mniejsze niż minOffcut — minOffcut filtruje wyłącznie tablicę offcuts, która zawiera najwyżej jeden wpis. W planie cięcia sheet to indeks pręta, axis ma zawsze wartość "v", stage ma zawsze wartość 1, a length zawsze 0: poprzeczne przecięcie pręta nie ma drogi przejazdu, którą można by zaraportować — i właśnie dlatego metrics dla 1D zawiera cuts, ale nie cutLength.

Drewno — przekrój

POST /v1/optimize/wood

{
  "parts": [
    { "name": "Rafter", "sw": 50, "sh": 100, "length": 2400, "qty": 2 },
    { "name": "Noggin", "sw": 50, "sh": 100, "length": 600, "qty": 4 },
    { "name": "Beam", "sw": 50, "sh": 150, "length": 3000, "qty": 2 }
  ],
  "stock": [
    { "name": "C24 50x100", "sw": 50, "sh": 100, "length": 4000, "qty": 5, "price": 12.5 },
    { "name": "C24 50x150", "sw": 50, "sh": 150, "length": 4000, "qty": 3, "price": 18 }
  ],
  "options": { "kerf": 3 }
}

Drewno ma tożsamość, której nie ma zwykły pręt: elementu 50×150 nie da się wyciąć z materiału 50×100, niezależnie od tego, ile długości zostało. Elementy i materiał niosą więc sw i sh, dwa boki przekroju, w dowolnej kolejności — 50×100 i 100×50 to ta sama belka obrócona i trafiają do jednego przekroju. Zlecenie jest dzielone według przekroju, każdy przekrój jest dopasowywany do własnego materiału i rozwiązywany osobno, a jedno wywołanie zwraca całość. Opcje są takie same jak w 1D.

Drewno — odpowiedź

{
  "engine": "heuristic",
  "engineVersion": "1.0.0+7c1f3a62",
  "contractVersion": "1",
  "deterministic": true,
  "sections": [
    {
      "section": "50x100",
      "sw": 50,
      "sh": 100,
      "stockName": "C24 50x100",
      "rods": [
        {
          "length": 4000,
          "price": 12.5,
          "remaining": 394,
          "parts": [
            { "name": "Rafter", "pos": 0, "length": 2400 },
            { "name": "Noggin", "pos": 2403, "length": 600 },
            { "name": "Noggin", "pos": 3006, "length": 600 }
          ],
          "offcuts": [394]
        },
        {
          "length": 4000,
          "price": 12.5,
          "remaining": 394,
          "parts": [
            { "name": "Rafter", "pos": 0, "length": 2400 },
            { "name": "Noggin", "pos": 2403, "length": 600 },
            { "name": "Noggin", "pos": 3006, "length": 600 }
          ],
          "offcuts": [394]
        }
      ],
      "metrics": {
        "rodCount": 2,
        "yieldPct": 90,
        "placed": 6,
        "total": 6,
        "cuts": 6,
        "totalPrice": 25
      },
      "unplaced": []
    },
    {
      "section": "50x150",
      "sw": 50,
      "sh": 150,
      "stockName": "C24 50x150",
      "rods": [
        {
          "length": 4000,
          "price": 18,
          "remaining": 1000,
          "parts": [{ "name": "Beam", "pos": 0, "length": 3000 }],
          "offcuts": [1000]
        },
        {
          "length": 4000,
          "price": 18,
          "remaining": 1000,
          "parts": [{ "name": "Beam", "pos": 0, "length": 3000 }],
          "offcuts": [1000]
        }
      ],
      "metrics": {
        "rodCount": 2,
        "yieldPct": 75,
        "placed": 2,
        "total": 2,
        "cuts": 2,
        "totalPrice": 36
      },
      "unplaced": []
    }
  ],
  "unmatched": [],
  "metrics": {
    "sectionCount": 2,
    "rodCount": 4,
    "yieldPct": 82.5,
    "placed": 8,
    "total": 8,
    "cuts": 8,
    "totalPrice": 61,
    "toleranceAcceptedCount": 0
  },
  "unplaced": [],
  "warnings": [],
  "timing": { "solveMs": 2.96 },
  "cutPlan": [
    {
      "section": "50x100",
      "sheet": 0,
      "step": 1,
      "axis": "v",
      "pos": 2400,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 0,
      "step": 2,
      "axis": "v",
      "pos": 3003,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 0,
      "step": 3,
      "axis": "v",
      "pos": 3606,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 1,
      "step": 1,
      "axis": "v",
      "pos": 2400,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 1,
      "step": 2,
      "axis": "v",
      "pos": 3003,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x100",
      "sheet": 1,
      "step": 3,
      "axis": "v",
      "pos": 3606,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x150",
      "sheet": 0,
      "step": 1,
      "axis": "v",
      "pos": 3000,
      "length": 0,
      "stage": 1
    },
    {
      "section": "50x150",
      "sheet": 1,
      "step": 1,
      "axis": "v",
      "pos": 3000,
      "length": 0,
      "stage": 1
    }
  ]
}

sections zastępuje rods na najwyższym poziomie: każdy wpis to jeden przekrój z własnymi rods (identycznymi w formie jak w 1D) i własnymi metrics, więc wartości dla danego materiału są od razu dostępne, bez przeliczania. unmatched nie ma odpowiednika w 1D — to zapotrzebowanie, dla którego przekroju nie podałeś żadnego materiału. To inny problem niż unplaced (elementy, które miały materiał i się nie zmieściły) i wymaga innej korekty, dlatego oba nigdy się nie mieszają. metrics.total liczy każdy zamówiony element, łącznie z tymi z unmatched. W planie cięcia każdy krok podaje też swoją section, a sheet to indeks pręta W OBRĘBIE tego przekroju, a nie licznik dla całego zlecenia.

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.

Silniki

Determinizm i wersjonowanie

Każda odpowiedź zawiera engineVersion. Algorytm jest deterministyczny, więc jego ulepszenie zmienia wynik dla tych samych danych wejściowych — a to zmiana łamiąca zgodność, jeśli buforujesz wyniki. Przypnij zachowanie, przesyłając engine jawnie i obserwując engineVersion; wersja w ścieżce /v1/ zmienia się tylko wtedy, gdy zmienia się struktura odpowiedzi.

Każdy silnik jest wersjonowany niezależnie, więc zmiana w jednym nigdy nie przesuwa wersji drugiego.

Błędy

400 invalid_request Błąd schematu. details.path wskazuje pole, które go wywołało.
401 unauthorized Brakujący lub nieznany klucz API.
402 quota_exceeded Wyczerpany miesięczny limit. Retry-After podaje liczbę sekund do początku nowego miesiąca.
403 key_revoked Klucz istnieje, ale został unieważniony.
404 not_found Nie ma takiej trasy — to samo otrzymasz, gdy ścieżka jest poprawna, ale metoda nie.
413 too_large Dane wejściowe przekraczają limit (patrz Limity).
429 busy Chwilowy brak wolnych zasobów. Retry-After w sekundach — to nigdy nie obciąża Twojego limitu.
500 internal Nieoczekiwany błąd albo backend uwierzytelniania jest nieosiągalny (zapytania są wtedy odrzucane — fail closed).
504 solve_timeout Obliczenia przekroczyły twardy limit czasu rzeczywistego (wymuszany na proxy).

Treść błędu

Każdy błąd generowany przez sam silnik korzysta z tej samej koperty. Rozgałęziaj logikę na polu error, które jest stabilnym kodem; nigdy na message, którego brzmienie może się zmienić między wydaniami. details występuje przy invalid_request, gdzie path wskazuje pole, które wywołało błąd, oraz przy too_large, gdzie max i got podają limit i to, co zostało przesłane.

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" }

Nagłówki limitu zapytań

Udane wywołanie optymalizacji zwraca X-RateLimit-Limit (miesięczny limit klucza) oraz X-RateLimit-Remaining (liczba wywołań pozostałych w tym miesiącu, już po tym wywołaniu). Wysyłają je wyłącznie endpointy optymalizacji: licznik jest rezerwowany w ramach autoryzacji obliczeń, więc /v1/usage i /v1/health nie mają o czym raportować.

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"
}

Tylko do odczytu: nie zużywa wywołania i nie wysyła nagłówków limitu. ⚠️ used i limit opisują KONTO, a nie klucz, którym wywołałeś: każdy aktywny klucz konta czerpie z jednej wspólnej puli, więc tworzenie kolejnych kluczy nie tworzy dodatkowego limitu. used liczy bieżący miesiąc kalendarzowy UTC dla wszystkich, remaining to limit minus used i nigdy nie schodzi poniżej zera, periodEnd to dzień zerowania jako zwykła data YYYY-MM-DD, a keyPrefix to jawny prefiks użytego klucza. Sam klucz nie jest zwracany przez żaden endpoint — przechowywany jest wyłącznie jego hash, więc zgubiony klucz się wymienia, a nie odzyskuje.

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"],
  "modes": ["2d", "1d", "wood"],
  "uptimeSec": 16
}

Bez klucza, bez limitu, bez bazy danych. Celowo nie sięga do niczego, co przechowuje stan, więc awaria magazynu kluczy nie może sprawić, że usługa wygląda dla orkiestratora na martwą. engines wypisuje identyfikatory, które to wdrożenie przyjmuje w polu engine, a engineVersion to wersja silnika domyślnego.

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.

Limity

Specyfikacja OpenAPI

Czytelny maszynowo dokument OpenAPI 3.1 opisuje wszystkie pięć endpointów, każdą treść zapytania, każdą strukturę odpowiedzi i każdy błąd. Skieruj na niego swój generator klienta, zamiast przepisywać tę stronę. Sam dokument jest wyłącznie w języku angielskim: składa się z elementów kontraktu, a OpenAPI nie ma mechanizmu lokalizacji.

curl -s https://cutoptim.com/engine/openapi.json > cutoptim-engine.json

Otwórz dokument OpenAPI 3.1 →