{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://artifacts.apis.io/arcnm-io/json-schema/arcnm-io-upload-and-quote-json-schema.json",
  "title": "UploadAndQuoteJSON",
  "description": "JSON body twin of the multipart ``/upload-and-quote`` — the files ride\nas base64 (or ``data:`` URI) strings instead of multipart parts.\n\nA multipart ``UploadFile`` route cannot be driven by an MCP client (which\ncan only pass strings), so this JSON sibling is the MCP-native way to get a\nCAD file in with one call. Base64 inflates the bytes ~33% and the whole\npayload rides inside the tool call; a gzip-compressed file is accepted\n(about 5× smaller for STEP) and counts at its uncompressed size. The CAD\nsize limits — 5 MB for a single part, 25 MB for an assembly of several\nsolids, which is priced part by part — are the same on every upload path,\nthis one included. Send a large file gzip-compressed.\n\nUnknown fields are rejected, not ignored. This body is usually filled by an\nLLM working from a generated schema, where ``lotSize`` for ``lot_size`` is\nan ordinary mistake — and quietly dropping it would return 202 with a quote\npriced at lot size 1 instead of the one you asked for.",
  "x-generated": "2026-11-01",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/arcnm-io-openapi.yml#/components/schemas/UploadAndQuoteJSON",
  "properties": {
    "part_number": {
      "type": "string",
      "maxLength": 64,
      "minLength": 1,
      "title": "Part Number",
      "description": "Natural-key part number; reused to attach a new revision if the part already exists."
    },
    "costing_environment_id": {
      "anyOf": [
        {
          "type": "string",
          "format": "uuid"
        },
        {
          "type": "null"
        }
      ],
      "title": "Costing Environment Id",
      "description": "UUID of the costing environment to price against. Omit to use your organization's default environment."
    },
    "costing_environment_ids": {
      "anyOf": [
        {
          "items": {
            "type": "string",
            "format": "uuid"
          },
          "type": "array",
          "maxItems": 16,
          "minItems": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Costing Environment Ids",
      "description": "Price the part in several environments at once, in comparison order. The file is uploaded once; each environment becomes one calculation and counts as one against the plan quota. The response then carries ``batch_id`` and ``environment_runs``. Mutually exclusive with ``costing_environment_id``."
    },
    "lot_size": {
      "type": "integer",
      "maximum": 1000000000.0,
      "minimum": 1.0,
      "title": "Lot Size",
      "description": "Number of identical parts produced per batch (1 to 1,000,000,000).",
      "default": 1
    },
    "annual_volume": {
      "type": "integer",
      "maximum": 1000000000.0,
      "minimum": 1.0,
      "title": "Annual Volume",
      "description": "Expected yearly quantity used to amortize setup cost (1 to 1,000,000,000).",
      "default": 1
    },
    "material_ref": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 256
        },
        {
          "type": "null"
        }
      ],
      "title": "Material Ref",
      "description": "Free-form material reference (URN, Werkstoffnummer, AISI/SAE code, trade name, or your SKU). Unresolvable values are rejected with 422 material_unresolved plus ranked candidates. Omit to price the material read from the part's drawing (or the environment default)."
    },
    "material_grade_id": {
      "anyOf": [
        {
          "type": "string",
          "format": "uuid"
        },
        {
          "type": "null"
        }
      ],
      "title": "Material Grade Id",
      "description": "UUID of a resolved material grade; takes precedence over material_ref when both are supplied."
    },
    "engine": {
      "type": "string",
      "maxLength": 64,
      "title": "Engine",
      "description": "Pricing engine selector; retained for back-compat and always normalized to the sole engine.",
      "default": "arcnm"
    },
    "folder_id": {
      "anyOf": [
        {
          "type": "string",
          "format": "uuid"
        },
        {
          "type": "null"
        }
      ],
      "title": "Folder Id",
      "description": "Optional workspace folder to file the created part into."
    },
    "on_conflict": {
      "$ref": "#/$defs/OnPartConflict",
      "description": "How to resolve a part_number that already exists: keep_folder (add a revision, leave the part where it is — the historical behaviour), move (add a revision and re-file into folder_id), new_part (create a separate part under a distinct number), or skip (upload nothing, create no calculation, meter nothing). The outcome is always reported in ``filing``.",
      "default": "keep_folder"
    },
    "material_is_provided": {
      "type": "boolean",
      "title": "Material Is Provided",
      "description": "True when the customer supplies the raw material (beigestellt).",
      "default": false
    },
    "provided_stock_kind": {
      "type": "string",
      "enum": [
        "none",
        "near_net_profile",
        "near_net_casting",
        "sawn_blank"
      ],
      "title": "Provided Stock Kind",
      "description": "Shape of the provided stock; 'near_net_profile' scopes the plan.",
      "default": "none"
    },
    "stock_format_id": {
      "anyOf": [
        {
          "type": "string",
          "format": "uuid"
        },
        {
          "type": "null"
        }
      ],
      "title": "Stock Format Id",
      "description": "Price this calculation on one stock format — the `id` of a format from `GET /stock-formats`. An id names the format, not only the version it came from: when the format has been changed since, the version current on the day the calculation is created is used. An id your organization cannot price with today (unknown, retired or hidden) is rejected with 422 `stock_format_unresolved`. When the part cannot be cut from that format — too small, not bought in the part's thickness or material, or one the machine cannot load — or is not cut from sheet at all (milled, turned or bought finished), the calculation is priced as if none had been chosen, and `analytics.pipeline_notes` says why (`stock_format_pin_unavailable`). Omit it to price on the format that uses the least material per part."
    },
    "nest_item_id": {
      "anyOf": [
        {
          "type": "string",
          "format": "uuid"
        },
        {
          "type": "null"
        }
      ],
      "title": "Nest Item Id",
      "description": "Price this calculation's material on its part's line of a job nest — the `id` of a line (`items[].id`) of a job nest that has solved it. The material is then the line's share of the sheets the job nest cut it from together with other parts, less its share of what their skeleton and reusable rests earn back; everything else is priced as usual. The line must be of your organization, of this costing environment and of this part revision, and its job nest must have solved it; otherwise the request is rejected with 422 `nest_item_unresolved`, `details.reason` saying why. It cannot be combined with `stock_format_id` or with customer-supplied material. An upload creates a new part revision, so the upload forms reject every line. A line of the sheets an assembly shares among its own components prices that assembly only: naming one is rejected as `not_found`, and a re-run of such a component prices it on its own sheet. Omit it to price the part on its own sheet."
    },
    "region": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Region",
      "description": "Pricing region override; defaults to the costing environment's region when omitted."
    },
    "currency": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Currency",
      "description": "ISO 4217 currency code for the quote; defaults to the costing environment's currency when omitted."
    },
    "language": {
      "type": "string",
      "maxLength": 8,
      "title": "Language",
      "description": "BCP 47 language for the calculation's captured context (e.g. 'de', 'en'). Part of the calculation's cache identity.",
      "default": "de"
    },
    "cad_file_b64": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Cad File B64",
      "description": "Base64 (or a data: URI) of the 3D CAD file (e.g. STEP). Required unless precheck_id is sent."
    },
    "cad_filename": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 255,
          "minLength": 1
        },
        {
          "type": "null"
        }
      ],
      "title": "Cad Filename",
      "description": "CAD filename including extension, e.g. bracket.step — used for format detection. Required with cad_file_b64."
    },
    "precheck_id": {
      "anyOf": [
        {
          "type": "string",
          "format": "uuid"
        },
        {
          "type": "null"
        }
      ],
      "title": "Precheck Id",
      "description": "Start this precheck (POST /prechecks): its stored file, drawings and statements — send no file. Starting it again answers with the calculation it started."
    },
    "precheck_revision": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 32
        },
        {
          "type": "null"
        }
      ],
      "title": "Precheck Revision",
      "description": "The revision the precheck showed; refused with 409 precheck_changed when what it decides moved since."
    },
    "max_overage_cents": {
      "anyOf": [
        {
          "type": "integer",
          "maximum": 1000000000.0,
          "minimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Max Overage Cents",
      "description": "The most this calculation may cost past your plan's included calculations, in cents; parts past it wait (run_spend_cap)."
    },
    "cad_content_type": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Cad Content Type",
      "description": "MIME type of the CAD file, when known."
    },
    "sheet_gauge_mm": {
      "anyOf": [
        {
          "type": "number",
          "maximum": 1000.0,
          "exclusiveMinimum": 0.0
        },
        {
          "type": "null"
        }
      ],
      "title": "Sheet Gauge Mm",
      "description": "Sheet thickness in millimetres — required when the CAD file is a DXF flat pattern (a 2D cutting file carries no thickness); ignored for STEP."
    },
    "drawing_file_b64": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Drawing File B64",
      "description": "Optional base64 (or data: URI) of a 2D drawing (PDF/PNG/JPEG)."
    },
    "drawing_filename": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 255
        },
        {
          "type": "null"
        }
      ],
      "title": "Drawing Filename",
      "description": "Drawing filename including extension."
    },
    "drawing_content_type": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Drawing Content Type",
      "description": "MIME type of the drawing file, when known."
    },
    "rfq_file_b64": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Rfq File B64",
      "description": "Optional base64 (or data: URI) of an RFQ text file."
    },
    "rfq_filename": {
      "anyOf": [
        {
          "type": "string",
          "maxLength": 255
        },
        {
          "type": "null"
        }
      ],
      "title": "Rfq Filename",
      "description": "RFQ filename including extension."
    },
    "rfq_content_type": {
      "anyOf": [
        {
          "type": "string"
        },
        {
          "type": "null"
        }
      ],
      "title": "Rfq Content Type",
      "description": "MIME type of the RFQ file, when known."
    }
  },
  "additionalProperties": false,
  "type": "object",
  "required": [
    "part_number"
  ],
  "$defs": {
    "OnPartConflict": {
      "type": "string",
      "enum": [
        "keep_folder",
        "move",
        "new_part",
        "skip"
      ],
      "title": "OnPartConflict",
      "description": "What to do when the ``part_number`` already exists in this workspace.\n\nPart identity is ``(org_id, part_number)`` and the number is derived from\nthe filename, so re-uploading ``977_000.STEP`` matches the existing part\n``977``. That is a conflict, and it used to be resolved silently: the\nrevision was attached to the existing part and the requested\n``folder_id`` was thrown away without a word.\n\n``keep_folder`` reproduces that behaviour exactly, so no existing caller\nchanges shape — but it is now a choice the caller made, and whatever\nhappens is reported back in ``filing``."
    }
  }
}