Documentación de la API
El optimizador como servicio HTTP: envíe las piezas y el material, reciba un plan de corte verificado o un PDF listo para imprimir. Estable, versionado y retrocompatible: se añaden campos, nunca se eliminan.
Introducción
URL base https://api.linearcutting.com. Todo es JSON, excepto el endpoint de PDF. Las peticiones no tienen estado: no guardamos sus listas de corte.
No necesita una clave de API para probar esto. La siguiente línea se puede ejecutar tal cual. Péguela en un terminal.
curl -X POST https://api.linearcutting.com/v1/optimise \
-H "Content-Type: application/json" \
-d '{"parts": "2400x5, 1800x3", "stock": 6000}' Eso es una lista de corte completa: cinco piezas de 2400, tres de 1800, cortadas de barras de 6000mm. Recibes los esquemas de corte, cuántas barras se necesitan, cuánto se desperdicia y si el plan es demostrablemente óptimo.
Las llamadas no autenticadas se ejecutan en un nivel público gratuito para que pueda evaluar el producto antes de pagar: 30 peticiones por hora y 250 al mes natural, por dirección IP. Superado ese límite, devuelve un 429 que indica cuándo se restablece. Las llamadas fallidas no cuentan. Para un proyecto real, consiga una clave desde 5 $ al mes: elimina ambos límites, aumenta los topes de tamaño y le da concurrencia propia para que una tarde de mucho trabajo en el nivel público no le ralentice.
Clientes oficiales
Cero dependencias, con licencia MIT, y tampoco necesitan clave. Si usa Python o JavaScript, no implemente un fetch manualmente:
import linearcutting as lc
plan = lc.optimise(parts="2400x5, 1800x3", stock=6000)
plan.bars_used # 4
plan.optimal # True, proved, not guessed import { optimise } from "linearcutting";
const plan = await optimise({ parts: "2400x5, 1800x3", stock: 6000 });
plan.barsUsed; // 4
plan.optimal; // true Python en PyPI · JavaScript en npm · Código fuente de Python · Código fuente de JavaScript
Dos garantías fundamentales. verified es siempre true si la operación se completa con éxito: cada plan de corte se vuelve a auditar con sus datos de entrada antes de ser devuelto, para que nunca reciba un plan que no sirva para cortar su lista. optimal es una prueba, no una estimación; es true solo cuando la búsqueda ha confirmado que no existe un plan de corte mejor, ya sea por agotar las posibilidades o mediante un certificado. Cuando no se puede demostrar dentro del tiempo asignado, devolvemos false en lugar de suponer.
optimal y stockBarsLowerBound son cosas distintas y discrepan a propósito. El límite inferior es una relajación, básicamente «longitud total de partes ÷ longitud de la barra», y a menudo es inalcanzable. Cinco partes de 2400 y tres de 1800 suman 17.400 mm, que son 2,9 barras de 6000, por lo que el límite inferior es 3. Pero no es posible cortar esas partes de 3 barras; 4 es el mínimo real, y el optimizador lo demuestra. Por tanto, stockBarsUsed: 4 con stockBarsLowerBound: 3 y optimal: true no es una contradicción. Significa que el límite era impreciso, no que el plan de corte fuera incorrecto. Utilice optimal para decidir si seguir buscando y el límite inferior solo como una comprobación.
Forma abreviada
La forma larga es explícita y un poco enrevesada. Para el caso común, use la forma corta: un número es una longitud y x significa la cantidad:
{"parts": "2400x5, 1800x3, 600", "stock": 6000} Todos estos ejemplos significan lo mismo, así que usa el que mejor se adapte a tu código:
{"parts": "2400x5"}
{"parts": ["2400x5"]}
{"parts": [[2400, 5]]}
{"parts": [{"length": 2400, "quantity": 5}]} Un tercer elemento es una etiqueta en una parte y un precio en el material de stock: [2400, 5, "rail"], [6000, 20, 45.50]. Y stock es el nombre corto para la antigua división stockLength / stocks; un número simple significa «esta longitud, compre tantas como necesite».
La forma corta se expande antes que nada, por lo que es una forma de escribir en lugar de un modo diferente: una solicitud corta y la solicitud larga a la que se expande devuelven planes de corte idénticos byte por byte. Puede mezclarlas libremente; todas las solicitudes existentes siguen funcionando exactamente igual.
Una lista de corte en una URL
Los trabajos pequeños caben en una cadena de consulta, así que GET también funciona. Esto es útil para un enlace que quieras compartir, presupuestar o adjuntar en un informe de error:
curl "https://api.linearcutting.com/v1/optimise?parts=2400x5,1800x3&stock=6000" Misma autenticación, mismos límites, mismo plan que con POST. Puedes abrirlo en un navegador si quieres.
Autenticación
El nivel público no necesita clave. Los niveles de pago envían su clave en una cabecera; las claves también contienen los orígenes de navegador permitidos, para que las llamadas desde tu propio sitio web funcionen sin un proxy.
X-API-Key: lk_live_9f2c... Una clave incorrecta o caducada devuelve 401. Las claves se emiten por espacio de trabajo: ver planes.
POST /v1/optimise
Resuelve una lista de corte. Devuelve el plan, estadísticas y límites demostrables.
Cuerpo de la petición
| Field | Tipo | Por defecto | Descripción |
|---|---|---|---|
parts requerido | array | - | Las piezas que necesitas. Cada una: length (> 0), quantity (entero ≥ 1), y opcionalmente label, material, leftAngle, rightAngle. |
stockLength | número | - | Un único largo de material ilimitado. Usa este campo o stocks, no ambos. |
stocks | array | - | Longitudes de compra. Cada una: length, quantity (null = ilimitada), price (null = optimizar material en lugar de coste), material opcional, priority. |
offcuts | array | [] | Longitudes que ya tiene en stock: length, quantity, material opcional, priority. Es material gratuito, no se cuenta como compra. |
kerf | número ≥ 0 | 0 | Ancho del corte. |
trim | booleano | false | Despuntar ambos extremos de cada barra antes de cortar. |
method | string | balanced | balanced, least_waste, offcuts_first, fewest_setups (alias: fewest_setups). Los precios en stocks cambian balanced a la opción de menor coste. |
minRemnant | número ≥ 0 | 0 | Los sobrantes de esta longitud o más largos se devuelven como usableRemnants en lugar de desperdicio. |
angleCuts | objeto | desactivado | {enabled, width, axialSymmetry}. Vea cortes en ángulo. |
unit | cadena | "mm" | Se devuelve en la respuesta y se imprime en los PDF. No convierte ninguna unidad. |
unitFormat | cadena | decimal | fraction representa las dimensiones en el PDF como fracciones redondeadas a 1/64. |
timeLimit | segundos | automático | Tiempo de cálculo, limitado por su plan. La búsqueda siempre devuelve un resultado; un tiempo de cálculo mayor solo lo mejora. |
Respuesta
{
"plan": {
"combinations": [
{
"usedComb": {
"combination": [2400, 2400, 1200],
"length": 6500,
"source": "stock",
"waste": { "total": 492, "remnant": 484, "saw": 8 }
},
"count": 2
}
],
"purchases": [{ "length": 6500, "count": 2 }],
"usableRemnants": [],
"unusedOffcuts": [],
"stockBarsUsed": 2,
"stockBarsLowerBound": 2,
"totalWaste": 984,
"optimal": true,
"verified": true,
"solveTimeMs": 12.5
},
"stats": {
"totalParts": 8, "totalPartsLength": 15600, "totalUsedLength": 16600,
"yieldPercent": 93.98, "cutCount": 8, "layoutCount": 1,
"totalCost": null, "trueWaste": 984
},
"costLowerBound": null,
"meta": { "method": "balanced", "kerf": 4, "tier": "public",
"poweredBy": "linearcutting.com" }
} | Field | Descripción |
|---|---|
plan.combinations[] | Una entrada por cada esquema de corte distinto. usedComb.combination enumera las piezas cortadas de esa barra, length es el origen, source es material o retal, y count es cuántas barras cortar de esa manera. |
usedComb.waste | total = origen − piezas; dividido en saw (ancho de corte + recorte) y remnant (el sobrante final). |
plan.purchases[] | La lista de la compra: length, count, más price/cost cuando se han indicado precios. |
plan.usableRemnants | Sobrantes ≥ minRemnant, de mayor a menor: el material de retales para el futuro. |
plan.unusedOffcuts | Retales que el plan no ha necesitado. |
plan.stockBarsUsed | Total de barras a comprar. |
plan.optimal | Prueba. Es `true` solo cuando el plan alcanza un mínimo demostrable (o si la búsqueda se ha completado de forma exhaustiva). Se puede mostrar al usuario como "el mejor resultado posible". |
plan.verified | Siempre `true` si el proceso tiene éxito: la demanda coincide, cada esquema de corte es físicamente posible y se respeta el suministro de retales. |
plan.stockBarsLowerBound, costLowerBound | Mínimos demostrables. Cuando optimal es `false`, la diferencia indica el margen máximo de error del plan. |
stats | Totales del trabajo: piezas, longitudes, yieldPercent, cutCount, layoutCount, totalCost, trueWaste y byMaterial cuando se usan grupos de materiales. |
POST /v1/optimise/pdf
Mismo cuerpo de solicitud, más un jobName opcional. Devuelve application/pdf: la página 1 es un resumen para el jefe de proyecto (orden de compra, costes, certificado de optimalidad); las páginas siguientes son hojas para el operario de la sierra con posiciones de corte, etiquetas para las piezas y una casilla de verificación por barra. Las llamadas con clave muestran la atribución o marca del cliente.
curl -X POST https://api.linearcutting.com/v1/optimise/pdf \
-H "Content-Type: application/json" \
-H "X-API-Key: lk_live_9f2c..." \
-d '{ "parts": [{"length": 2400, "quantity": 4}], "stockLength": 6500,
"kerf": 4, "jobName": "Smith deck order" }' \
--output cutting-plan.pdf GET /v1/health
{ "status": "ok", "version": "1.3.0", "solver": "Linear Cutlist Optimizer v2" } Grupos de materiales
Etiquete parts, stocks y offcuts con material y cada material se optimiza como un trabajo independiente dentro de una misma solicitud: los cortes solo se asignan a material del mismo tipo. Los diagramas y las compras se devuelven etiquetados, y stats.byMaterial desglosa las cifras por grupo.
{
"parts": [
{ "length": 2400, "quantity": 4, "material": "40x40 SHS" },
{ "length": 1300, "quantity": 6, "material": "Pine 90x45" }
],
"stocks": [
{ "length": 6500, "quantity": null, "material": "40x40 SHS" },
{ "length": 5400, "quantity": null, "material": "Pine 90x45", "priority": true }
],
"kerf": 3
} priority: true en una barra o retal es una preferencia no estricta: entre planes de igual calidad, los elementos priorizados se usan primero. Nunca se sacrifica la calidad del plan.
Cortes en ángulo / inglete
Asigne a las piezas un leftAngle y rightAngle y active angleCuts. Los ángulos son los ángulos inferiores de la pieza en su posición de corte; introduzca un ángulo superior como un número negativo (120 y -60 son el mismo corte). Rango válido después de esa transformación: de 10 a 170 grados.
| angleCuts field | Descripción |
|---|---|
enabled | Interruptor general. Los ángulos en las piezas solo se aceptan si es `true`. |
width | Ancho del material, en la misma unidad que las longitudes. Un corte en ángulo se desplaza lateralmente width / tan(angle). |
axialSymmetry | none, X, Y, Z o XYZ: volteos que permite el material, que deciden cómo pueden anidar los ingletes. El tubo cuadrado es normalmente XYZ; el material con una sola cara es none. |
Cuando dos ingletes contiguos son paralelos, un solo corte de sierra crea ambos bordes. Los diagramas en modo de ángulo añaden un array parts ordenado (con la orientación usada) y cutPositions: cada corte medido desde el extremo cero de la barra, como un par [superior, inferior] si es en ángulo, o un solo número si es recto.
"cutPositions": [[1200, 1300], [2442.265, 2500], 3300] Errores
El cuerpo de los errores es { "error": "...", "detail": "..." }. El campo detail está escrito para mostrarlo a una persona: nombra la pieza o el material que causa el error.
| Status | error | Cuándo |
|---|---|---|
| 400 | validation | Longitudes o cantidades incorrectas, método desconocido, una pieza que no cabe en ninguna barra, demanda que excede el material finito, reglas de ángulo no cumplidas, o envío simultáneo de stocks y stockLength. |
| 401 | badKey | Clave de API inexistente, desconocida o desactivada. |
| 413 | partsLimit | Más piezas de las que permite su plan. |
| 429 | rateLimited | Límite de frecuencia o mensual alcanzado. Respete el encabezado Retry-After. |
| 503 | busy | Optimizador a plena capacidad. Reintente en breve. |
Límites de frecuencia
Los límites restringen, nunca generan una factura por excedente. Las solicitudes con errores de validación no cuentan para su límite mensual. Las cifras completas están en la página de planes.
| Guard | Gratuito | Planes de pago |
|---|---|---|
| Límite | 10 peticiones / 5 min por IP | 20-240 / min por nivel |
| Piezas totales | 300 | 500 - 5,000 |
| Líneas de piezas distintas | 60 | hasta 400 |
| Límite de cálculo | 20 s | 60 - 300 s |
El número de líneas de piezas distintas está limitado, además del total de piezas, porque el coste del cálculo depende de la diversidad de longitudes: 300 cortes idénticos se optimizan en milisegundos, pero 300 distintos no.
Ejemplos
Combinación más económica de varios largos de material (JavaScript)
const res = await fetch("https://api.linearcutting.com/v1/optimise", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-Key": "lk_live_9f2c...",
},
body: JSON.stringify({
parts: [
{ length: 3000, quantity: 8 },
{ length: 1200, quantity: 10 },
],
stocks: [
{ length: 6500, quantity: null, price: 45 }, // unlimited
{ length: 12000, quantity: 4, price: 70 }, // only four on hand
],
offcuts: [{ length: 2600, quantity: 1 }],
kerf: 4,
minRemnant: 400,
method: "balanced", // prices present -> optimises dollars
}),
});
const { plan, stats } = await res.json();
console.log(plan.purchases); // what to buy
console.log(stats.totalCost); // what it costs
console.log(plan.optimal); // true = provably the cheapest Piezas para marcos con cortes a inglete (Python)
import requests
r = requests.post(
"https://api.linearcutting.com/v1/optimise",
json={
"parts": [
{"length": 1300, "quantity": 2, "leftAngle": 90, "rightAngle": 45},
{"length": 1200, "quantity": 2, "leftAngle": 135, "rightAngle": 60},
],
"stockLength": 3310,
"kerf": 0,
"angleCuts": {"enabled": True, "width": 100, "axialSymmetry": "none"},
},
timeout=30,
)
r.raise_for_status()
for combo in r.json()["plan"]["combinations"]:
print(combo["count"], "x", combo["usedComb"]["cutPositions"]) Gestionar un trabajo grande y lento
Las listas grandes pueden tardar de segundos a minutos. La búsqueda es continua, así que ajusta timeLimit al tiempo que tu interfaz pueda esperar y muestra optimal para indicar si la solución está probada o es simplemente la mejor encontrada hasta el momento.
{ "parts": [ /* 900 parts */ ], "stockLength": 6500,
"kerf": 4, "timeLimit": 60 }