{
  "openapi": "3.1.0",
  "info": {
    "title": "Foudra MRP API",
    "version": "1",
    "description": "JSON over HTTPS on your company's own subdomain. Decimals (quantities, money) are strings, never JSON numbers. Errors are `{\"error\": \"…\"}` with a meaningful status. Guides and examples: https://foudra.com/developers"
  },
  "servers": [
    {
      "url": "https://{company}.foudra.com",
      "variables": {
        "company": {
          "default": "demo",
          "description": "Your company's subdomain"
        }
      }
    }
  ],
  "security": [
    {
      "bearer": []
    },
    {
      "oauth2": [
        "read"
      ]
    }
  ],
  "tags": [
    {
      "name": "Parts"
    },
    {
      "name": "BOM and routing"
    },
    {
      "name": "Images"
    },
    {
      "name": "Documents"
    },
    {
      "name": "Stock and lots"
    },
    {
      "name": "Suppliers and sourcing"
    },
    {
      "name": "Purchase orders"
    },
    {
      "name": "Demand"
    },
    {
      "name": "Channels"
    },
    {
      "name": "Tasks"
    },
    {
      "name": "Assistant"
    },
    {
      "name": "Planning"
    },
    {
      "name": "Work orders"
    },
    {
      "name": "Workstations"
    },
    {
      "name": "Custom data"
    }
  ],
  "paths": {
    "/api/parts": {
      "get": {
        "operationId": "listParts",
        "tags": [
          "Parts"
        ],
        "summary": "List parts",
        "description": "Parts, paged. `category` narrows to one category; `q` searches number, name and description.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per"
          },
          {
            "name": "category",
            "in": "query",
            "description": "A category code, e.g. 504.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "q",
            "in": "query",
            "description": "Search text.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Part"
                          }
                        }
                      },
                      "required": [
                        "data"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      },
      "post": {
        "operationId": "createPart",
        "tags": [
          "Parts"
        ],
        "summary": "Create a part",
        "description": "Creates a part; the stock number is allocated in the category. Returns the part.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "category_code": {
                    "type": "string"
                  },
                  "name": {
                    "type": "string"
                  },
                  "part_type": {
                    "type": "string"
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "unit_of_measure": {
                    "type": "string",
                    "default": "each"
                  },
                  "is_stocked": {
                    "type": "boolean",
                    "default": true
                  },
                  "minimum_stock": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "lead_time_days": {
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "tracking": {
                    "type": "string",
                    "enum": [
                      "none",
                      "lot",
                      "serial"
                    ]
                  },
                  "custom": {
                    "type": "object",
                    "description": "Opaque JSON kept for integrations (64 KB max)."
                  }
                },
                "required": [
                  "category_code",
                  "name",
                  "part_type"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Part"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/parts/{id}": {
      "get": {
        "operationId": "getPart",
        "tags": [
          "Parts"
        ],
        "summary": "Get a part",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Part"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/parts/{id}/barcode.png": {
      "get": {
        "operationId": "getPartBarcode",
        "tags": [
          "Parts"
        ],
        "summary": "A part's barcode",
        "description": "The part number as a Code 128 barcode PNG, the number printed beneath.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "name": "scale",
            "in": "query",
            "description": "The narrowest bar's width in pixels.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 3
            }
          },
          {
            "name": "height",
            "in": "query",
            "description": "The bars' height in pixels (at most 600).",
            "schema": {
              "type": "integer",
              "maximum": 600
            }
          },
          {
            "name": "text",
            "in": "query",
            "description": "0 for bars only.",
            "schema": {
              "type": "string",
              "enum": [
                "0"
              ]
            }
          },
          {
            "name": "download",
            "in": "query",
            "description": "1 to send it as an attachment.",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The PNG.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "description": "The number can't be drawn as Code 128 (plain text)."
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/parts/{id}/bom": {
      "get": {
        "operationId": "getBOM",
        "tags": [
          "BOM and routing"
        ],
        "summary": "A part's BOM",
        "description": "The part's BOM lines: child part, quantity per, reference, routing step.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/BOMLine"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/parts/{id}/bom/lines": {
      "post": {
        "operationId": "addBOMLine",
        "tags": [
          "BOM and routing"
        ],
        "summary": "Add a BOM line",
        "description": "Adds a line; the cycle rules apply.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "child_part_id": {
                    "type": "integer",
                    "format": "int64"
                  },
                  "quantity": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "reference": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "routing_step_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "format": "int64"
                  }
                },
                "required": [
                  "child_part_id",
                  "quantity"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "line_no": {
                          "type": "integer"
                        },
                        "quantity": {
                          "$ref": "#/components/schemas/Decimal"
                        }
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/bom/lines/{id}": {
      "put": {
        "operationId": "updateBOMLine",
        "tags": [
          "BOM and routing"
        ],
        "summary": "Edit a BOM line",
        "description": "Fields not sent keep their value.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "quantity": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "reference": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/parts/{id}/routing": {
      "get": {
        "operationId": "getRouting",
        "tags": [
          "BOM and routing"
        ],
        "summary": "A part's routing",
        "description": "The part's routing steps, with totals.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/RoutingStep"
                      }
                    },
                    "totals": {
                      "type": "object",
                      "properties": {
                        "setup_minutes": {
                          "type": "integer"
                        },
                        "run_minutes_per_unit": {
                          "$ref": "#/components/schemas/Decimal"
                        },
                        "steps": {
                          "type": "integer"
                        }
                      }
                    }
                  },
                  "required": [
                    "data",
                    "totals"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/parts/{id}/routing/steps": {
      "post": {
        "operationId": "addRoutingStep",
        "tags": [
          "BOM and routing"
        ],
        "summary": "Add a routing step",
        "description": "`seq` blank takes the next ten.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  },
                  "group_code": {
                    "type": "string",
                    "description": "A workstation group's code."
                  },
                  "seq": {
                    "type": "integer"
                  },
                  "setup_minutes": {
                    "type": "integer"
                  },
                  "run_minutes_per_unit": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "batch_size": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "instructions": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "name"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "seq": {
                          "type": "integer"
                        },
                        "name": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/parts/{id}/images": {
      "get": {
        "operationId": "listPartImages",
        "tags": [
          "Images"
        ],
        "summary": "A part's images",
        "description": "The part's images. With none of its own, its category's, marked `from_category`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Image"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      },
      "post": {
        "operationId": "uploadPartImage",
        "tags": [
          "Images"
        ],
        "summary": "Upload an image",
        "description": "Uploads an image (12 MB max), inside the company's storage limits; the first becomes primary.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  }
                },
                "required": [
                  "file"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Image"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/images/{id}": {
      "get": {
        "operationId": "getImageFile",
        "tags": [
          "Images"
        ],
        "summary": "An image's file",
        "description": "The image as uploaded (scaled down if it was very large).",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "The file. Its Content-Type is the file's own (image/jpeg, application/pdf, …).",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match)."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      },
      "delete": {
        "operationId": "deleteImage",
        "tags": [
          "Images"
        ],
        "summary": "Delete an image",
        "description": "Deletes one of a part's images. A category's image is refused (422): every part in the category without its own shows it.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/images/{id}/thumb": {
      "get": {
        "operationId": "getImageThumb",
        "tags": [
          "Images"
        ],
        "summary": "An image's thumbnail",
        "description": "A JPEG thumbnail, or the image itself when it is already small.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "The file. Its Content-Type is the file's own (image/jpeg, application/pdf, …).",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match)."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/parts/{id}/documents": {
      "get": {
        "operationId": "listPartDocuments",
        "tags": [
          "Documents"
        ],
        "summary": "A part's documents",
        "description": "Data sheets, manuals, drawings, 3D models, G-code. For G-code, `printer_model` is the printer it was sliced for.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Document"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      },
      "post": {
        "operationId": "uploadPartDocument",
        "tags": [
          "Documents"
        ],
        "summary": "Upload a document",
        "description": "Uploads a PDF, .docx, Markdown, STL, 3MF or G-code (.gcode, .bgcode, .nc …) document: 20 MB max, Markdown 256 KB, 3D models and G-code 50 MB, inside the company's storage limits. The format is decided from the file's bytes.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary"
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "datasheet",
                      "technical_data",
                      "safety_data",
                      "manual",
                      "assembly",
                      "drawing",
                      "certificate",
                      "model_3d",
                      "gcode",
                      "other"
                    ]
                  },
                  "title": {
                    "type": "string",
                    "description": "Defaults to the file's name."
                  },
                  "revision": {
                    "type": "string",
                    "description": "e.g. Rev C, 2024-03."
                  }
                },
                "required": [
                  "file",
                  "type"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Document"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/documents/{id}": {
      "get": {
        "operationId": "getDocumentFile",
        "tags": [
          "Documents"
        ],
        "summary": "A document's file",
        "description": "The file as uploaded. PDFs are sent inline, everything else as an attachment.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "The file. Its Content-Type is the file's own (image/jpeg, application/pdf, …).",
            "content": {
              "application/octet-stream": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            },
            "headers": {
              "ETag": {
                "schema": {
                  "type": "string"
                }
              }
            }
          },
          "304": {
            "description": "Not modified (If-None-Match)."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      },
      "delete": {
        "operationId": "deleteDocument",
        "tags": [
          "Documents"
        ],
        "summary": "Delete a document",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/stock": {
      "get": {
        "operationId": "listStock",
        "tags": [
          "Stock and lots"
        ],
        "summary": "Stock on hand",
        "description": "One row per part, location and lot, by part number.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/StockRow"
                          }
                        }
                      },
                      "required": [
                        "data"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/lots": {
      "get": {
        "operationId": "listLots",
        "tags": [
          "Stock and lots"
        ],
        "summary": "Lots and serial numbers",
        "description": "Newest first.",
        "parameters": [
          {
            "$ref": "#/components/parameters/page"
          },
          {
            "$ref": "#/components/parameters/per"
          },
          {
            "name": "q",
            "in": "query",
            "description": "Search text.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "allOf": [
                    {
                      "$ref": "#/components/schemas/Page"
                    },
                    {
                      "type": "object",
                      "properties": {
                        "data": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/Lot"
                          }
                        }
                      },
                      "required": [
                        "data"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/lots/{id}/barcode.png": {
      "get": {
        "operationId": "getLotBarcode",
        "tags": [
          "Stock and lots"
        ],
        "summary": "A lot's barcode",
        "description": "The lot's or serial's code as a Code 128 PNG; the same query parameters as a part's barcode.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "name": "scale",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 10,
              "default": 3
            }
          },
          {
            "name": "height",
            "in": "query",
            "schema": {
              "type": "integer",
              "maximum": 600
            }
          },
          {
            "name": "text",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "0"
              ]
            }
          },
          {
            "name": "download",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "1"
              ]
            }
          }
        ],
        "responses": {
          "200": {
            "description": "The PNG.",
            "content": {
              "image/png": {
                "schema": {
                  "type": "string",
                  "format": "binary"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/suppliers": {
      "get": {
        "operationId": "listSuppliers",
        "tags": [
          "Suppliers and sourcing"
        ],
        "summary": "The suppliers",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Supplier"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/parts/{id}/sourcing": {
      "get": {
        "operationId": "getPartSourcing",
        "tags": [
          "Suppliers and sourcing"
        ],
        "summary": "Who sells a part",
        "description": "Who sells the part, as what, for how much.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PartSource"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/parts/{id}/sourcing/{supplier}": {
      "put": {
        "operationId": "putPartSourcing",
        "tags": [
          "Suppliers and sourcing"
        ],
        "summary": "Record a supplier for a part",
        "description": "Creates the supplier's row for the part (201) or updates it (200); fields not sent keep their value. A part's first source becomes preferred. `{supplier}` is the supplier's code.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "name": "supplier",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "supplier_part_number": {
                    "type": "string",
                    "description": "Required for a new source."
                  },
                  "unit_price": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "currency": {
                    "type": "string"
                  },
                  "product_url": {
                    "type": "string"
                  },
                  "minimum_order_qty": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "pack_size": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "lead_time_days": {
                    "type": "integer"
                  },
                  "is_preferred": {
                    "type": "boolean"
                  },
                  "notes": {
                    "type": "string"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "supplier_part_number": {
                          "type": "string"
                        },
                        "unit_price": {
                          "oneOf": [
                            {
                              "$ref": "#/components/schemas/Decimal"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "is_preferred": {
                          "type": "boolean"
                        }
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "supplier_part_number": {
                          "type": "string"
                        },
                        "unit_price": {
                          "oneOf": [
                            {
                              "$ref": "#/components/schemas/Decimal"
                            },
                            {
                              "type": "null"
                            }
                          ]
                        },
                        "currency": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "is_preferred": {
                          "type": "boolean"
                        }
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/sourcing": {
      "get": {
        "operationId": "searchSourcing",
        "tags": [
          "Suppliers and sourcing"
        ],
        "summary": "Look up sourcing",
        "description": "Which of our parts is a supplier's number, or the reverse. Case-insensitive.",
        "parameters": [
          {
            "name": "supplier",
            "in": "query",
            "description": "Supplier code.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "supplier_part_number",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "part_number",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Sourcing"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/purchase-orders": {
      "get": {
        "operationId": "listPurchaseOrders",
        "tags": [
          "Purchase orders"
        ],
        "summary": "Purchase orders",
        "description": "Orders with supplier, subtotal and a link. `number` finds one the way people name it.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "number",
            "in": "query",
            "description": "e.g. PO-00004.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PurchaseOrderSummary"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      },
      "post": {
        "operationId": "createPurchaseOrder",
        "tags": [
          "Purchase orders"
        ],
        "summary": "Create a draft purchase order",
        "description": "Creates a **draft**; lines given by the supplier's number are resolved through sourcing. An unknown number fails the whole request, naming the line. A person approves.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "supplier": {
                    "type": "string",
                    "description": "The supplier's code."
                  },
                  "currency": {
                    "type": "string",
                    "description": "Default: the supplier's."
                  },
                  "exchange_rate": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "expected_at": {
                    "type": "string",
                    "format": "date"
                  },
                  "external_ref": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "lines": {
                    "type": "array",
                    "items": {
                      "type": "object",
                      "properties": {
                        "part_id": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "supplier_part_number": {
                          "type": "string"
                        },
                        "quantity": {
                          "$ref": "#/components/schemas/Decimal"
                        },
                        "unit_price": {
                          "type": "string",
                          "description": "Default: the sourcing price."
                        },
                        "description": {
                          "type": [
                            "string",
                            "null"
                          ]
                        }
                      },
                      "required": [
                        "quantity"
                      ]
                    },
                    "description": "Each line gives part_id or supplier_part_number."
                  }
                },
                "required": [
                  "supplier",
                  "lines"
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PurchaseOrder"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/purchase-orders/{id}": {
      "get": {
        "operationId": "getPurchaseOrder",
        "tags": [
          "Purchase orders"
        ],
        "summary": "A purchase order",
        "description": "An order with its lines.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/PurchaseOrder"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/purchase-orders/{id}/cancel": {
      "post": {
        "operationId": "cancelPurchaseOrder",
        "tags": [
          "Purchase orders"
        ],
        "summary": "Cancel a purchase order",
        "description": "Needs the planner role.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "$ref": "#/components/schemas/PurchaseOrder"
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/purchase-orders/{id}/approve": {
      "post": {
        "operationId": "approvePurchaseOrder",
        "tags": [
          "Purchase orders"
        ],
        "summary": "Approve a purchase order",
        "description": "Draft to approved. Needs the admin role and an `approve` key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "$ref": "#/components/schemas/PurchaseOrder"
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "approve"
      }
    },
    "/api/purchase-orders/{id}/send": {
      "post": {
        "operationId": "sendPurchaseOrder",
        "tags": [
          "Purchase orders"
        ],
        "summary": "Record a purchase order as sent",
        "description": "Needs the planner role and an `approve` key. Foudra does not transmit the order to the supplier.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "$ref": "#/components/schemas/PurchaseOrder"
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "approve"
      }
    },
    "/api/demand": {
      "get": {
        "operationId": "listDemand",
        "tags": [
          "Demand"
        ],
        "summary": "Demand orders",
        "description": "Demand with channel, reference, quantity and shipped quantity.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "open",
                "closed",
                "cancelled"
              ]
            }
          },
          {
            "name": "channel",
            "in": "query",
            "description": "A channel code.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Demand"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      },
      "post": {
        "operationId": "receiveDemand",
        "tags": [
          "Demand"
        ],
        "summary": "Demand received",
        "description": "Idempotent on (channel, reference): a repeat while open updates quantity and date and answers 200; a first arrival answers 201. A closed or cancelled reference is left alone. The part is given by part_id, part_number or channel_part_number.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel": {
                    "type": "string"
                  },
                  "reference": {
                    "type": "string"
                  },
                  "part_id": {
                    "type": "integer",
                    "format": "int64"
                  },
                  "part_number": {
                    "type": "string"
                  },
                  "channel_part_number": {
                    "type": "string"
                  },
                  "quantity": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "due_at": {
                    "type": "string",
                    "format": "date"
                  },
                  "kind": {
                    "type": "string"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "channel",
                  "reference",
                  "quantity"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Already open: updated",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Demand"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Created",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Demand"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/demand/{id}/ship": {
      "post": {
        "operationId": "shipDemand",
        "tags": [
          "Demand"
        ],
        "summary": "Demand shipped",
        "description": "Issues that quantity (blank: the rest) from the location, or the part's biggest pile, as a ledger movement; closes the demand once everything has gone. `lots` names the lots or serial numbers shipped: required for a serial-tracked product. 409 when there is not enough on hand.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "quantity": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "location_id": {
                    "type": "integer",
                    "format": "int64"
                  },
                  "location_code": {
                    "type": "string"
                  },
                  "lots": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Demand"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/demand/{id}/cancel": {
      "post": {
        "operationId": "cancelDemand",
        "tags": [
          "Demand"
        ],
        "summary": "Demand cancelled",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/demand/{id}/progress": {
      "get": {
        "operationId": "getDemandProgress",
        "tags": [
          "Assistant"
        ],
        "summary": "Where is my order?",
        "description": "The demand, the part's stock and inbound supply, the planning run's proposals for this order, the work orders making it, and which of those are scheduled late. Supply belongs to the part, not the order.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "demand": {
                          "type": "object",
                          "properties": {
                            "id": {
                              "type": "integer",
                              "format": "int64"
                            },
                            "reference": {
                              "type": "string"
                            },
                            "channel": {
                              "type": [
                                "string",
                                "null"
                              ]
                            },
                            "kind": {
                              "type": "string"
                            },
                            "status": {
                              "type": "string"
                            },
                            "part": {
                              "$ref": "#/components/schemas/PartRef"
                            },
                            "quantity": {
                              "$ref": "#/components/schemas/Decimal"
                            },
                            "shipped_qty": {
                              "$ref": "#/components/schemas/Decimal"
                            },
                            "due_at": {
                              "type": "string",
                              "format": "date"
                            },
                            "url": {
                              "type": "string"
                            }
                          }
                        },
                        "part_supply": {
                          "type": "object",
                          "properties": {
                            "on_hand": {
                              "$ref": "#/components/schemas/Decimal"
                            },
                            "committed_to_all_demand": {
                              "$ref": "#/components/schemas/Decimal"
                            },
                            "inbound_on_purchase_orders": {
                              "$ref": "#/components/schemas/Decimal"
                            },
                            "note": {
                              "type": "string"
                            }
                          }
                        },
                        "planned": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PlannedOrder"
                          }
                        },
                        "work_orders": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "description": "A work order as in WorkOrder, without steps, with steps_done, step_count and url."
                          }
                        },
                        "late": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ScheduledJob"
                          }
                        }
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/channels": {
      "get": {
        "operationId": "listChannels",
        "tags": [
          "Channels"
        ],
        "summary": "The channels",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Channel"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/channels/{code}/listings": {
      "get": {
        "operationId": "listChannelListings",
        "tags": [
          "Channels"
        ],
        "summary": "A channel's listings",
        "description": "Every part listed on the channel with its channel part number; null means not pushed yet.",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Listing"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/channels/{code}/listings/{id}": {
      "put": {
        "operationId": "putChannelListing",
        "tags": [
          "Channels"
        ],
        "summary": "List a part on a channel",
        "description": "Lists a part on the channel, or records the channel's number for it. `{id}` is the part's id.",
        "parameters": [
          {
            "name": "code",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "channel_part_number": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "is_listed": {
                    "type": "boolean"
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "part_id": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "channel_part_number": {
                          "type": [
                            "string",
                            "null"
                          ]
                        },
                        "is_listed": {
                          "type": "boolean"
                        }
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/tasks/{source}": {
      "put": {
        "operationId": "replaceTasks",
        "tags": [
          "Tasks"
        ],
        "summary": "Replace a source's tasks",
        "description": "Replaces this source's task set **wholesale**; anything omitted is resolved. Source names are lowercase slugs. A task may offer up to six actions; pressing one resolves the task with that key as its outcome. A task with an outcome is never reopened: use a new key for a new occurrence.",
        "parameters": [
          {
            "name": "source",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "key": {
                      "type": "string"
                    },
                    "priority": {
                      "type": "integer",
                      "description": "1 urgent … 5 low."
                    },
                    "message": {
                      "type": "string"
                    },
                    "href": {
                      "type": "string"
                    },
                    "actions": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/TaskAction"
                      }
                    }
                  },
                  "required": [
                    "key",
                    "priority",
                    "message"
                  ]
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "source": {
                      "type": "string"
                    },
                    "open": {
                      "type": "integer"
                    },
                    "resolved": {
                      "type": "integer"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      },
      "get": {
        "operationId": "listSourceTasks",
        "tags": [
          "Tasks"
        ],
        "summary": "A source's tasks",
        "description": "This source's open tasks and its recently resolved ones with an `outcome`: how an integration learns which button was pressed.",
        "parameters": [
          {
            "name": "source",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/SourceTask"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/tasks": {
      "get": {
        "operationId": "listOpenTasks",
        "tags": [
          "Assistant"
        ],
        "summary": "Open tasks",
        "description": "Every open task, most urgent first, with its actions.",
        "parameters": [
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OpenTask"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/tasks/{id}/act": {
      "post": {
        "operationId": "actOnTask",
        "tags": [
          "Assistant"
        ],
        "summary": "Press a task's button",
        "description": "Needs the operator role.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "action": {
                    "type": "string",
                    "description": "One of the task's action keys."
                  }
                },
                "required": [
                  "action"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "outcome": {
                          "type": "string"
                        }
                      }
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/tasks/{id}/snooze": {
      "post": {
        "operationId": "snoozeTask",
        "tags": [
          "Assistant"
        ],
        "summary": "Snooze a task",
        "description": "Hides the task; a week when no number is given. Needs the operator role.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "days": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 90
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "format": "int64"
                        },
                        "snoozed_until": {
                          "type": "string",
                          "format": "date-time"
                        }
                      }
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/tasks/{id}/resolve": {
      "post": {
        "operationId": "resolveTask",
        "tags": [
          "Assistant"
        ],
        "summary": "Resolve a task",
        "description": "Closes the task by hand. Needs the operator role.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "id": {
                          "type": "integer",
                          "format": "int64"
                        }
                      }
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/brief": {
      "get": {
        "operationId": "getBrief",
        "tags": [
          "Assistant"
        ],
        "summary": "What needs attention",
        "description": "Everything that needs attention now. `headline` says the same in a few sentences, most urgent first.",
        "parameters": [
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "company": {
                          "type": "string"
                        },
                        "for": {
                          "type": "string",
                          "description": "The acting user's email."
                        },
                        "generated_at": {
                          "type": "string",
                          "format": "date-time"
                        },
                        "headline": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        },
                        "tasks": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/OpenTask"
                          }
                        },
                        "workstations": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "description": "id, code, name, group, status, status_note, status_changed_at, url."
                          }
                        },
                        "work_orders": {
                          "type": "object",
                          "properties": {
                            "draft": {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            },
                            "open": {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            }
                          }
                        },
                        "purchase_orders": {
                          "type": "object",
                          "properties": {
                            "draft": {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            },
                            "approved_not_sent": {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            },
                            "overdue": {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            }
                          }
                        },
                        "planning": {
                          "type": "object",
                          "properties": {
                            "last_run_at": {
                              "type": [
                                "string",
                                "null"
                              ],
                              "format": "date-time"
                            },
                            "due_to_firm": {
                              "type": "array",
                              "items": {
                                "type": "object"
                              }
                            },
                            "url": {
                              "type": "string"
                            }
                          }
                        },
                        "transfers_needed": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        },
                        "shortages": {
                          "type": "array",
                          "items": {
                            "type": "object"
                          }
                        },
                        "late_jobs": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/ScheduledJob"
                          }
                        },
                        "dashboard_url": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/planning": {
      "get": {
        "operationId": "getPlanning",
        "tags": [
          "Planning"
        ],
        "summary": "The planning page",
        "description": "Proposed and firmed planned orders (each says `because` of what), and each workstation group's schedule with its late jobs.",
        "parameters": [
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "object",
                      "properties": {
                        "last_run_at": {
                          "type": [
                            "string",
                            "null"
                          ],
                          "format": "date-time"
                        },
                        "proposed": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PlannedOrder"
                          }
                        },
                        "firmed": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PlannedOrder"
                          }
                        },
                        "capacity": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "code": {
                                "type": "string"
                              },
                              "name": {
                                "type": "string"
                              },
                              "stations": {
                                "type": "integer"
                              },
                              "hours_per_week": {
                                "type": "number"
                              },
                              "late_jobs": {
                                "type": "integer"
                              },
                              "jobs": {
                                "type": "array",
                                "items": {
                                  "$ref": "#/components/schemas/ScheduledJob"
                                }
                              }
                            }
                          }
                        },
                        "url": {
                          "type": "string"
                        }
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/planning/run": {
      "post": {
        "operationId": "runPlanning",
        "tags": [
          "Planning"
        ],
        "summary": "Run planning now",
        "description": "Needs the planner role. Answers with the proposals.",
        "parameters": [
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "make": {
                          "type": "integer"
                        },
                        "buy": {
                          "type": "integer"
                        },
                        "proposed": {
                          "type": "array",
                          "items": {
                            "$ref": "#/components/schemas/PlannedOrder"
                          }
                        }
                      }
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/planning/firm": {
      "post": {
        "operationId": "firmPlanning",
        "tags": [
          "Planning"
        ],
        "summary": "Firm proposals",
        "description": "Proposals into **draft** work orders and purchase orders. `location` (or `location_id`) is needed when a make is among them. Needs the planner role.",
        "parameters": [
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "ids": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "format": "int64"
                    }
                  },
                  "location": {
                    "type": "string",
                    "description": "A location code."
                  },
                  "location_id": {
                    "type": "integer",
                    "format": "int64"
                  }
                },
                "required": [
                  "ids"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "type": "object",
                      "properties": {
                        "work_orders": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "format": "int64"
                              },
                              "number": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string"
                              },
                              "quantity": {
                                "$ref": "#/components/schemas/Decimal"
                              },
                              "url": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "purchase_orders": {
                          "type": "array",
                          "items": {
                            "type": "object",
                            "properties": {
                              "id": {
                                "type": "integer",
                                "format": "int64"
                              },
                              "number": {
                                "type": "string"
                              },
                              "status": {
                                "type": "string"
                              },
                              "url": {
                                "type": "string"
                              }
                            }
                          }
                        },
                        "skipped": {
                          "type": "array",
                          "items": {
                            "type": "string"
                          }
                        }
                      }
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/work-orders": {
      "get": {
        "operationId": "listWorkOrders",
        "tags": [
          "Work orders"
        ],
        "summary": "Work orders",
        "description": "Orders, without their steps. Each open order carries the forward schedule's forecast.",
        "parameters": [
          {
            "name": "status",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "number",
            "in": "query",
            "description": "e.g. WO-00023.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "external_ref",
            "in": "query",
            "description": "Exact match.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/WorkOrder"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      },
      "post": {
        "operationId": "createWorkOrder",
        "tags": [
          "Work orders"
        ],
        "summary": "Raise a work order",
        "description": "A draft, or released when `release` is true. `location` is the code of where it draws parts (and puts output, unless `output_location` differs). **Send an Idempotency-Key**, or a retry raises a second order. Needs the planner role.",
        "parameters": [
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "part_number": {
                    "type": "string"
                  },
                  "part_id": {
                    "type": "integer",
                    "format": "int64"
                  },
                  "location": {
                    "type": "string"
                  },
                  "quantity": {
                    "type": "string",
                    "description": "Default 1."
                  },
                  "external_ref": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "due_at": {
                    "type": "string",
                    "format": "date"
                  },
                  "priority": {
                    "type": "integer",
                    "minimum": 1,
                    "maximum": 5
                  },
                  "notes": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "output_location": {
                    "type": "string"
                  },
                  "release": {
                    "type": "boolean"
                  }
                },
                "required": [
                  "location"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "$ref": "#/components/schemas/WorkOrder"
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/work-orders/{id}": {
      "get": {
        "operationId": "getWorkOrder",
        "tags": [
          "Work orders"
        ],
        "summary": "A work order",
        "description": "An order with its steps and each step's materials and readiness.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/WorkOrder"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/work-orders/{id}/issue": {
      "post": {
        "operationId": "issueToWorkOrder",
        "tags": [
          "Work orders"
        ],
        "summary": "Issue a part to a work order",
        "description": "Draws a part from stores for the order, outside its BOM. 409 when there is not enough. Needs the operator role.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "part_number": {
                    "type": "string"
                  },
                  "part_id": {
                    "type": "integer",
                    "format": "int64"
                  },
                  "quantity": {
                    "$ref": "#/components/schemas/Decimal"
                  },
                  "note": {
                    "type": "string"
                  },
                  "lots": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "$ref": "#/components/schemas/WorkOrder"
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/work-orders/{id}/release": {
      "post": {
        "operationId": "releaseWorkOrder",
        "tags": [
          "Work orders"
        ],
        "summary": "Release a work order",
        "description": "Draft to released. Needs the planner role.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "$ref": "#/components/schemas/WorkOrder"
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/work-orders/{id}/cancel": {
      "post": {
        "operationId": "cancelWorkOrder",
        "tags": [
          "Work orders"
        ],
        "summary": "Cancel a work order",
        "description": "Cancels an unfinished order. Needs the planner role.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "$ref": "#/components/parameters/dryRun"
          },
          {
            "$ref": "#/components/parameters/idempotencyKey"
          },
          {
            "$ref": "#/components/parameters/onBehalfOf"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "summary": {
                      "type": "string",
                      "description": "One sentence fit to repeat to a person."
                    },
                    "data": {
                      "$ref": "#/components/schemas/WorkOrder"
                    },
                    "dry_run": {
                      "type": "boolean",
                      "description": "True when ?dry_run=1: nothing was changed."
                    },
                    "acted_as": {
                      "$ref": "#/components/schemas/ActedAs"
                    }
                  },
                  "required": [
                    "summary",
                    "data",
                    "dry_run",
                    "acted_as"
                  ]
                }
              }
            },
            "headers": {
              "Idempotent-Replay": {
                "description": "`true` when this is the stored answer to an earlier request with the same Idempotency-Key.",
                "schema": {
                  "type": "string",
                  "enum": [
                    "true"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/work-order-steps/{id}/start": {
      "post": {
        "operationId": "startStep",
        "tags": [
          "Work orders"
        ],
        "summary": "Start a step",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "workstation_id": {
                    "type": "integer",
                    "format": "int64"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/work-order-steps/{id}/complete": {
      "post": {
        "operationId": "completeStep",
        "tags": [
          "Work orders"
        ],
        "summary": "Report units made",
        "description": "Records units made; backflushes materials at BOM standard, or at the reported actual for any part named in `consumed`; the last step receives output.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StepReport"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/work-order-steps/{id}/scrap": {
      "post": {
        "operationId": "scrapStep",
        "tags": [
          "Work orders"
        ],
        "summary": "Report a failed run",
        "description": "A run that made nothing: materials consumed, no output, the step's count unchanged.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/StepReport"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/work-order-steps/{id}/pause": {
      "post": {
        "operationId": "pauseStep",
        "tags": [
          "Work orders"
        ],
        "summary": "Pause a step",
        "description": "A running step with quantity left goes back to pending, progress kept, station released.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/workstations": {
      "get": {
        "operationId": "listWorkstations",
        "tags": [
          "Workstations"
        ],
        "summary": "Workstations",
        "description": "Stations with group and status.",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Workstation"
                      }
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/workstations/{id}": {
      "get": {
        "operationId": "getWorkstation",
        "tags": [
          "Workstations"
        ],
        "summary": "A workstation",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/Workstation"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/workstations/{id}/status": {
      "put": {
        "operationId": "setWorkstationStatus",
        "tags": [
          "Workstations"
        ],
        "summary": "Set a station's status",
        "description": "Mirrors a machine's state.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "idle",
                      "busy",
                      "blocked",
                      "offline"
                    ]
                  },
                  "note": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "status"
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/workstations/{id}/next": {
      "get": {
        "operationId": "getNextJob",
        "tags": [
          "Workstations"
        ],
        "summary": "The station's next job",
        "description": "The job this station should do now, with its materials: null unless the station is idle **and** a step is ready. `material` restricts to jobs consuming that part.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          },
          {
            "name": "material",
            "in": "query",
            "description": "A part id.",
            "schema": {
              "type": "integer",
              "format": "int64"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "data": {
                      "$ref": "#/components/schemas/NextJob"
                    }
                  },
                  "required": [
                    "data"
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      }
    },
    "/api/workstations/{id}/material": {
      "put": {
        "operationId": "setWorkstationMaterial",
        "tags": [
          "Workstations"
        ],
        "summary": "Set a station's material",
        "description": "What the station is set up with: a printer's loaded filament. `null` for none.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "part_id": {
                    "type": [
                      "integer",
                      "null"
                    ],
                    "format": "int64"
                  }
                },
                "required": [
                  "part_id"
                ]
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "422": {
            "$ref": "#/components/responses/Unprocessable"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/parts/{id}/custom": {
      "get": {
        "operationId": "getPartCustom",
        "tags": [
          "Custom data"
        ],
        "summary": "A part's custom data",
        "description": "The opaque JSON kept for integrations, exactly as stored, or `null`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "The JSON itself (not wrapped in `data`).",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      },
      "put": {
        "operationId": "putPartCustom",
        "tags": [
          "Custom data"
        ],
        "summary": "Replace a part's custom data",
        "description": "Replaces the opaque blob (64 KB max). The body is the JSON itself.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {}
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/workstations/{id}/custom": {
      "get": {
        "operationId": "getWorkstationCustom",
        "tags": [
          "Custom data"
        ],
        "summary": "A workstation's custom data",
        "description": "The opaque JSON kept for integrations, exactly as stored, or `null`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "The JSON itself (not wrapped in `data`).",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      },
      "put": {
        "operationId": "putWorkstationCustom",
        "tags": [
          "Custom data"
        ],
        "summary": "Replace a workstation's custom data",
        "description": "Replaces the opaque blob (64 KB max). The body is the JSON itself.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {}
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "write"
      }
    },
    "/api/work-orders/{id}/custom": {
      "get": {
        "operationId": "getWorkOrderCustom",
        "tags": [
          "Custom data"
        ],
        "summary": "A work order's custom data",
        "description": "The opaque JSON kept for integrations, exactly as stored, or `null`.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "responses": {
          "200": {
            "description": "The JSON itself (not wrapped in `data`).",
            "content": {
              "application/json": {
                "schema": {}
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "read"
      },
      "put": {
        "operationId": "putWorkOrderCustom",
        "tags": [
          "Custom data"
        ],
        "summary": "Replace a work order's custom data",
        "description": "Replaces the opaque blob (64 KB max). The body is the JSON itself.",
        "parameters": [
          {
            "$ref": "#/components/parameters/id"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {}
            }
          }
        },
        "responses": {
          "204": {
            "description": "Done; no body."
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "402": {
            "$ref": "#/components/responses/PaymentRequired"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        },
        "x-required-scope": "write"
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "description": "An API key (`fdr_…`) made under API keys, or an OAuth access token (`fdo_…`)."
      },
      "oauth2": {
        "type": "oauth2",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://foudra.com/oauth/authorize",
            "tokenUrl": "https://foudra.com/oauth/token",
            "refreshUrl": "https://foudra.com/oauth/token",
            "scopes": {
              "read": "Look, never change",
              "write": "Everything operational: stock, work orders, tasks, drafts",
              "approve": "Also approve and send purchase orders"
            }
          }
        },
        "description": "Authorization code with PKCE (S256 required). Refresh tokens rotate."
      }
    },
    "parameters": {
      "id": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "integer",
          "format": "int64"
        }
      },
      "page": {
        "name": "page",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "per": {
        "name": "per",
        "in": "query",
        "description": "Rows per page.",
        "schema": {
          "type": "integer",
          "enum": [
            10,
            25,
            50,
            100
          ],
          "default": 25
        }
      },
      "dryRun": {
        "name": "dry_run",
        "in": "query",
        "description": "1 does everything for real and then undoes it: the answer is exactly what would have happened, with `dry_run: true`.",
        "schema": {
          "type": "string",
          "enum": [
            "1",
            "true",
            "yes"
          ]
        }
      },
      "idempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "description": "Any string up to 200 characters, unique per intended action. A repeat within a week gets the first answer back, with `Idempotent-Replay: true`; the same key on a different request is refused (422).",
        "schema": {
          "type": "string",
          "maxLength": 200
        }
      },
      "onBehalfOf": {
        "name": "X-On-Behalf-Of",
        "in": "header",
        "description": "The email of a user of this company: the request is made in that user's name and with that user's role. Without it, the key's creator (or the user who allowed the application) acts.",
        "schema": {
          "type": "string",
          "format": "email"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "The body or a parameter isn't what was expected; the message says what was.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "No credential, or an invalid, expired or revoked one.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "PaymentRequired": {
        "description": "The company's subscription has expired.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The credential's scope, or the acting user's role, doesn't allow this.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "No such thing in this company.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Conflict": {
        "description": "The thing isn't in a state that allows this; the message says why.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Unprocessable": {
        "description": "Understood but refused; the message says why.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "description": "A message meant to be read by a person."
          }
        },
        "required": [
          "error"
        ]
      },
      "Decimal": {
        "type": "string",
        "description": "A decimal number as text: JSON numbers are floats, and quantities and money are not.",
        "pattern": "^-?[0-9]+(\\.[0-9]+)?$",
        "examples": [
          "12.5"
        ]
      },
      "PartRef": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "part_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "unit_of_measure": {
            "type": "string"
          }
        }
      },
      "Ref": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "Page": {
        "type": "object",
        "properties": {
          "page": {
            "type": "integer",
            "description": "This page, from 1."
          },
          "pages": {
            "type": "integer",
            "description": "How many pages there are."
          },
          "total": {
            "type": "integer",
            "description": "How many rows there are in all."
          }
        },
        "required": [
          "page",
          "pages",
          "total"
        ]
      },
      "ActedAs": {
        "type": "object",
        "properties": {
          "user": {
            "type": "string",
            "description": "The email of the user the change was made as."
          },
          "name": {
            "type": "string"
          },
          "api_key": {
            "type": "string",
            "description": "The key's name, when a key made the request."
          },
          "application": {
            "type": "string",
            "description": "The application's name, when an OAuth token made the request."
          }
        },
        "required": [
          "user",
          "name"
        ]
      },
      "Part": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "part_number": {
            "type": "string",
            "description": "Canonical dashed form, e.g. 100-000001-00."
          },
          "category_code": {
            "type": "string"
          },
          "stock_number": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "string",
              "null"
            ]
          },
          "part_type": {
            "type": "string",
            "description": "The part's type, e.g. finished_good, sub_assembly, raw_material."
          },
          "unit_of_measure": {
            "type": "string"
          },
          "is_stocked": {
            "type": "boolean"
          },
          "is_active": {
            "type": "boolean"
          },
          "tracking": {
            "type": "string",
            "enum": [
              "none",
              "lot",
              "serial"
            ]
          },
          "minimum_stock": {
            "$ref": "#/components/schemas/Decimal"
          },
          "maximum_stock": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "null"
              }
            ]
          },
          "lead_time_days": {
            "type": [
              "integer",
              "null"
            ]
          },
          "standard_cost": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "null"
              }
            ]
          },
          "on_hand": {
            "$ref": "#/components/schemas/Decimal"
          },
          "committed": {
            "$ref": "#/components/schemas/Decimal"
          },
          "available": {
            "$ref": "#/components/schemas/Decimal"
          },
          "barcode_url": {
            "type": "string",
            "description": "Path of the part's barcode PNG on the same host."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "`on_hand` is all stock of the part; `committed` is open demand not yet shipped; `available` is on hand less committed."
      },
      "Image": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "filename": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "sha256": {
            "type": "string"
          },
          "is_primary": {
            "type": "boolean"
          },
          "width": {
            "type": [
              "integer",
              "null"
            ]
          },
          "height": {
            "type": [
              "integer",
              "null"
            ]
          },
          "from_category": {
            "type": "boolean",
            "description": "True when the part has no image of its own and this is its category's."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "url": {
            "type": "string",
            "description": "Path of the file: GET /api/images/{id}."
          },
          "thumb_url": {
            "type": "string",
            "description": "Path of the thumbnail."
          }
        }
      },
      "Document": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "part_id": {
            "type": "integer",
            "format": "int64"
          },
          "type": {
            "type": "string",
            "enum": [
              "datasheet",
              "technical_data",
              "safety_data",
              "manual",
              "assembly",
              "drawing",
              "certificate",
              "model_3d",
              "gcode",
              "other"
            ]
          },
          "title": {
            "type": "string"
          },
          "revision": {
            "type": [
              "string",
              "null"
            ]
          },
          "filename": {
            "type": "string"
          },
          "content_type": {
            "type": "string"
          },
          "bytes": {
            "type": "integer"
          },
          "sha256": {
            "type": "string"
          },
          "printer_model": {
            "type": [
              "string",
              "null"
            ],
            "description": "For G-code: the printer it was sliced for, read from the file at upload (e.g. COREONE, MK4S). Null when the file doesn't say, and for every other document."
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "url": {
            "type": "string",
            "description": "Path of the file: GET /api/documents/{id}."
          }
        }
      },
      "BOMLine": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "line_no": {
            "type": "integer"
          },
          "child_part_id": {
            "type": "integer",
            "format": "int64"
          },
          "child_part_number": {
            "type": "string"
          },
          "child_name": {
            "type": "string"
          },
          "quantity": {
            "$ref": "#/components/schemas/Decimal"
          },
          "unit_of_measure": {
            "type": "string"
          },
          "reference": {
            "type": [
              "string",
              "null"
            ]
          },
          "routing_step_id": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "step_seq": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "RoutingStep": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "seq": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "group_id": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "group_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "setup_minutes": {
            "type": "integer"
          },
          "run_minutes_per_unit": {
            "$ref": "#/components/schemas/Decimal"
          },
          "batch_size": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "null"
              }
            ]
          },
          "instructions": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Supplier": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "currency": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          }
        }
      },
      "PartSource": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "supplier_id": {
            "type": "integer",
            "format": "int64"
          },
          "supplier_code": {
            "type": "string"
          },
          "supplier_name": {
            "type": "string"
          },
          "supplier_part_number": {
            "type": "string"
          },
          "product_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "unit_price": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "null"
              }
            ]
          },
          "currency": {
            "type": [
              "string",
              "null"
            ]
          },
          "minimum_order_qty": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "null"
              }
            ]
          },
          "pack_size": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "null"
              }
            ]
          },
          "lead_time_days": {
            "type": [
              "integer",
              "null"
            ]
          },
          "is_preferred": {
            "type": "boolean"
          }
        }
      },
      "Sourcing": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "part": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "format": "int64"
              },
              "part_number": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": "string"
              }
            }
          },
          "supplier": {
            "$ref": "#/components/schemas/Ref"
          },
          "supplier_part_number": {
            "type": "string"
          },
          "product_url": {
            "type": [
              "string",
              "null"
            ]
          },
          "unit_price": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "null"
              }
            ]
          },
          "currency": {
            "type": "string",
            "description": "The source's currency, or the supplier's default."
          },
          "minimum_order_qty": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "null"
              }
            ]
          },
          "pack_size": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "null"
              }
            ]
          },
          "lead_time_days": {
            "type": [
              "integer",
              "null"
            ]
          },
          "is_preferred": {
            "type": "boolean"
          }
        }
      },
      "StockRow": {
        "type": "object",
        "properties": {
          "part_id": {
            "type": "integer",
            "format": "int64"
          },
          "part_number": {
            "type": "string"
          },
          "part_name": {
            "type": "string"
          },
          "location_code": {
            "type": "string"
          },
          "lot_id": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "lot_code": {
            "type": [
              "string",
              "null"
            ]
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "quantity": {
            "$ref": "#/components/schemas/Decimal"
          },
          "unit_of_measure": {
            "type": "string"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        },
        "description": "One row per part, location and lot; untracked stock has no lot."
      },
      "Lot": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "code": {
            "type": "string"
          },
          "serial": {
            "type": "boolean",
            "description": "True for a serial number, false for a lot."
          },
          "part_id": {
            "type": "integer",
            "format": "int64"
          },
          "part_number": {
            "type": "string"
          },
          "on_hand": {
            "$ref": "#/components/schemas/Decimal"
          },
          "expires_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "supplier_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "barcode_url": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PurchaseOrder": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "number": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "description": "draft, approved, ordered (sent), partially received, received, cancelled."
          },
          "supplier": {
            "$ref": "#/components/schemas/Ref"
          },
          "currency": {
            "type": "string"
          },
          "exchange_rate": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "null"
              }
            ]
          },
          "expected_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          },
          "external_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "approved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ordered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "lines": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer",
                  "format": "int64"
                },
                "line_no": {
                  "type": "integer"
                },
                "part": {
                  "$ref": "#/components/schemas/PartRef"
                },
                "supplier_part_number": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "ordered_qty": {
                  "$ref": "#/components/schemas/Decimal"
                },
                "received_qty": {
                  "$ref": "#/components/schemas/Decimal"
                },
                "unit_price": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Decimal"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              }
            }
          }
        }
      },
      "PurchaseOrderSummary": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "number": {
            "type": "string"
          },
          "status": {
            "type": "string"
          },
          "supplier": {
            "$ref": "#/components/schemas/Ref"
          },
          "currency": {
            "type": "string"
          },
          "subtotal": {
            "$ref": "#/components/schemas/Decimal"
          },
          "expected_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "approved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "ordered_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "url": {
            "type": "string",
            "description": "The order's page, for a person."
          }
        }
      },
      "Demand": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "channel": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "part_id": {
            "type": "integer",
            "format": "int64"
          },
          "quantity": {
            "$ref": "#/components/schemas/Decimal"
          },
          "shipped_qty": {
            "$ref": "#/components/schemas/Decimal"
          },
          "due_at": {
            "type": "string",
            "format": "date"
          },
          "kind": {
            "type": "string"
          },
          "priority": {
            "type": "integer"
          },
          "status": {
            "type": "string",
            "description": "open, closed or cancelled."
          },
          "notes": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Channel": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "is_active": {
            "type": "boolean"
          },
          "listings": {
            "type": "integer",
            "description": "How many parts are listed on it."
          }
        }
      },
      "Listing": {
        "type": "object",
        "properties": {
          "part_id": {
            "type": "integer",
            "format": "int64"
          },
          "part_number": {
            "type": "string"
          },
          "part_name": {
            "type": "string"
          },
          "channel_part_number": {
            "type": [
              "string",
              "null"
            ],
            "description": "Null until the part has been pushed to the channel."
          },
          "is_listed": {
            "type": "boolean"
          },
          "last_synced_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "TaskAction": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "tone": {
            "type": "string",
            "enum": [
              "primary",
              "danger",
              ""
            ]
          }
        },
        "required": [
          "key",
          "label"
        ]
      },
      "OpenTask": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "source": {
            "type": "string"
          },
          "key": {
            "type": "string"
          },
          "priority": {
            "type": "integer"
          },
          "priority_label": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "url": {
            "type": "string",
            "description": "Where a person deals with it."
          },
          "actions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TaskAction"
            }
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "SourceTask": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "key": {
            "type": "string"
          },
          "priority": {
            "type": "integer"
          },
          "message": {
            "type": "string"
          },
          "href": {
            "type": "string"
          },
          "actions": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/TaskAction"
            }
          },
          "outcome": {
            "type": [
              "string",
              "null"
            ],
            "description": "The key of the action a person pressed."
          },
          "resolved_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "snoozed_until": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "MaterialRef": {
        "type": [
          "object",
          "null"
        ],
        "description": "What the station is set up with, or null.",
        "properties": {
          "part_id": {
            "type": "integer",
            "format": "int64"
          },
          "part_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Workstation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "code": {
            "type": "string"
          },
          "name": {
            "type": "string"
          },
          "kind": {
            "type": "string",
            "enum": [
              "manual",
              "automatic"
            ]
          },
          "group": {
            "$ref": "#/components/schemas/Ref"
          },
          "status": {
            "type": "string",
            "enum": [
              "idle",
              "busy",
              "blocked",
              "offline",
              "error"
            ]
          },
          "status_note": {
            "type": [
              "string",
              "null"
            ]
          },
          "status_changed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "is_active": {
            "type": "boolean"
          },
          "current_material": {
            "$ref": "#/components/schemas/MaterialRef"
          }
        }
      },
      "Material": {
        "type": "object",
        "properties": {
          "part_id": {
            "type": "integer",
            "format": "int64"
          },
          "part_number": {
            "type": [
              "string",
              "null"
            ]
          },
          "name": {
            "type": "string"
          },
          "unit_of_measure": {
            "type": "string"
          },
          "qty_per": {
            "$ref": "#/components/schemas/Decimal"
          },
          "required": {
            "$ref": "#/components/schemas/Decimal"
          },
          "on_hand": {
            "$ref": "#/components/schemas/Decimal"
          },
          "short": {
            "type": "boolean"
          }
        }
      },
      "WorkOrder": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "number": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "draft",
              "released",
              "in_progress",
              "completed",
              "cancelled"
            ]
          },
          "priority": {
            "type": "integer"
          },
          "part": {
            "$ref": "#/components/schemas/PartRef"
          },
          "quantity": {
            "$ref": "#/components/schemas/Decimal"
          },
          "quantity_done": {
            "$ref": "#/components/schemas/Decimal"
          },
          "due_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "component_location_id": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "output_location_id": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "released_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "completed_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "external_ref": {
            "type": [
              "string",
              "null"
            ]
          },
          "steps": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": "integer",
                  "format": "int64"
                },
                "seq": {
                  "type": "integer"
                },
                "name": {
                  "type": "string"
                },
                "status": {
                  "type": "string"
                },
                "group_id": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "format": "int64"
                },
                "workstation_id": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "format": "int64"
                },
                "quantity_done": {
                  "$ref": "#/components/schemas/Decimal"
                },
                "ready": {
                  "type": "boolean"
                },
                "started_at": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "finished_at": {
                  "type": [
                    "string",
                    "null"
                  ],
                  "format": "date-time"
                },
                "materials": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/Material"
                  }
                }
              }
            },
            "description": "Empty in a list; a single order has its steps."
          },
          "expected_start": {
            "type": [
              "string",
              "null"
            ],
            "description": "The forward schedule's forecast; null for a finished or cancelled order.",
            "format": "date"
          },
          "expected_finish": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "late": {
            "type": "boolean",
            "description": "Finishing after due_at."
          },
          "delay_reasons": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "PlannedOrder": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64"
          },
          "kind": {
            "type": "string",
            "enum": [
              "make",
              "buy"
            ]
          },
          "status": {
            "type": "string"
          },
          "priority": {
            "type": "integer"
          },
          "part": {
            "$ref": "#/components/schemas/PartRef"
          },
          "quantity": {
            "$ref": "#/components/schemas/Decimal"
          },
          "needed_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "release_by": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "because": {
            "type": "array",
            "description": "What drove the proposal."
          },
          "demand_order_id": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "parent_id": {
            "type": [
              "integer",
              "null"
            ],
            "format": "int64"
          },
          "supplier": {
            "$ref": "#/components/schemas/Ref"
          },
          "work_order": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "format": "int64"
              },
              "number": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            }
          },
          "purchase_order": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "format": "int64"
              },
              "number": {
                "type": "string"
              },
              "url": {
                "type": "string"
              }
            }
          }
        }
      },
      "ScheduledJob": {
        "type": "object",
        "properties": {
          "group": {
            "type": "string"
          },
          "label": {
            "type": "string"
          },
          "kind": {
            "type": "string"
          },
          "priority": {
            "type": "integer"
          },
          "hours": {
            "type": "string"
          },
          "due": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          },
          "start": {
            "type": "string",
            "format": "date"
          },
          "finish": {
            "type": "string",
            "format": "date"
          },
          "late": {
            "type": "boolean"
          },
          "url": {
            "type": "string"
          }
        }
      },
      "NextJob": {
        "type": [
          "object",
          "null"
        ],
        "description": "The job the station should do now, or null.",
        "properties": {
          "step_id": {
            "type": "integer",
            "format": "int64"
          },
          "seq": {
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "work_order": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "format": "int64"
              },
              "number": {
                "type": "string"
              },
              "priority": {
                "type": "integer"
              },
              "due_at": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date-time"
              },
              "quantity": {
                "$ref": "#/components/schemas/Decimal"
              }
            }
          },
          "part": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer",
                "format": "int64"
              },
              "part_number": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "name": {
                "type": "string"
              }
            }
          },
          "materials": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Material"
            }
          },
          "current_material": {
            "$ref": "#/components/schemas/MaterialRef"
          },
          "requires_material_change": {
            "type": "boolean"
          }
        }
      },
      "StepReport": {
        "type": "object",
        "properties": {
          "quantity": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/Decimal"
              },
              {
                "type": "number"
              }
            ],
            "description": "Units made (complete) or lost (scrap)."
          },
          "consumed": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "part_id": {
                  "type": "integer",
                  "format": "int64"
                },
                "quantity": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/Decimal"
                    },
                    {
                      "type": "number"
                    }
                  ]
                }
              }
            },
            "description": "Actual use of these parts, instead of the BOM's standard."
          },
          "used": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "part_id": {
                  "type": "integer",
                  "format": "int64"
                },
                "lots": {
                  "type": "array",
                  "items": {
                    "type": "string"
                  }
                }
              }
            },
            "description": "Lots or serial numbers consumed, for tracked parts."
          },
          "made": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "description": "The output's serial numbers (one per unit), or its lot."
          },
          "expires": {
            "type": "string",
            "description": "A new output lot's expiry.",
            "format": "date"
          },
          "note": {
            "type": "string",
            "description": "Scrap only: what went wrong."
          }
        },
        "required": [
          "quantity"
        ]
      }
    }
  }
}
