{
 "openapi": "3.0.1",
 "info": {
  "title": "CUTL Integration API",
  "version": "v2",
  "description": "Price DXF, STEP and PDF drawings, read projects with their quoted prices, fetch nesting results and check your coin balance from your own software.\n\nAuthenticate every request with your integration token in the `X-Auth-Token` header (click **Authorize**). Responses are JSON by default; send `Accept: application/xml` for XML. Calculations debit real coins from your team balance, so **Try it out** is enabled here for read-only (GET) requests only.\n\nGuides, billing and limits: https://cutl.online/documentation/api/"
 },
 "paths": {
  "/v2/integration/calculations": {
   "post": {
    "tags": [
     "Calculations"
    ],
    "operationId": "createIntegrationCalculation",
    "summary": "Submit a single DXF, STEP or PDF file for calculation (async)",
    "description": "Accepts a base64-encoded DXF or STEP file and starts a standalone calculation. Returns 202 with a calculation id to poll. All pricing inputs are optional; when materialId + cuttingModeId are supplied the polled result also includes the price computed by the standard calculation logic. A PDF drawing is also accepted: it is extracted into candidate parts (poll returns `pdfCandidates`, each a cut-contour DXF url); resubmit a candidate's dxfUrl here to price it.",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/IntegrationCalculationRequest"
       }
      }
     },
     "required": true
    },
    "responses": {
     "202": {
      "description": "Accepted",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationCalculation"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationCalculation"
        }
       }
      }
     },
     "400": {
      "description": "Validation error (missing/invalid file, unsupported type, file too large)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     },
     "401": {
      "description": "Missing or invalid X-Auth-Token (integration API key)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     }
    }
   }
  },
  "/v2/integration/calculations/{id}": {
   "get": {
    "tags": [
     "Calculations"
    ],
    "operationId": "getIntegrationCalculation",
    "summary": "Poll a calculation's status and result by id",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "format": "uuid"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationCalculation"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationCalculation"
        }
       }
      }
     },
     "401": {
      "description": "Missing or invalid X-Auth-Token (integration API key)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     },
     "404": {
      "description": "Calculation not found (or not visible to this API key)",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     }
    }
   }
  },
  "/v2/integration/projects": {
   "get": {
    "tags": [
     "Projects"
    ],
    "operationId": "listIntegrationProjects",
    "parameters": [
     {
      "name": "createdFrom",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "format": "date"
      }
     },
     {
      "name": "createdTo",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "format": "date"
      }
     },
     {
      "name": "updatedFrom",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "format": "date"
      }
     },
     {
      "name": "updatedTo",
      "in": "query",
      "required": false,
      "schema": {
       "type": "string",
       "format": "date"
      }
     },
     {
      "name": "page",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "format": "int32"
      }
     },
     {
      "name": "size",
      "in": "query",
      "required": false,
      "schema": {
       "type": "integer",
       "format": "int32"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/PageXmlProjectLite"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/PageXmlProjectLite"
        }
       }
      }
     },
     "400": {
      "description": "A supplied date window spans more than a month (C00026). The filters themselves are all optional, a call with no window lists the team's projects newest first.",
      "content": {
       "*/*": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     },
     "401": {
      "description": "Missing or invalid X-Auth-Token (integration API key)",
      "content": {
       "*/*": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     }
    },
    "summary": "List projects, paged, with their prices"
   }
  },
  "/v2/integration/projects/{id}": {
   "get": {
    "tags": [
     "Projects"
    ],
    "operationId": "findIntegrationProject",
    "parameters": [
     {
      "name": "id",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "format": "uuid"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/project"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/project"
        }
       }
      }
     },
     "401": {
      "description": "Missing or invalid X-Auth-Token (integration API key)",
      "content": {
       "*/*": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     },
     "404": {
      "description": "No such project, or it belongs to another team",
      "content": {
       "*/*": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     }
    },
    "summary": "Read one project with parts, material summary and nested sheets"
   }
  },
  "/v2/integration/projects/{projectId}/nesting": {
   "get": {
    "tags": [
     "Nesting"
    ],
    "operationId": "getIntegrationProjectNesting",
    "summary": "Nesting result of a project, by project id (poll)",
    "description": "The nested sheets behind a project's quote. Sheets with an identical layout are collapsed into a single entry carrying the number of physical sheets it stands for, one image and one part composition, so a run of twenty identical sheets is one entry with count=20.\nSheet images (SVG and PNG) and the per-sheet DXF are short-lived signed URLs, treat them as credentials, fetch them promptly and never store them.\nPolling only, no webhooks. The first call starts the DXF export and the PNG render and answers PENDING; keep polling until status is READY.",
    "parameters": [
     {
      "name": "projectId",
      "in": "path",
      "required": true,
      "schema": {
       "type": "string",
       "format": "uuid"
      }
     },
     {
      "name": "format",
      "in": "query",
      "required": false,
      "description": "Machine format for the per-sheet DXF. Defaults to STANDARD_DXF.",
      "schema": {
       "$ref": "#/components/schemas/IntegrationNestingExportFormat"
      }
     }
    ],
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationNesting"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationNesting"
        }
       }
      }
     },
     "401": {
      "description": "Missing or invalid X-Auth-Token (integration API key)",
      "content": {
       "*/*": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     },
     "404": {
      "description": "Project not found (or not visible to this API key)",
      "content": {
       "*/*": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     }
    }
   }
  },
  "/v2/integration/balance": {
   "get": {
    "tags": [
     "Account"
    ],
    "operationId": "getIntegrationBalance",
    "summary": "The team's current coin balance",
    "description": "Coins are what an integration spends. Every calculation, nesting job and part upload debits this balance, and the API refuses the work once the balance cannot cover it (402, errorCode C00034), so a client that never reads this number can only discover it by being refused.\n`balance` is the spendable total and the number that refusal is decided on. `bonusBalance` is the part of that total made up of bonus coins, which are spent before paid ones; the remainder, `balance - bonusBalance`, is what was paid for.\nCoins are not money and carry no currency: what a coin costs to buy depends on the account, and is a property of the purchase rather than of the balance.\nA balance can be negative. Some operations bill per item after an up-front check, so a team that started above the threshold can finish below zero.",
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationBalance"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationBalance"
        }
       }
      }
     },
     "401": {
      "description": "Missing or invalid X-Auth-Token (integration API key)",
      "content": {
       "*/*": {
        "schema": {
         "$ref": "#/components/schemas/IntegrationError"
        }
       }
      }
     }
    }
   }
  },
  "/v2/integration/data-assets/signed-url": {
   "post": {
    "tags": [
     "Files"
    ],
    "operationId": "signedUrl",
    "requestBody": {
     "content": {
      "application/json": {
       "schema": {
        "$ref": "#/components/schemas/SignedUrlRequest"
       }
      }
     },
     "required": true
    },
    "responses": {
     "200": {
      "description": "OK",
      "content": {
       "application/json": {
        "schema": {
         "$ref": "#/components/schemas/SignedUrlResponse"
        }
       },
       "application/xml": {
        "schema": {
         "$ref": "#/components/schemas/SignedUrlResponse"
        }
       }
      }
     }
    },
    "summary": "Get a fresh signed link for a stored file URL"
   }
  }
 },
 "components": {
  "schemas": {
   "IntegrationError": {
    "type": "object",
    "properties": {
     "errorCode": {
      "type": "string"
     },
     "message": {
      "type": "string"
     }
    }
   },
   "IntegrationCalculationRequest": {
    "required": [
     "fileName",
     "fileBase64"
    ],
    "type": "object",
    "properties": {
     "fileName": {
      "type": "string",
      "description": "Original file name (informational; the type is verified by sniffing the bytes, DXF, STEP or PDF)."
     },
     "fileBase64": {
      "type": "string",
      "description": "Base64-encoded file content (DXF, STEP or PDF)."
     },
     "materialId": {
      "type": "string",
      "format": "uuid",
      "description": "Optional. When set (with cuttingModeId), stored on the created library draft and used to price the result."
     },
     "cuttingModeId": {
      "type": "string",
      "format": "uuid",
      "description": "Optional. Must be supplied together with materialId."
     },
     "units": {
      "$ref": "#/components/schemas/Units"
     }
    }
   },
   "IntegrationCalculation": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string",
      "format": "uuid",
      "description": "Library draft id; poll GET /v2/integration/calculations/{id}."
     },
     "status": {
      "type": "string",
      "enum": [
       "PENDING",
       "COMPLETED",
       "FAILED"
      ]
     },
     "created": {
      "type": "string",
      "format": "date-time"
     },
     "completed": {
      "type": "string",
      "format": "date-time"
     },
     "result": {
      "$ref": "#/components/schemas/IntegrationCalculationResult"
     },
     "pdfCandidates": {
      "type": "array",
      "description": "Present when a PDF was submitted and status is COMPLETED. The extracted candidate parts, each an already-separated cut-contour DXF (`dxfUrl`, CDN/signed). To price a candidate, resubmit its dxfUrl to POST /v2/integration/calculations with material/cuttingMode/units. Several candidates may be views of one physical part (see confidence).",
      "items": {
       "$ref": "#/components/schemas/PdfCandidate"
      }
     },
     "failureReason": {
      "type": "string"
     },
     "errorCode": {
      "type": "string",
      "description": "Structured parser error code on FAILED.",
      "enum": [
       "ERROR_READ_DXF",
       "ERROR_NO_LINES",
       "ERROR_BIG_FILE",
       "STEP_ASSEMBLY",
       "STEP_NOT_LINEAR_PROFILE",
       "STEP_UNKNOWN",
       "STEP_UNSUPPORTED_GEOMETRY",
       "PDF_UNSUPPORTED",
       "PDF_EXTRACT_FAILED",
       "INSUFFICIENT_BALANCE"
      ]
     }
    }
   },
   "IntegrationCalculationResult": {
    "type": "object",
    "properties": {
     "fileType": {
      "type": "string",
      "enum": [
       "DXF",
       "STEP",
       "PDF"
      ]
     },
     "sizeXMm": {
      "type": "number"
     },
     "sizeYMm": {
      "type": "number"
     },
     "areaSqM": {
      "type": "number"
     },
     "cutLengthMm": {
      "type": "number"
     },
     "contourCount": {
      "type": "integer",
      "format": "int32"
     },
     "pierceCount": {
      "type": "integer",
      "format": "int32",
      "description": "Number of pierces (the value used by pricing)."
     },
     "units": {
      "type": "string",
      "description": "Units the geometry is in.",
      "enum": [
       "UNITLESS",
       "INCHES",
       "FEET",
       "MILES",
       "MILLIMETERS",
       "CENTIMETERS",
       "METERS",
       "KILOMETERS",
       "MICROINCHES",
       "MILS",
       "YARDS",
       "ANGSTROMS",
       "NANOMETERS",
       "MICRONS",
       "DECIMETERS",
       "DECAMETERS",
       "HECTOMETERS",
       "GIGAMETERS",
       "ASTRONOMICAL_UNITS",
       "LIGHT_YEARS",
       "PARSECS"
      ]
     },
     "validationResult": {
      "type": "object",
      "description": "Parser validation/warnings (valid flag, overlapping/unclosed contours, summary)."
     },
     "weightKg": {
      "type": "number"
     },
     "volumeM3": {
      "type": "number"
     },
     "svgBase64": {
      "type": "string",
      "description": "Base64-encoded result SVG bytes."
     },
     "thumbnailBase64": {
      "type": "string",
      "description": "Base64-encoded thumbnail (JPG) bytes."
     },
     "materialPrice": {
      "type": "number"
     },
     "cuttingPrice": {
      "type": "number"
     },
     "incuttingPrice": {
      "type": "number",
      "description": "Piercing cost (the price of all pierces)."
     },
     "singlePrice": {
      "type": "number"
     },
     "totalPrice": {
      "type": "number"
     },
     "markupPercent": {
      "type": "number"
     },
     "clientSinglePrice": {
      "type": "number"
     },
     "clientTotalPrice": {
      "type": "number"
     },
     "contours": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/IntegrationContourResult"
      }
     }
    }
   },
   "IntegrationContourResult": {
    "type": "object",
    "properties": {
     "index": {
      "type": "integer",
      "format": "int32"
     },
     "lengthMm": {
      "type": "number"
     },
     "areaMm2": {
      "type": "number"
     },
     "closed": {
      "type": "boolean"
     },
     "pointCount": {
      "type": "integer",
      "format": "int32"
     },
     "slowPointCount": {
      "type": "integer",
      "format": "int32",
      "description": "Slow/sharp-corner points."
     },
     "type": {
      "type": "string",
      "description": "Geometry kind.",
      "enum": [
       "CIRCLE",
       "POINT",
       "ELLIPSE",
       "LINE",
       "ANY",
       "SPLINE"
      ]
     },
     "lineType": {
      "type": "string",
      "description": "Line style.",
      "enum": [
       "DEFAULT",
       "DASH_DOTTED",
       "DASHED",
       "AUXILIARY"
      ]
     },
     "parentId": {
      "type": "string",
      "description": "Outer contour id this contour nests in (a hole); null for outer contours."
     }
    }
   },
   "XmlProjectLite": {
    "type": "object",
    "description": "Project as it appears in a list, no parts, no totals breakdown.",
    "properties": {
     "id": {
      "type": "string",
      "format": "uuid"
     },
     "name": {
      "type": "string"
     },
     "code": {
      "type": "string"
     },
     "description": {
      "type": "string"
     },
     "agreed": {
      "type": "boolean"
     },
     "created": {
      "type": "string",
      "format": "date-time"
     },
     "updated": {
      "type": "string",
      "format": "date-time"
     },
     "tax": {
      "type": "number"
     },
     "taxPriceIncluded": {
      "type": "boolean"
     },
     "price": {
      "type": "number"
     },
     "taxPrice": {
      "type": "number"
     },
     "clientPrice": {
      "type": "number",
      "description": "Total at the price the customer is quoted (after markup), as in XmlProjectData."
     },
     "clientTaxPrice": {
      "type": "number",
      "description": "VAT on clientPrice."
     },
     "clientTaxPriceIncluded": {
      "type": "boolean",
      "description": "Whether clientTaxPrice is included in clientPrice (true) or added on top."
     },
     "customer": {
      "$ref": "#/components/schemas/XmlCustomer"
     },
     "organisation": {
      "$ref": "#/components/schemas/XmlOrganisation"
     }
    }
   },
   "XmlProjectPart": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string",
      "format": "uuid",
      "description": "Join key for the sheet composition returned by the nesting endpoint."
     },
     "count": {
      "type": "integer",
      "format": "int32"
     },
     "cuttingCoefficient": {
      "type": "number"
     },
     "complexityCoefficient": {
      "type": "number"
     },
     "created": {
      "type": "string",
      "format": "date-time"
     },
     "updated": {
      "type": "string",
      "format": "date-time"
     },
     "fileName": {
      "type": "string"
     },
     "image": {
      "type": "string",
      "description": "Base64-encoded thumbnail bytes."
     },
     "comment": {
      "type": "string"
     },
     "material": {
      "$ref": "#/components/schemas/XmlMaterial"
     },
     "cuttingMode": {
      "$ref": "#/components/schemas/XmlCuttingMode"
     },
     "operations": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/XmlSupplementaryEntity"
      }
     },
     "materials": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/XmlSupplementaryEntity"
      }
     },
     "sizeX": {
      "type": "number"
     },
     "sizeY": {
      "type": "number"
     },
     "area": {
      "type": "number"
     },
     "cutLength": {
      "type": "number"
     },
     "contours": {
      "type": "integer",
      "format": "int32"
     },
     "cuttingTime": {
      "type": "number"
     },
     "incuttingTime": {
      "type": "number"
     },
     "supplementalEntityPrice": {
      "type": "number"
     },
     "materialPrice": {
      "type": "number"
     },
     "materialTotalPrice": {
      "type": "number"
     },
     "cuttingPrice": {
      "type": "number"
     },
     "incuttingPrice": {
      "type": "number"
     },
     "singlePrice": {
      "type": "number"
     },
     "totalCalculatedSinglePrice": {
      "type": "number"
     },
     "totalSinglePrice": {
      "type": "number"
     },
     "totalPrice": {
      "type": "number"
     },
     "taxPrice": {
      "type": "number"
     },
     "markupPercent": {
      "type": "number"
     },
     "clientSinglePrice": {
      "type": "number"
     },
     "clientTotalPrice": {
      "type": "number"
     }
    }
   },
   "XmlMaterial": {
    "type": "object",
    "properties": {
     "code": {
      "type": "string"
     },
     "name": {
      "type": "string"
     },
     "description": {
      "type": "string"
     },
     "areaPrice": {
      "type": "number",
      "description": "Price per square metre, how material is priced in practice."
     },
     "thickness": {
      "type": "number"
     },
     "density": {
      "type": "number",
      "description": "g/cm3. Absent on most non-metals."
     },
     "brand": {
      "type": "string"
     },
     "materialType": {
      "type": "string"
     },
     "pricePerKg": {
      "type": "number"
     }
    }
   },
   "XmlCuttingMode": {
    "type": "object",
    "properties": {
     "code": {
      "type": "string"
     },
     "name": {
      "type": "string"
     },
     "description": {
      "type": "string"
     },
     "cutPrice": {
      "type": "number"
     },
     "incutPrice": {
      "type": "number"
     }
    }
   },
   "XmlSupplementaryEntity": {
    "type": "object",
    "description": "A supplementary operation or material applied to a part (bending, paint, ...).",
    "properties": {
     "name": {
      "type": "string"
     },
     "quantity": {
      "type": "number"
     },
     "singlePrice": {
      "type": "number"
     },
     "totalPrice": {
      "type": "number"
     },
     "clientSinglePrice": {
      "type": "number"
     },
     "clientTotalPrice": {
      "type": "number"
     }
    }
   },
   "XmlMaterialSummary": {
    "type": "object",
    "properties": {
     "code": {
      "type": "string"
     },
     "name": {
      "type": "string"
     },
     "cutArea": {
      "type": "number"
     },
     "totalPrice": {
      "type": "number"
     },
     "clientTotalPrice": {
      "type": "number"
     }
    }
   },
   "XmlNestingSheet": {
    "type": "object",
    "description": "One physical nested sheet, as costed on the quote. Derived rather than stored: where an input is missing (density and brand are null on most materials) the derived field is absent rather than zero.",
    "properties": {
     "index": {
      "type": "integer",
      "format": "int32",
      "description": "1-based, in the order the nesting worker returned the sheets."
     },
     "materialName": {
      "type": "string"
     },
     "materialType": {
      "type": "string"
     },
     "brand": {
      "type": "string"
     },
     "thickness": {
      "type": "number"
     },
     "density": {
      "type": "number"
     },
     "pricePerKg": {
      "type": "number"
     },
     "areaPrice": {
      "type": "number"
     },
     "usedWidth": {
      "type": "number",
      "description": "Sheet width in mm."
     },
     "usedHeight": {
      "type": "number",
      "description": "Sheet height in mm."
     },
     "weightKg": {
      "type": "number"
     },
     "cost": {
      "type": "number"
     },
     "utilisationPercent": {
      "type": "number",
      "description": "0..100."
     }
    }
   },
   "IntegrationNestingExportFormat": {
    "type": "string",
    "description": "Machine format the per-sheet DXF is rendered for.",
    "default": "STANDARD_DXF",
    "enum": [
     "STANDARD_DXF",
     "CYPCUT",
     "RDWORKS"
    ]
   },
   "IntegrationNestingStatus": {
    "type": "string",
    "description": "PENDING: a nest is still solving, or its DXF export has not finished; poll again. READY, every nest is solved and exported. PARTIAL, some nests are ready and others failed. FAILED, every nest failed.\nSheet images do NOT hold this back. A PNG is a convenience for clients that cannot display SVG, and waiting for one meant a nest whose drawing would not rasterise never reported READY at all, while its sheets, counts and DXFs sat unread in the response. Poll until READY for the data; treat pngUrl as something that may appear later, or not at all.",
    "enum": [
     "PENDING",
     "READY",
     "PARTIAL",
     "FAILED"
    ]
   },
   "IntegrationNesting": {
    "type": "object",
    "properties": {
     "status": {
      "$ref": "#/components/schemas/IntegrationNestingStatus"
     },
     "nests": {
      "type": "array",
      "description": "One entry per nesting run behind the project, nests are built per material.",
      "items": {
       "$ref": "#/components/schemas/IntegrationNest"
      }
     }
    }
   },
   "IntegrationNest": {
    "type": "object",
    "properties": {
     "nestingId": {
      "type": "string",
      "format": "uuid"
     },
     "materialCode": {
      "type": "string"
     },
     "materialName": {
      "type": "string"
     },
     "thickness": {
      "type": "number"
     },
     "sheetWidth": {
      "type": "number",
      "description": "Bin width in mm."
     },
     "sheetHeight": {
      "type": "number",
      "description": "Bin height in mm."
     },
     "spacing": {
      "type": "number",
      "description": "Gap between parts in mm."
     },
     "sheetCount": {
      "type": "integer",
      "format": "int32",
      "description": "Physical sheets this nest consumes, before identical ones are collapsed."
     },
     "failureReason": {
      "type": "string"
     },
     "sheets": {
      "type": "array",
      "description": "One entry per UNIQUE sheet layout.",
      "items": {
       "$ref": "#/components/schemas/IntegrationNestingSheet"
      }
     }
    }
   },
   "IntegrationNestingSheet": {
    "type": "object",
    "properties": {
     "count": {
      "type": "integer",
      "format": "int32",
      "description": "How many physical sheets carry this layout."
     },
     "sheetIndexes": {
      "type": "array",
      "description": "1-based indexes of the physical sheets this entry stands for.",
      "items": {
       "type": "integer",
       "format": "int32"
      }
     },
     "utilisationPercent": {
      "type": "number",
      "description": "0..100."
     },
     "svgUrl": {
      "type": "string",
      "description": "Signed URL to the sheet drawing (SVG), valid ~15 minutes. Do not store."
     },
     "pngUrl": {
      "type": "string",
      "description": "Signed URL to the same drawing rasterised to PNG, valid ~15 minutes. Do not store.\nAbsent when the render has not finished, and possibly absent for good: rasterising is best-effort and never blocks status. Use svgUrl when this is missing."
     },
     "dxfUrl": {
      "type": "string",
      "description": "Signed URL to the sheet's DXF in the requested format, valid ~15 minutes. Absent while the export is still pending. Do not store."
     },
     "parts": {
      "type": "array",
      "description": "What sits on this sheet.",
      "items": {
       "$ref": "#/components/schemas/IntegrationNestingSheetPart"
      }
     }
    }
   },
   "IntegrationNestingSheetPart": {
    "type": "object",
    "properties": {
     "partId": {
      "type": "string",
      "format": "uuid",
      "description": "The project part, matching XmlProjectPart.id."
     },
     "fileName": {
      "type": "string"
     },
     "quantity": {
      "type": "integer",
      "format": "int32",
      "description": "Instances of this part on ONE sheet of this layout."
     }
    }
   },
   "IntegrationBalance": {
    "type": "object",
    "description": "A team's coin balance. See GET /v2/integration/balance for what the numbers mean.",
    "properties": {
     "balance": {
      "type": "number",
      "description": "Spendable coins, bonus included. This is the number the API checks before accepting work. May be negative."
     },
     "bonusBalance": {
      "type": "number",
      "description": "The part of `balance` made up of bonus coins. Spent before paid coins, and topped up monthly to a limit rather than accumulating."
     },
     "lowBalanceThreshold": {
      "type": "number",
      "description": "When a debit takes `balance` below this, team administrators are emailed once. Advisory, nothing stops working at this level."
     },
     "updated": {
      "type": "string",
      "format": "date-time",
      "description": "When the balance last changed."
     }
    }
   },
   "BusinessEntityData": {
    "type": "object",
    "properties": {
     "tax": {
      "type": "number"
     },
     "externalId": {
      "type": "string"
     },
     "phone": {
      "type": "string"
     },
     "email": {
      "type": "string"
     },
     "taxPriceIncluded": {
      "type": "boolean"
     },
     "name": {
      "type": "string"
     }
    }
   },
   "PageXmlProjectLite": {
    "type": "object",
    "properties": {
     "totalElements": {
      "type": "integer",
      "format": "int64"
     },
     "totalPages": {
      "type": "integer",
      "format": "int32"
     },
     "first": {
      "type": "boolean"
     },
     "last": {
      "type": "boolean"
     },
     "numberOfElements": {
      "type": "integer",
      "format": "int32"
     },
     "pageable": {
      "$ref": "#/components/schemas/PageableObject"
     },
     "size": {
      "type": "integer",
      "format": "int32"
     },
     "content": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/XmlProjectLite"
      }
     },
     "number": {
      "type": "integer",
      "format": "int32"
     },
     "sort": {
      "$ref": "#/components/schemas/SortObject"
     },
     "empty": {
      "type": "boolean"
     }
    }
   },
   "SignedUrlRequest": {
    "required": [
     "url"
    ],
    "type": "object",
    "properties": {
     "url": {
      "type": "string"
     }
    }
   },
   "SignedUrlResponse": {
    "type": "object",
    "properties": {
     "url": {
      "type": "string"
     },
     "expiresAt": {
      "type": "string",
      "format": "date-time"
     }
    }
   },
   "XmlCustomer": {
    "type": "object",
    "properties": {
     "name": {
      "type": "string"
     },
     "phone": {
      "type": "string"
     },
     "email": {
      "type": "string"
     },
     "address": {
      "type": "string"
     },
     "description": {
      "type": "string"
     }
    }
   },
   "XmlOrganisation": {
    "type": "object",
    "properties": {
     "externalId": {
      "type": "string"
     },
     "name": {
      "type": "string"
     },
     "organisation": {
      "$ref": "#/components/schemas/BusinessEntityData"
     }
    }
   },
   "project": {
    "type": "object",
    "properties": {
     "id": {
      "type": "string",
      "format": "uuid"
     },
     "name": {
      "type": "string"
     },
     "code": {
      "type": "string"
     },
     "description": {
      "type": "string"
     },
     "agreed": {
      "type": "boolean"
     },
     "created": {
      "type": "string",
      "format": "date-time"
     },
     "updated": {
      "type": "string",
      "format": "date-time"
     },
     "tax": {
      "type": "number"
     },
     "taxPriceIncluded": {
      "type": "boolean"
     },
     "price": {
      "type": "number"
     },
     "taxPrice": {
      "type": "number"
     },
     "customer": {
      "$ref": "#/components/schemas/XmlCustomer"
     },
     "organisation": {
      "$ref": "#/components/schemas/XmlOrganisation"
     },
     "clientPrice": {
      "type": "number",
      "description": "Total at the price the customer is quoted (after markup)."
     },
     "clientTaxPrice": {
      "type": "number"
     },
     "clientTaxPriceIncluded": {
      "type": "boolean"
     },
     "materialPrice": {
      "type": "number",
      "description": "Sum of the per-material totals below."
     },
     "projectParts": {
      "type": "array",
      "items": {
       "$ref": "#/components/schemas/XmlProjectPart"
      }
     },
     "materialSummary": {
      "type": "array",
      "description": "One row per material used by the project.",
      "items": {
       "$ref": "#/components/schemas/XmlMaterialSummary"
      }
     },
     "nestingSheets": {
      "type": "array",
      "description": "One row per physical nested sheet, as printed on the quote, material, weight, cost and utilisation. This is the costing view; for sheet images, part composition and DXF use GET /v2/integration/projects/{projectId}/nesting.",
      "items": {
       "$ref": "#/components/schemas/XmlNestingSheet"
      }
     },
     "currency": {
      "type": "string",
      "description": "ISO 4217 code."
     },
     "currencySign": {
      "type": "string"
     },
     "validUntil": {
      "type": "string",
      "format": "date"
     },
     "quoteFooterText": {
      "type": "string"
     },
     "paymentTerm": {
      "type": "string",
      "description": "Payment terms printed under the totals; absent when the project has none."
     },
     "versionLabel": {
      "type": "string",
      "description": "Version label on a frozen snapshot (e.g. \"v3\"); absent on a live project."
     },
     "deliveryCost": {
      "type": "number"
     }
    }
   },
   "PageableObject": {
    "type": "object",
    "properties": {
     "paged": {
      "type": "boolean"
     },
     "pageNumber": {
      "type": "integer",
      "format": "int32"
     },
     "pageSize": {
      "type": "integer",
      "format": "int32"
     },
     "unpaged": {
      "type": "boolean"
     },
     "offset": {
      "type": "integer",
      "format": "int64"
     },
     "sort": {
      "$ref": "#/components/schemas/SortObject"
     }
    }
   },
   "PdfCandidate": {
    "description": "One candidate part extracted from a PDF drawing (batchKind = PDF_CANDIDATES), a per-view cut-contour DXF. The PDF mirror of a DXF batch shape. Pick candidates by `id` via the batch import; several may be views of one physical part (low `confidence`).",
    "type": "object",
    "properties": {
     "id": {
      "type": "string",
      "description": "Selection key for the batch pick."
     },
     "index": {
      "type": "integer",
      "format": "int32"
     },
     "page": {
      "type": "integer",
      "format": "int32"
     },
     "dxfUrl": {
      "type": "string"
     },
     "confidence": {
      "type": "number"
     },
     "scale": {
      "type": "integer",
      "format": "int32",
      "description": "Detected drawing scale; absent or 1 means true size is unconfirmed."
     },
     "needsScaleConfirm": {
      "type": "boolean"
     },
     "entityCount": {
      "type": "integer",
      "format": "int32"
     },
     "viewBox": {
      "type": "array",
      "items": {
       "type": "number"
      }
     }
    }
   },
   "SortObject": {
    "type": "object",
    "properties": {
     "sorted": {
      "type": "boolean"
     },
     "unsorted": {
      "type": "boolean"
     },
     "empty": {
      "type": "boolean"
     }
    }
   },
   "Units": {
    "type": "string",
    "description": "Drawing unit override; reinterprets (rescales) DXF geometry that the parser detected in a different unit.",
    "enum": [
     "UNITLESS",
     "INCHES",
     "FEET",
     "MILES",
     "MILLIMETERS",
     "CENTIMETERS",
     "METERS"
    ]
   }
  },
  "securitySchemes": {
   "IntegrationToken": {
    "type": "apiKey",
    "in": "header",
    "name": "X-Auth-Token",
    "description": "Your integration token (account settings > Integration)."
   }
  }
 },
 "security": [
  {
   "IntegrationToken": []
  }
 ],
 "tags": [
  {
   "name": "Calculations",
   "description": "Price a single DXF, STEP or PDF drawing: submit it, then poll the result."
  },
  {
   "name": "Projects",
   "description": "Read projects with their quoted prices, parts and nested sheets."
  },
  {
   "name": "Nesting",
   "description": "Nesting behind a project quote: sheet images, part placement and per-sheet DXF."
  },
  {
   "name": "Account",
   "description": "Coin balance."
  },
  {
   "name": "Files",
   "description": "Fresh signed links for stored files."
  }
 ],
 "servers": [
  {
   "url": "https://api.cutl.online",
   "description": "Production"
  }
 ]
}
