{
  "openapi": "3.0.3",
  "info": {
    "title": "Factory Sales API",
    "description": "Create and read sales orders and quotes, customers, and the supporting product catalogue, for your company.\n\nEvery request is authenticated with a bearer token and is scoped to the authenticated company.\n\nNumbers and money: monetary amounts are Money objects — an integer number of micros (millionths of the currency's major unit) in amountMicros (1.00 = 1,000,000 micros, so $12.34 is \"12340000\") plus an ISO 4217 currency code. Quantities, discounts, percentages, and markups are decimal strings (a quantity of \"2.5\", a discount of \"10.00\" meaning 10%). All 64-bit integer fields — including amountMicros and purchaseOrder — are sent and returned as JSON strings, not JSON numbers, to avoid precision loss in JavaScript clients.\n\nEnums: enum fields take the upper-case value names listed on each field (for example \"FULFILMENT_METHOD_DELIVERY\"). The string name is the canonical wire form: responses always return the name, and requests should send it (integer values are also accepted, but names are recommended).\n\nErrors: every error response, whatever the HTTP status, has the same body — an error envelope with a short stable \"type\" naming the kind of failure, a human-readable \"message\", the request's idempotency key echoed back as \"requestId\" when the failing request carried one, and an \"errors\" array. The \"type\" values are \"validation_failure\" (HTTP 400 — a malformed or invalid request), \"failed_precondition\" (HTTP 400 — the request was valid but the resource's state does not allow it, for example writing a line on an invoiced order), \"unauthenticated\" (HTTP 401), \"permission_denied\" (HTTP 403), \"not_found\" (HTTP 404), \"conflict\" (HTTP 409), \"rate_limited\" (HTTP 429 — you are being throttled, and the Retry-After header, when present, says when to come back), \"resource_exhausted\" (HTTP 429 — a limit other than a request rate was exceeded, in practice a request or a response too large to carry; the message names the limit, and retrying unchanged will fail the same way — send less, or ask for a smaller page), \"internal\" (HTTP 500), \"unavailable\" (HTTP 503), and \"timeout\" (HTTP 504); treat an unrecognised type as a generic error. On a validation failure the errors array carries one entry per field-level problem — each with a stable error code, a human-readable message, and a pointer to the offending field (for example \"lines[2].catalogue.product_id\"); for every other type the array is empty. Stable error codes defined so far include \"customer_not_found\" (the customer reference on a create could not be resolved) and \"product_not_found\" (a catalogue line references a product id that does not exist). The list is not exhaustive — more codes will be added as validations grow, so treat an unrecognised code as a generic validation failure rather than an error.\n\nA request without a valid bearer token returns HTTP 401 (gRPC UNAUTHENTICATED). Retrieving a single resource by an id that does not exist in your company returns HTTP 404 (gRPC NOT_FOUND).\n\nRate limits: requests are rate limited. A request over the limit returns HTTP 429 (gRPC RESOURCE_EXHAUSTED) with a Retry-After header saying how long to wait before retrying; concrete limits will be announced.\n\nField filtering: reduce response payload size by requesting only the fields you need. Pass a `fields` query parameter with a comma-separated list of camelCase JSON names — e.g. `?fields=orderId,total.amountMicros`. Dot paths reach into nested objects and map transparently across arrays. Paths are relative to the resource, not the response envelope; envelope keys like `nextPageToken` are always preserved. To keep everything except a few heavy fields, use `excludeFields` instead. If both are provided, `fields` takes precedence. Filtering applies to successful (2xx) JSON responses only — error bodies are never filtered. Unknown field names are silently ignored.\n\nVersioning: the major version is carried in the path (\"/v1/\"). Within v1, changes are additive — new fields and endpoints may appear, so clients must tolerate unknown fields in responses. Breaking changes ship only under a new version path, and deprecations are announced in advance.\n\nWebhooks: the API does not send webhooks in v1. To detect new or changed orders and quotes, poll the change feeds (QueryOrders, QueryQuotes) with an updatedSince checkpoint.",
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://api.factory.app"
    }
  ],
  "tags": [
    {
      "name": "CatalogueService",
      "description": "Your product catalogue — the products, kits, and flashings you sell."
    },
    {
      "name": "CompanyService"
    },
    {
      "name": "CustomerService",
      "description": "Your company's customers — the businesses you quote and sell to."
    },
    {
      "name": "InventoryService",
      "description": "Read and update stock levels for your catalogue products. Stock is tracked per product variant row, optionally split by colour. Each entry carries on-hand, promised, on-the-way, and available quantities for that one variant — never a cumulative product-level total — plus the variant's identifying attributes (thickness and name/value pairs), so entries are self-describing. An entry's productRowId is the same id the catalogue product's rows carry."
    },
    {
      "name": "QuoteService",
      "description": "Create quotes, read them, add, update, and remove their line items, and upload flashing-drawing SVGs — the only surface for creating and reading quotes; confirmed orders live on OrderService. A quote's line items can be added, updated, and removed individually after creation; the server recomputes totals on every change. A quote created as a draft is finalised and sent from the Factory app. The quote itself is not voided or deleted through this API, and turning an accepted quote into a confirmed order happens in the Factory app."
    },
    {
      "name": "LabelService",
      "description": "The labels your company uses to tag orders."
    },
    {
      "name": "OrderService",
      "description": "Create sales orders, read and list them, and upload flashing-drawing SVGs — the order surface of the API. An order is created either as a draft or submitted immediately; after creation this API reads orders but does not update them — workflow status, fulfilment, and payment changes happen in the Factory app and are reflected on subsequent reads. Quotes live on QuoteService."
    },
    {
      "name": "OrderStatusService",
      "description": "The workflow statuses your company's orders move through."
    },
    {
      "name": "SearchService",
      "description": "Free-text search across your catalogue."
    },
    {
      "name": "SupplierService",
      "description": "List your company's suppliers. Suppliers are managed in the Factory purchasing UI — this read-only surface lets API consumers discover supplier ids for use as inventory list filters."
    },
    {
      "name": "UserService",
      "description": "The people in your company, for assigning work to."
    }
  ],
  "paths": {
    "/v1/catalogue/flashings": {
      "get": {
        "summary": "List the flashing product templates for your company.",
        "description": "Returns a paginated list of flashing templates. A flashing line item must\nreference one of these templates by id, so use this list to find the\ntemplate for the thickness you need and its selectable colours. Templates are\nreturned ordered by flashing name, then thickness.",
        "operationId": "CatalogueService_ListFlashings",
        "tags": [
          "CatalogueService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of templates to return per page. 0 uses the server default;\nthe server may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "productType",
            "in": "query",
            "description": "Optional: only return templates of this flashing type.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 150
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListFlashingsResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalogue/flashings/{templateId}": {
      "get": {
        "summary": "Retrieve a single flashing product template by its id.",
        "description": "Returns the full flashing template, including its thickness and selectable\ncolours.",
        "operationId": "CatalogueService_GetFlashing",
        "tags": [
          "CatalogueService"
        ],
        "parameters": [
          {
            "name": "templateId",
            "in": "path",
            "description": "The id of the flashing template to retrieve. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^flashtpl_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetFlashingResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No flashing template with the given id exists for the authenticated company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalogue/kits": {
      "get": {
        "summary": "List the configured product kits for your company.",
        "description": "Returns a paginated list of product kit summaries. This is a lightweight\nlisting: rows, components, and sub-assemblies are not populated — use\nGetKit to fetch a single kit's priced rows and full component tree. Kits\nare returned alphabetically by name.",
        "operationId": "CatalogueService_ListKits",
        "tags": [
          "CatalogueService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of kits to return per page. 0 uses the server default; the\nserver may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "query",
            "description": "Optional: only return kits whose name matches this text.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 150
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListKitsResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalogue/kits/{kitId}": {
      "get": {
        "summary": "Retrieve a single product kit by its id, with its full component tree.",
        "description": "Returns the kit with its priced rows, top-level component products, and\nsub-assemblies — the single call that lets you drill into one kit's full\ndetail.",
        "operationId": "CatalogueService_GetKit",
        "tags": [
          "CatalogueService"
        ],
        "parameters": [
          {
            "name": "kitId",
            "in": "path",
            "description": "The id of the kit to retrieve. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^kit_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetKitResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No product kit with the given id exists for the authenticated company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalogue/products": {
      "get": {
        "summary": "List the orderable catalogue products for your company.",
        "description": "Returns a paginated list of catalogue product summaries. The listing is\nlightweight: `rows` is not populated — retrieve a product with GetProduct\nto get its priced variant rows. Product kits are not included here; list\nthem with ListKits. Products are returned alphabetically by name.",
        "operationId": "CatalogueService_ListProducts",
        "tags": [
          "CatalogueService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of products to return per page. 0 uses the server default;\nthe server may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "query",
            "description": "Optional: only return products whose name contains this text. A browse aid,\nnot a precise lookup key.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "categoryName",
            "in": "query",
            "description": "Optional: only return products in this category, by category name.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 150
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListProductsResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalogue/products/{productId}": {
      "get": {
        "summary": "Retrieve a single catalogue product by its id.",
        "description": "Returns the full product, including all of its priced variant rows.",
        "operationId": "CatalogueService_GetProduct",
        "tags": [
          "CatalogueService"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "description": "The id of the product to retrieve. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^prod_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetProductResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No catalogue product with the given id exists for the authenticated company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/catalogue:search": {
      "get": {
        "summary": "Search your catalogue.",
        "description": "Searches products, kits, and flashings by text query and returns one\nranked list of minimal hits, most relevant first. Every whitespace-\nseparated word in the query must match — against an entry's name, its\ncategory, a material, or a product attribute value (for example an Item\nCode). Fetch full detail with GetProduct, GetKit, or GetFlashing using\nthe hit's id. Result order is relevance-based and may change between\nrequests; it is not a stable contract.",
        "operationId": "SearchService_SearchCatalogue",
        "tags": [
          "SearchService"
        ],
        "parameters": [
          {
            "name": "query",
            "in": "query",
            "description": "The text to search for. Every whitespace-separated word must match.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 200,
              "minLength": 1
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of hits to return per page. 0 uses the server default;\nthe server may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.\nA token is bound to the query it was minted for.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SearchCatalogueResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an empty query or an unusable page token. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "Authentication failed: the bearer token is missing, invalid, or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/company": {
      "get": {
        "summary": "Get your company's settings.",
        "description": "A singleton read keyed by the bearer token. Returns the company's\nidentity, locale and currency settings, the measurement system its\nquantities and dimensions are expressed in, its tax context, contact and\naddress details, and logo.",
        "operationId": "CompanyService_GetCompany",
        "tags": [
          "CompanyService"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCompanyResponse"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/company/custom-fields": {
      "get": {
        "summary": "List your company's custom field definitions.",
        "description": "Custom fields are account-configured, typed fields on sales documents,\ncustomers, and suppliers. The `key` returned here is the identifier write\nendpoints accept — for example CreateQuoteRequest.custom_fields is an\nobject keyed by these keys. For select and multi-select fields, the value\na write must carry is an option `key` from the definition's options (not\nthe option's label). Hidden definitions are included — existing documents\ncan still carry values for fields that were hidden after being set.\nOptionally filter by module.",
        "operationId": "CompanyService_ListCompanyCustomFields",
        "tags": [
          "CompanyService"
        ],
        "parameters": [
          {
            "name": "module",
            "in": "query",
            "description": "Only return definitions for this module. Optional; unspecified returns\nevery module's definitions.\n\n - CUSTOM_FIELD_MODULE_SALES_DOCUMENTS: Quotes and orders share one set of sales-document fields.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "CUSTOM_FIELD_MODULE_UNSPECIFIED",
                "CUSTOM_FIELD_MODULE_SALES_DOCUMENTS",
                "CUSTOM_FIELD_MODULE_CUSTOMERS",
                "CUSTOM_FIELD_MODULE_SUPPLIERS",
                "CUSTOM_FIELD_MODULE_PURCHASE_ORDERS"
              ],
              "default": "CUSTOM_FIELD_MODULE_UNSPECIFIED"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListCompanyCustomFieldsResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unknown module filter value. The body is a validation-failure envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/company/labels": {
      "get": {
        "summary": "List your company's labels, sorted by name.",
        "description": "Returns the labels configured for the authenticated company. Use the\nreturned label ids when replacing an order's labels with SetOrderLabels.",
        "operationId": "LabelService_ListLabels",
        "tags": [
          "LabelService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "The maximum number of labels to return in one page. Optional; a server\ndefault applies when unset.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListLabelsResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/company/order-statuses": {
      "get": {
        "summary": "List your company's order statuses, in column order.",
        "description": "Returns the order statuses (workflow columns) configured for the\nauthenticated company, in display order. Use the returned status ids when\nsetting an order's status on CreateOrder.",
        "operationId": "OrderStatusService_ListOrderStatuses",
        "tags": [
          "OrderStatusService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of statuses to return per page. 0 uses the server default.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListOrderStatusesResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/customers": {
      "get": {
        "summary": "List or search the customers in your company.",
        "description": "A company-scoped list of customers, optionally filtered by company name\n(matched case-insensitively). Use it to resolve a customer by name before\nreferencing it on an order. Customers are returned alphabetically by company\nname. List entries are summaries — id, company name, billing address, and\nlast order time; fetch the full record (email, contacts, tax identifier,\nprice level) with GetCustomer.",
        "operationId": "CustomerService_ListCustomers",
        "tags": [
          "CustomerService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of customers to return per page. 0 uses the server default;\nthe server may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "companyName",
            "in": "query",
            "description": "Optional: only return customers whose company name matches, compared\ncase-insensitively. Use this to resolve a customer by name.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 150
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListCustomersResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a customer for the authenticated company.",
        "description": "Adds a new customer — company details, addresses, and contacts — to your\ncompany. Company name and email are required; a tax identifier is optional\n(in v1 that means an ABN). The company name must be\nunique within your company (matched case-insensitively); a duplicate is\nrejected with HTTP 400 (InvalidArgument), carrying a\nfactory.api.v1.Error detail. `requestId` reserves idempotent-retry semantics: it is validated\ntoday but not yet deduplicated, so a retry of a create that already succeeded is\nrejected as a duplicate company name rather than double-created — resolve\nit by listing with the company_name filter. Reference the created customer\nby id or company name when creating an order.",
        "operationId": "CustomerService_CreateCustomer",
        "tags": [
          "CustomerService"
        ],
        "requestBody": {
          "description": "Input for creating a customer: company details, addresses, and contacts. Company\nname and email are required; a tax identifier is optional.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateCustomerRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateCustomerResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation (for example a missing required field or a duplicate company name). The body is a validation-failure envelope: an overall type and message, the echoed request_id, and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/customers/{customerId}": {
      "get": {
        "summary": "Retrieve a single customer by its id.",
        "description": "Returns the full customer record — company details, addresses, contacts,\nand account flags — for the given id, scoped to your company.",
        "operationId": "CustomerService_GetCustomer",
        "tags": [
          "CustomerService"
        ],
        "parameters": [
          {
            "name": "customerId",
            "in": "path",
            "description": "The id of the customer to retrieve. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^cust_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetCustomerResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No customer with the given id exists for the authenticated company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/inventory": {
      "get": {
        "summary": "List stock levels for your inventory-tracked products.",
        "description": "Returns a paginated list of stock entries — one per product row per colour\n— for every catalogue product that has inventory tracking enabled.\nEvery entry is the stock of a single variant row, not a product total:\na product with several rows appears once per row, and a single-row\nproduct yields one entry. Each entry carries its variant's identifying\nattributes (thickness and name/value pairs), so entries are tellable\napart on their own; sum a product's entries when you need its overall\nposition. Filterable by product name, category, and supplier. Products\nare ordered alphabetically by name; entries within a product follow the\ncatalogue's row order. Non-tracked products never appear.",
        "operationId": "InventoryService_ListInventory",
        "tags": [
          "InventoryService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of stock entries to return per page. 0 uses the server\ndefault; the server may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "query",
            "description": "Optional: only return entries for products whose name contains this text\n(case-insensitive substring match).",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "categoryId",
            "in": "query",
            "description": "Optional: only return entries for products in this category.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^category_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "supplierId",
            "in": "query",
            "description": "Optional: only return entries for products linked to any of these\nsuppliers. A product appears if it is linked to at least one of the\nlisted suppliers. Use ListSuppliers on the SupplierService to discover\nvalid ids.",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListInventoryResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/inventory/{productId}": {
      "get": {
        "summary": "Retrieve stock levels for a single catalogue product.",
        "description": "Returns every stock entry for the given product — one per row per colour.\nEach entry is the stock of a single variant row (and colour), not a\nproduct total; sum the entries when you need the product's overall\nposition. Entries carry their variant's identifying attributes, and row\nids match the rows returned by the catalogue GetProduct endpoint. The\nproduct must have inventory tracking enabled; a non-tracked product\nreturns HTTP 404.",
        "operationId": "InventoryService_GetProductInventory",
        "tags": [
          "InventoryService"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "description": "The id of the product to retrieve inventory for. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^prod_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetProductInventoryResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No catalogue product with the given id exists for the authenticated company, or the product does not have inventory tracking enabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Set on-hand stock levels for a tracked product's rows.",
        "description": "Replaces the on-hand quantity for each row+colour combination you\ninclude. Entries you omit are left unchanged — this is a partial update,\nnot a full replace. The response echoes the product's complete stock\nposition (all entries, including unchanged ones) with recomputed\npromised, on-the-way, and available. The product must have inventory\ntracking enabled; a non-tracked product returns HTTP 404.",
        "operationId": "InventoryService_SetStockLevels",
        "tags": [
          "InventoryService"
        ],
        "parameters": [
          {
            "name": "productId",
            "in": "path",
            "description": "The product to update. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^prod_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetStockLevelsBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetStockLevelsResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation — for example a row id that does not belong to this product, a colour not in the product's material, or a malformed on_hand value. The body is a validation-failure envelope.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No catalogue product with the given id exists for the authenticated company, or the product does not have inventory tracking enabled.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders": {
      "get": {
        "summary": "List the authenticated company's orders, with pagination and optional filters.",
        "description": "A flat, company-scoped list of orders. Confirmed orders only: quotes are\nread through QuoteService, and work-in-progress documents that have not yet\nbecome a quote or an order never appear here. Orders are returned most\nrecently updated first; because that order shifts as orders change, use\nQueryOrders for a stable ordering suitable for incremental sync. Paginated\nvia page_size / page_token.\n\nOrders carry three independent status dimensions, each filterable here:\n\n * Workflow status (status_id or status_name) — the order's position on\n   your company's kanban board. Statuses are company-configured: discover\n   yours with GET /v1/company/order-statuses, then filter by id or exact\n   name. Filter by one, not both.\n * Payment status (payment_status) — whether the order is unpaid, partially\n   paid, or paid. A fixed enum.\n * Received status (received_status) — whether the order has been\n   delivered, picked up, installed, or not yet received. A fixed enum.\n\nAdditional filters: customer_id, reference (substring), is_submitted\n(draft/submitted), label_ids, fulfilment_method, required_after/before\n(required-by window), and archived (live vs archived view).",
        "operationId": "OrderService_ListOrders",
        "tags": [
          "OrderService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of orders to return per page. 0 uses the server default; the\nserver may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "customerId",
            "in": "query",
            "description": "Optional: only return orders for this customer id.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^cust_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "reference",
            "in": "query",
            "description": "Optional: only return orders whose external reference contains this text,\ncompared case-insensitively (a substring match, not exact-equals) — e.g. to\nfind a previously submitted order by part of its reference.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "isSubmitted",
            "in": "query",
            "description": "Optional: filter by whether the order has been submitted. Leave unset to\nreturn all orders; set to false to return only drafts; set to true to return\nonly submitted orders.",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "archived",
            "in": "query",
            "description": "Optional: return the archived set instead of the live one. False (the\ndefault) lists live orders, as always; true lists ONLY archived orders —\na separate view, not a superset. Each returned order carries\nis_archived: true. Changing this flag mid-pagination invalidates the page\ntoken.",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "labelIds",
            "in": "query",
            "description": "Optional: only return orders carrying at least one of these labels (by\nid — discover your labels with GET /v1/company/labels). An id not in\nyour label set matches no orders. Combines with the other filters.\nChanging the label filter mid-pagination invalidates the page token.\nDuplicate ids are rejected.",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "statusId",
            "in": "query",
            "description": "Optional: only return orders whose current workflow status matches this id.\nWorkflow statuses are company-configured kanban columns — every company has\ndifferent statuses. Call GET /v1/company/order-statuses to discover your\ncompany's statuses and their ids. Use either status_id or status_name to\nfilter, not both.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^status_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "statusName",
            "in": "query",
            "description": "Optional: only return orders whose current workflow status has this exact\nname (case-sensitive). An alternative to status_id when you know the name\nbut not the id — for example status_name=Submitted. Call\nGET /v1/company/order-statuses to see available names.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "paymentStatus",
            "in": "query",
            "description": "Optional: only return orders with this payment status. Unlike workflow\nstatus, payment status is a fixed set: UNPAID, PARTIALLY_PAID, or PAID.\nLeave unset to include all.\n\n - PAYMENT_STATUS_UNSPECIFIED: Default, unset value.\n - PAYMENT_STATUS_UNPAID: No payment has been received.\n - PAYMENT_STATUS_PARTIALLY_PAID: Part of the order total has been paid.\n - PAYMENT_STATUS_PAID: The order has been paid in full.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "PAYMENT_STATUS_UNSPECIFIED",
                "PAYMENT_STATUS_UNPAID",
                "PAYMENT_STATUS_PARTIALLY_PAID",
                "PAYMENT_STATUS_PAID"
              ],
              "default": "PAYMENT_STATUS_UNSPECIFIED"
            }
          },
          {
            "name": "receivedStatus",
            "in": "query",
            "description": "Optional: only return orders with this received status. Unlike workflow\nstatus, received status is a fixed set: DELIVERED, PICKED_UP,\nNOT_RECEIVED, or INSTALLED. Leave unset to include all.\n\n - RECEIVED_STATUS_UNSPECIFIED: Default, unset value.\n - RECEIVED_STATUS_DELIVERED: The order was delivered.\n - RECEIVED_STATUS_PICKED_UP: The order was picked up by the customer.\n - RECEIVED_STATUS_NOT_RECEIVED: The order has not yet reached the customer.\n - RECEIVED_STATUS_INSTALLED: The order was installed.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "RECEIVED_STATUS_UNSPECIFIED",
                "RECEIVED_STATUS_DELIVERED",
                "RECEIVED_STATUS_PICKED_UP",
                "RECEIVED_STATUS_NOT_RECEIVED",
                "RECEIVED_STATUS_INSTALLED"
              ],
              "default": "RECEIVED_STATUS_UNSPECIFIED"
            }
          },
          {
            "name": "fulfilmentMethod",
            "in": "query",
            "description": "Optional: only return orders with this fulfilment method. A fixed set:\nDELIVER, PICK_UP, or INSTALL. Leave unset to include all.\n\n - FULFILMENT_METHOD_UNSPECIFIED: Default, unset value.\n - FULFILMENT_METHOD_PICKUP: The customer collects the goods themselves.\n - FULFILMENT_METHOD_DELIVERY: The goods are delivered to the delivery address.\n - FULFILMENT_METHOD_INSTALL: The goods are installed at the install address.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "FULFILMENT_METHOD_UNSPECIFIED",
                "FULFILMENT_METHOD_PICKUP",
                "FULFILMENT_METHOD_DELIVERY",
                "FULFILMENT_METHOD_INSTALL"
              ],
              "default": "FULFILMENT_METHOD_UNSPECIFIED"
            }
          },
          {
            "name": "requiredAfter",
            "in": "query",
            "description": "Only orders required at or after this time. Optional; filters on the\norder's required-by date, independent of the other filters.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "requiredBefore",
            "in": "query",
            "description": "Only orders required strictly before this time. Optional; combine with\nrequired_after to bound a required-by window.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListOrdersResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a sales order for the authenticated company.",
        "description": "The primary write endpoint: submit a complete order — header, line items,\nadjustments, and addresses — in one call, either saved as a draft or\nsubmitted immediately. The order belongs to the company identified by the\nbearer token. To make a retry safe, send an idempotency key in `requestId`;\na repeat with the same key returns the original order instead of creating a\nduplicate. A customer that can't be resolved or an unknown catalogue id is\nrejected with HTTP 400 (InvalidArgument), carrying a ValidationFailure\ndetail.\n\nTax and currency follow your account's settings: a single tax rate and a\nsingle currency. You may send a `tax` descriptor (rate, code, jurisdiction)\nand amounts in any currency, but in this version they are advisory — the\nserver applies your account's configured rate to taxable lines (mark a line\nexempt with `isTaxFree`, or a customer with `disableTaxByDefault`) and\nstores all amounts in your account's currency. It can't yet apply a\ndifferent rate or currency per line, order, or jurisdiction, such as VAT or a\n15% rate. The created order echoes back the tax and currency actually applied\n(`tax`, `taxAmount`), so compare them to your input to detect any override;\nif your source system priced the order differently, the recomputed total can\ndiffer from your source document.",
        "operationId": "OrderService_CreateOrder",
        "tags": [
          "OrderService"
        ],
        "requestBody": {
          "description": "Input for creating an order: the customer, line items, adjustments,\naddresses, and delivery/submission options. Order totals are derived by the\nserver, not supplied here.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateOrderRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateOrderResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation. The body is a validation-failure envelope: an overall type and message, the echoed request_id, and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The idempotency key in request_id was already used with a materially different request body. Repeat the original request unchanged to retrieve the order it created, or send a new request_id to create a new order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{orderId}": {
      "get": {
        "summary": "Retrieve a single order by its id.",
        "description": "Returns the full order — header, lines, adjustments, totals, and\nserver-derived fields — for the given id, scoped to the authenticated\ncompany. Resolves confirmed orders only: an id that belongs to a quote\nreturns HTTP 404 (NotFound) — read quotes with GetQuote.",
        "operationId": "OrderService_GetOrder",
        "tags": [
          "OrderService"
        ],
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "description": "The id of the order to retrieve. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^order_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "includeArchived",
            "in": "query",
            "description": "Optional: also resolve the order if it is archived. By default an archived\norder is not found (404); set this to true to read it anyway — the\nreturned order carries is_archived: true. Archived orders stay read-only:\nthis flag never makes them writable.",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetOrderResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No order with the given id exists for the authenticated company, the order is archived (pass include_archived=true to read it anyway), or the document is still a quote — read it with GetQuote, swapping the id's order_ prefix for quote_ (the 26-character suffix stays the same).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{orderId}/drawings:uploadSvg": {
      "post": {
        "summary": "Attach client-rendered SVG images to an order's flashing drawings.",
        "description": "Call this after creating an order: map each drawing's temp_id to the\ndrawing_id returned on the created order, then upload the rendered SVG for\neach drawing_id. Stored SVGs are returned as a presigned URL\n(FlashingDrawing.svg_url) on later reads. Each drawing's outcome is reported\nindividually; a drawing is skipped if it isn't part of this order, isn't an\nimage/svg+xml, or has been deleted.",
        "operationId": "OrderService_UploadDrawingSvg",
        "tags": [
          "OrderService"
        ],
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "description": "The id of the order whose drawings are being uploaded. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^order_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OrderServiceUploadDrawingSvgBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/salesordersUploadDrawingSvgResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation. The body is a validation-failure envelope: an overall type and message, the echoed request_id, and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{orderId}/labels": {
      "put": {
        "summary": "Replace the set of labels on an order.",
        "description": "The list you send becomes the order's complete label set: labels not\nlisted are removed, and an empty list removes them all. Look up label\nids with ListLabels. A label id that does not exist in your company is\nrejected with HTTP 404 (NotFound) and nothing is changed.",
        "operationId": "OrderService_SetOrderLabels",
        "tags": [
          "OrderService"
        ],
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "description": "The id of the order, as returned by CreateOrder.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^order_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetOrderLabelsBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetOrderLabelsResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation — for example a badly formed or duplicated label id. The body is a validation-failure envelope: an overall type and message and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No order with the given id exists for the authenticated company, the order is archived (archived orders are read-only), or the document is still a quote — label it with SetQuoteLabels. Also returned when a label id does not exist in your company: nothing is changed and the order's labels are left as they were.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{orderId}/lines": {
      "post": {
        "summary": "Add a line item to an existing order.",
        "description": "Build an order up over time: each call creates one line under the order and\nrecomputes the order's totals. One line per call; add several lines with\nseveral calls.\n\nFlashing lines cannot yet be added to an existing order and are rejected\nwith HTTP 501 (Unimplemented): include them when creating the order, or\nadd them to the quote before conversion.",
        "operationId": "OrderService_AddOrderLineItem",
        "tags": [
          "OrderService"
        ],
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "description": "The id of the order to add the line item to, as returned by CreateOrder.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^order_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddOrderLineItemBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddOrderLineItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation — the body is a validation-failure envelope: an overall type and message, the echoed request_id, and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field) — or because the order is not modifiable: an invoiced or accounting-locked order refuses line changes (FailedPrecondition).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No order with the given id exists for the authenticated company, the order is archived (archived orders are read-only), or the document is still a quote — manage its lines with the QuoteService line endpoints, swapping the id's order_ prefix for quote_ (the 26-character suffix stays the same).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The idempotency key in request_id was already used with a materially different request body. Repeat the original request unchanged to retrieve its original result, or send a new request_id to add a new line.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders/{orderId}/lines/{lineId}": {
      "delete": {
        "summary": "Remove a single line item from an order.",
        "description": "Identify the line by the server-assigned id returned when you read the\norder; the order's totals are recomputed after removal. A line id that does\nnot belong to the order is rejected with HTTP 404 (NotFound). An order that\nhas been invoiced is rejected with HTTP 400 (FailedPrecondition).",
        "operationId": "OrderService_DeleteOrderLineItem",
        "tags": [
          "OrderService"
        ],
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "description": "The id of the order the line belongs to, as returned by CreateOrder.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^order_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "lineId",
            "in": "path",
            "description": "The server-assigned id of the line to remove, as returned when the order is\nread. This has a different form from the order id.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^line_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteOrderLineItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id (the body is a validation-failure envelope: an overall type and message and one entry per problem, each with a stable code, a message, and a param pointing at the offending parameter) — or because the order is not modifiable: an invoiced or accounting-locked order refuses line changes (FailedPrecondition).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No order with the given id exists for the authenticated company, the order is archived (archived orders are read-only), the document is still a quote — manage its lines with the QuoteService line endpoints — or the line id does not identify a line on this order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Replace a single line item on an order.",
        "description": "Send the full replacement line content; the whole line is replaced and the\norder's totals are recomputed. Identify the line by the server-assigned id\nreturned when you read the order. A line id that does not belong to the\norder is rejected with HTTP 404 (NotFound). An order that has been invoiced\nis rejected with HTTP 400 (FailedPrecondition).",
        "operationId": "OrderService_UpdateOrderLineItem",
        "tags": [
          "OrderService"
        ],
        "parameters": [
          {
            "name": "orderId",
            "in": "path",
            "description": "The id of the order the line belongs to, as returned by CreateOrder.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^order_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "lineId",
            "in": "path",
            "description": "The server-assigned id of the line to replace, as returned when the order is\nread. This has a different form from the order id.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^line_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "requestBody": {
          "description": "The replacement line content. The whole line is replaced with this — its kind\n(on-the-fly, catalogue, labour, notes, or product kit) and all its fields\ntake effect as sent.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SalesLine"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateOrderLineItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation — the body is a validation-failure envelope: an overall type and message and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field) — or because the order is not modifiable: an invoiced or accounting-locked order refuses line changes (FailedPrecondition).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No order with the given id exists for the authenticated company, the order is archived (archived orders are read-only), the document is still a quote — manage its lines with the QuoteService line endpoints — or the line id does not identify a line on this order.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/orders:query": {
      "get": {
        "summary": "Query the company's orders as a change feed, for incremental sync.",
        "description": "Like ListOrders, but ordered by when each order last changed (oldest first)\nand filterable by an updated_since / updated_before time window — so a\npoller fetches only what changed since its last run and checkpoints on the\nmost recent order it saw. Use ListOrders for simple lookups; use this to\nkeep an external system (CRM, reporting, accounting) in sync. Paginated\nvia page_size / page_token.\n\nCarries confirmed orders only — quotes are read through QuoteService, and\nwork-in-progress documents never appear. One migration to expect while\nsyncing: an order that began as a quote enters this feed at conversion as\nan order id that shares the quote id's 26-character suffix —\n\"quote_<suffix>\" becomes \"order_<suffix>\" — so a document you first saw on\nthe quote feed is recognised here by swapping the prefix.\n\nThe same three status dimensions as ListOrders are filterable here:\n\n * Workflow status (status_id or status_name) — the order's position on\n   your company's kanban board. Discover statuses with\n   GET /v1/company/order-statuses, then filter by id or exact name.\n * Payment status (payment_status) — unpaid, partially paid, or paid.\n * Received status (received_status) — delivered, picked up, installed, or\n   not yet received.\n\nAdditional filters: customer_id, reference (substring), is_submitted,\nfulfilment_method, created_after/before (creation window),\nrequired_after/before (required-by window), and include_archived.",
        "operationId": "OrderService_QueryOrders",
        "tags": [
          "OrderService"
        ],
        "parameters": [
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Return only orders updated at or after this time. Pass your last sync time\nhere to fetch just what changed since then; omit to start from the\nbeginning. This is the primary filter for incremental sync.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedBefore",
            "in": "query",
            "description": "Optional upper bound: return only orders updated strictly before this time.\nCombine with updated_since to page through a bounded window; omit for \"up to\nnow\".",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of orders to return per page. 0 uses the server default; the\nserver may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "customerId",
            "in": "query",
            "description": "Optional: only return orders for this customer id.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^cust_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "reference",
            "in": "query",
            "description": "Optional: only return orders whose external reference contains this text,\ncompared case-insensitively (a substring match, not exact-equals).",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "statusId",
            "in": "query",
            "description": "Optional: only return orders whose current workflow status matches this id.\nWorkflow statuses are company-configured kanban columns — every company has\ndifferent statuses. Call GET /v1/company/order-statuses to discover your\ncompany's statuses and their ids. Use either status_id or status_name to\nfilter, not both.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^status_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "paymentStatus",
            "in": "query",
            "description": "Optional: only return orders with this payment status. Unlike workflow\nstatus, payment status is a fixed set: UNPAID, PARTIALLY_PAID, or PAID.\nLeave unset to include all.\n\n - PAYMENT_STATUS_UNSPECIFIED: Default, unset value.\n - PAYMENT_STATUS_UNPAID: No payment has been received.\n - PAYMENT_STATUS_PARTIALLY_PAID: Part of the order total has been paid.\n - PAYMENT_STATUS_PAID: The order has been paid in full.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "PAYMENT_STATUS_UNSPECIFIED",
                "PAYMENT_STATUS_UNPAID",
                "PAYMENT_STATUS_PARTIALLY_PAID",
                "PAYMENT_STATUS_PAID"
              ],
              "default": "PAYMENT_STATUS_UNSPECIFIED"
            }
          },
          {
            "name": "isSubmitted",
            "in": "query",
            "description": "Optional: filter by whether the order has been submitted. Leave unset to\nreturn all orders; set to false to return only drafts; set to true to return\nonly submitted orders.",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "includeArchived",
            "in": "query",
            "description": "Optional: widen the feed to include archived orders. False (the default)\nkeeps today's feed of live orders; true returns live AND archived orders\nin one feed. Archiving stamps the order's update time, so with this flag\na poller sees the archive happen in-band: the order reappears with\nis_archived: true and a fresh last_updated_at (and a restore reappears\nwith false) — no separate feed to reconcile. Changing this flag\nmid-pagination invalidates the page token.",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "description": "Only orders created at or after this time (inclusive). Optional; filters on\nthe order's creation time, independent of the updated window — combine the\ntwo windows to sync recent changes for orders created in a period. Note:\nthis parameter is named created_after, not created_since — it is a range\nfilter, not a sync checkpoint.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "createdBefore",
            "in": "query",
            "description": "Only orders created strictly before this time (exclusive). Optional;\ncombine with created_after to bound a creation window.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "statusName",
            "in": "query",
            "description": "Optional: only return orders whose current workflow status has this exact\nname (case-sensitive). An alternative to status_id when you know the name\nbut not the id — for example status_name=Submitted. Call\nGET /v1/company/order-statuses to see available names.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          },
          {
            "name": "receivedStatus",
            "in": "query",
            "description": "Optional: only return orders with this received status. Unlike workflow\nstatus, received status is a fixed set: DELIVERED, PICKED_UP,\nNOT_RECEIVED, or INSTALLED. Leave unset to include all.\n\n - RECEIVED_STATUS_UNSPECIFIED: Default, unset value.\n - RECEIVED_STATUS_DELIVERED: The order was delivered.\n - RECEIVED_STATUS_PICKED_UP: The order was picked up by the customer.\n - RECEIVED_STATUS_NOT_RECEIVED: The order has not yet reached the customer.\n - RECEIVED_STATUS_INSTALLED: The order was installed.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "RECEIVED_STATUS_UNSPECIFIED",
                "RECEIVED_STATUS_DELIVERED",
                "RECEIVED_STATUS_PICKED_UP",
                "RECEIVED_STATUS_NOT_RECEIVED",
                "RECEIVED_STATUS_INSTALLED"
              ],
              "default": "RECEIVED_STATUS_UNSPECIFIED"
            }
          },
          {
            "name": "fulfilmentMethod",
            "in": "query",
            "description": "Optional: only return orders with this fulfilment method. A fixed set:\nDELIVER, PICK_UP, or INSTALL. Leave unset to include all.\n\n - FULFILMENT_METHOD_UNSPECIFIED: Default, unset value.\n - FULFILMENT_METHOD_PICKUP: The customer collects the goods themselves.\n - FULFILMENT_METHOD_DELIVERY: The goods are delivered to the delivery address.\n - FULFILMENT_METHOD_INSTALL: The goods are installed at the install address.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "FULFILMENT_METHOD_UNSPECIFIED",
                "FULFILMENT_METHOD_PICKUP",
                "FULFILMENT_METHOD_DELIVERY",
                "FULFILMENT_METHOD_INSTALL"
              ],
              "default": "FULFILMENT_METHOD_UNSPECIFIED"
            }
          },
          {
            "name": "requiredAfter",
            "in": "query",
            "description": "Only orders required at or after this time. Optional; filters on the\norder's required-by date, independent of the other filters.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "requiredBefore",
            "in": "query",
            "description": "Only orders required strictly before this time. Optional; combine with\nrequired_after to bound a required-by window.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QueryOrdersResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/quotes": {
      "get": {
        "summary": "List the authenticated company's quotes, with pagination and optional filters.",
        "description": "A flat, company-scoped list of quotes. Quotes only — confirmed orders are\nread through OrderService. Quotes are returned most recently updated first;\nbecause that order shifts as quotes change, use QueryQuotes for a stable\nordering suitable for incremental sync. List entries are summaries — the\nquote header and its totals without the line items; fetch the full document\nwith GetQuote. Paginated via page_size / page_token.\n\nQuotes have no workflow or payment status. Available filters: customer_id,\nreference (substring), is_submitted (draft/sent), label_ids,\nfulfilment_method, required_after/before (required-by window), and\narchived (live vs archived view).",
        "operationId": "QuoteService_ListQuotes",
        "tags": [
          "QuoteService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of quotes to return per page. 0 uses the server default; the\nserver may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "customerId",
            "in": "query",
            "description": "Optional: only return quotes for this customer id.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^cust_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "reference",
            "in": "query",
            "description": "Optional: only return quotes whose external reference contains this text,\ncompared case-insensitively (a substring match, not exact-equals) — e.g. to\nfind a previously submitted quote by part of its reference.",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "isSubmitted",
            "in": "query",
            "description": "Optional: filter by whether the quote has been sent. Leave unset to return\nall quotes; set to false to return only drafts; set to true to return only\nsent quotes.",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "labelIds",
            "in": "query",
            "description": "Optional: only return quotes carrying at least one of these labels (by\nid — discover your labels with GET /v1/company/labels). An id not in\nyour label set matches no quotes. Combines with the other filters.\nChanging the label filter mid-pagination invalidates the page token.\nDuplicate ids are rejected.",
            "required": false,
            "schema": {
              "type": "array",
              "items": {
                "type": "string"
              }
            }
          },
          {
            "name": "fulfilmentMethod",
            "in": "query",
            "description": "Optional: only return quotes with this fulfilment method. Leave unset to\ninclude all.\n\n - FULFILMENT_METHOD_UNSPECIFIED: Default, unset value.\n - FULFILMENT_METHOD_PICKUP: The customer collects the goods themselves.\n - FULFILMENT_METHOD_DELIVERY: The goods are delivered to the delivery address.\n - FULFILMENT_METHOD_INSTALL: The goods are installed at the install address.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "FULFILMENT_METHOD_UNSPECIFIED",
                "FULFILMENT_METHOD_PICKUP",
                "FULFILMENT_METHOD_DELIVERY",
                "FULFILMENT_METHOD_INSTALL"
              ],
              "default": "FULFILMENT_METHOD_UNSPECIFIED"
            }
          },
          {
            "name": "requiredAfter",
            "in": "query",
            "description": "Only quotes required at or after this time. Optional; filters on the\nquote's required-by date, independent of the other filters.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "requiredBefore",
            "in": "query",
            "description": "Only quotes required strictly before this time. Optional; combine with\nrequired_after to bound a required-by window.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListQuotesResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "post": {
        "summary": "Create a quote for the authenticated company.",
        "description": "Submit a complete quote — customer, line items, adjustments, and addresses —\nin one call, either saved as a draft or finalised and sent. The quote\nbelongs to the company identified by the bearer token. To make a retry safe,\nsend an idempotency key in `requestId`; a repeat with the same key returns\nthe original quote instead of creating a duplicate. A customer that can't be\nresolved or an unknown catalogue id is rejected with HTTP 400\n(InvalidArgument), carrying a factory.api.v1.Error detail.\n\nQuote totals (subtotal, tax amount, total, margin) are derived by the server\nfrom the line items, not supplied here. You may send a `tax` descriptor (rate,\ncode, jurisdiction); in this version it is advisory — the server applies your\naccount's configured rate to taxable lines (mark a line exempt with\n`isTaxFree`) and echoes the tax actually applied back on the quote\n(`tax`, `taxAmount`).",
        "operationId": "QuoteService_CreateQuote",
        "tags": [
          "QuoteService"
        ],
        "requestBody": {
          "description": "Input for creating a quote: the customer, line items, adjustments, addresses,\nand draft/sent option. Quote totals are derived by the server, not supplied\nhere.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateQuoteRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CreateQuoteResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation. The body is a validation-failure envelope: an overall type and message, the echoed request_id, and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The idempotency key in request_id was already used with a materially different request body. Repeat the original request unchanged to retrieve the quote it created, or send a new request_id to create a new quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/quotes/{quoteId}": {
      "get": {
        "summary": "Retrieve a single quote by its id.",
        "description": "Returns the full quote — header, lines, adjustments, totals, and\nserver-derived fields — for the given id, scoped to the authenticated\ncompany. Resolves quotes only: once a quote has been accepted and converted\nto an order in the Factory app, its id returns HTTP 404 (NotFound) here and\nthe document is read with GetOrder instead.",
        "operationId": "QuoteService_GetQuote",
        "tags": [
          "QuoteService"
        ],
        "parameters": [
          {
            "name": "quoteId",
            "in": "path",
            "description": "The id of the quote to retrieve. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^quote_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetQuoteResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No quote with the given id exists for the authenticated company, or the quote has been accepted and converted to an order — read it with GetOrder, swapping the id's quote_ prefix for order_ (the 26-character suffix stays the same).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/quotes/{quoteId}/drawings:uploadSvg": {
      "post": {
        "summary": "Attach client-rendered SVG images to a quote's flashing drawings.",
        "description": "Call this after creating a quote: map each drawing's temp_id to the\ndrawing_id returned on the created quote, then upload the rendered SVG for\neach drawing_id. Stored SVGs are returned as a presigned URL\n(FlashingDrawing.svg_url) on later reads. Each drawing's outcome is reported\nindividually; a drawing is skipped if it isn't part of this quote, isn't an\nimage/svg+xml, or has been deleted.",
        "operationId": "QuoteService_UploadDrawingSvg",
        "tags": [
          "QuoteService"
        ],
        "parameters": [
          {
            "name": "quoteId",
            "in": "path",
            "description": "The id of the quote whose drawings are being uploaded. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^quote_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/QuoteServiceUploadDrawingSvgBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/quotesUploadDrawingSvgResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation. The body is a validation-failure envelope: an overall type and message, the echoed request_id, and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/quotes/{quoteId}/labels": {
      "put": {
        "summary": "Replace the set of labels on a quote.",
        "description": "The list you send becomes the quote's complete label set: labels not\nlisted are removed, and an empty list removes them all. Look up label\nids with ListLabels. A label id that does not exist in your company is\nrejected with HTTP 404 (NotFound) and nothing is changed.",
        "operationId": "QuoteService_SetQuoteLabels",
        "tags": [
          "QuoteService"
        ],
        "parameters": [
          {
            "name": "quoteId",
            "in": "path",
            "description": "The id of the quote, as returned by CreateQuote.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^quote_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SetQuoteLabelsBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SetQuoteLabelsResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation — for example a badly formed or duplicated label id. The body is a validation-failure envelope: an overall type and message and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No quote with the given id exists for the authenticated company, or the quote has been accepted and converted to an order — label it with SetOrderLabels, swapping the id's quote_ prefix for order_ (the 26-character suffix stays the same). Also returned when a label id does not exist in your company: nothing is changed and the quote's labels are left as they were.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/quotes/{quoteId}/lines": {
      "post": {
        "summary": "Add a line item to an existing quote.",
        "description": "Build a quote up over time: each call creates one line under the quote and\nrecomputes the quote's totals. One line per call; add several lines with\nseveral calls. Pair with CreateQuote, which imports a whole quote in one\natomic call (or creates an empty quote you then add lines to with this).",
        "operationId": "QuoteService_AddQuoteLineItem",
        "tags": [
          "QuoteService"
        ],
        "parameters": [
          {
            "name": "quoteId",
            "in": "path",
            "description": "The id of the quote to add the line item to, as returned by CreateQuote.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^quote_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddQuoteLineItemBody"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AddQuoteLineItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation. The body is a validation-failure envelope: an overall type and message, the echoed request_id, and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No quote with the given id exists for the authenticated company, or the quote has been accepted and converted to an order — manage its lines with the OrderService line endpoints, swapping the id's quote_ prefix for order_ (the 26-character suffix stays the same).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "409": {
            "description": "The idempotency key in request_id was already used with a materially different request body. Repeat the original request unchanged to retrieve its original result, or send a new request_id to add a new line.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/quotes/{quoteId}/lines/{lineId}": {
      "delete": {
        "summary": "Remove a single line item from a quote.",
        "description": "Identify the line by the server-assigned id returned when you read the quote;\nthe quote's totals are recomputed after removal. A line id that does not\nbelong to the quote is rejected with HTTP 404 (NotFound).",
        "operationId": "QuoteService_DeleteQuoteLineItem",
        "tags": [
          "QuoteService"
        ],
        "parameters": [
          {
            "name": "quoteId",
            "in": "path",
            "description": "The id of the quote the line belongs to, as returned by CreateQuote.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^quote_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "lineId",
            "in": "path",
            "description": "The server-assigned id of the line to remove, as returned when the quote is\nread. This has a different form from the quote id.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^line_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DeleteQuoteLineItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No quote with the given id exists for the authenticated company, the quote has been accepted and converted to an order — manage its lines with the OrderService line endpoints — or the line id does not identify a line on this quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      },
      "put": {
        "summary": "Replace a single line item on a quote.",
        "description": "Send the full replacement line content; the whole line is replaced and the\nquote's totals are recomputed. Identify the line by the server-assigned id\nreturned when you read the quote. A line id that does not belong to the quote\nis rejected with HTTP 404 (NotFound).",
        "operationId": "QuoteService_UpdateQuoteLineItem",
        "tags": [
          "QuoteService"
        ],
        "parameters": [
          {
            "name": "quoteId",
            "in": "path",
            "description": "The id of the quote the line belongs to, as returned by CreateQuote.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^quote_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "lineId",
            "in": "path",
            "description": "The server-assigned id of the line to replace, as returned when the quote is\nread. This has a different form from the quote id.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^line_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "requestBody": {
          "description": "The replacement line content. The whole line is replaced with this — its kind\n(on-the-fly, catalogue, labour, notes, flashing, or product kit) and all its\nfields take effect as sent.",
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SalesLine"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UpdateQuoteLineItemResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it failed validation. The body is a validation-failure envelope: an overall type and message and one entry per field-level problem (each with a stable code, a message, and a param pointing at the offending field).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No quote with the given id exists for the authenticated company, the quote has been accepted and converted to an order — manage its lines with the OrderService line endpoints — or the line id does not identify a line on this quote.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/quotes:query": {
      "get": {
        "summary": "Query the company's quotes as a change feed, for incremental sync.",
        "description": "Like ListQuotes, but ordered by when each quote last changed (oldest first)\nand filterable by an updated_since / updated_before time window — so a\npoller fetches only what changed since its last run and checkpoints on the\nmost recent quote it saw. Use ListQuotes for simple lookups; use this to\nkeep an external system (CRM, reporting) in sync. Paginated via page_size /\npage_token.\n\nQuotes have no workflow or payment status. Available filters: customer_id,\nreference (substring), is_submitted (draft/sent), label_ids,\nfulfilment_method, created_after/before (creation window),\nrequired_after/before (required-by window), and include_archived.\n\nOne migration to expect while syncing: a quote accepted in the Factory app\nbecomes an order. It leaves this feed without a tombstone entry — there is\nno final \"deleted\" record — and reappears in QueryOrders as an order id.\nThe two ids share their 26-character suffix and differ only in prefix: a\nquote \"quote_<suffix>\" becomes the order \"order_<suffix>\". Reconcile a\nquote that stops updating here by swapping the prefix and checking the\norder feed for that id.",
        "operationId": "QuoteService_QueryQuotes",
        "tags": [
          "QuoteService"
        ],
        "parameters": [
          {
            "name": "updatedSince",
            "in": "query",
            "description": "Return only quotes updated at or after this time. Pass your last sync time\nhere to fetch just what changed since then; omit to start from the\nbeginning. This is the primary filter for incremental sync.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "updatedBefore",
            "in": "query",
            "description": "Optional upper bound: return only quotes updated strictly before this time.\nCombine with updated_since to page through a bounded window; omit for \"up to\nnow\".",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of quotes to return per page. 0 uses the server default; the\nserver may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "customerId",
            "in": "query",
            "description": "Optional: only return quotes for this customer id.",
            "required": false,
            "schema": {
              "type": "string",
              "pattern": "^cust_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "name": "reference",
            "in": "query",
            "description": "Optional: only return quotes whose external reference contains this text,\ncompared case-insensitively (a substring match, not exact-equals).",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 1000
            }
          },
          {
            "name": "isSubmitted",
            "in": "query",
            "description": "Optional: filter by whether the quote has been sent. Leave unset to return\nall quotes; set to false to return only drafts; set to true to return only\nsent quotes.",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "name": "createdAfter",
            "in": "query",
            "description": "Only quotes created at or after this time (inclusive). Optional; filters on\nthe quote's creation time, independent of the updated window — combine the\ntwo windows to sync recent changes for quotes created in a period. Note:\nthis parameter is named created_after, not created_since — it is a range\nfilter, not a sync checkpoint.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "createdBefore",
            "in": "query",
            "description": "Only quotes created strictly before this time (exclusive). Optional;\ncombine with created_after to bound a creation window.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "fulfilmentMethod",
            "in": "query",
            "description": "Optional: only return quotes with this fulfilment method. Leave unset to\ninclude all.\n\n - FULFILMENT_METHOD_UNSPECIFIED: Default, unset value.\n - FULFILMENT_METHOD_PICKUP: The customer collects the goods themselves.\n - FULFILMENT_METHOD_DELIVERY: The goods are delivered to the delivery address.\n - FULFILMENT_METHOD_INSTALL: The goods are installed at the install address.",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "FULFILMENT_METHOD_UNSPECIFIED",
                "FULFILMENT_METHOD_PICKUP",
                "FULFILMENT_METHOD_DELIVERY",
                "FULFILMENT_METHOD_INSTALL"
              ],
              "default": "FULFILMENT_METHOD_UNSPECIFIED"
            }
          },
          {
            "name": "requiredAfter",
            "in": "query",
            "description": "Only quotes required at or after this time. Optional; filters on the\nquote's required-by date, independent of the other filters.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "name": "requiredBefore",
            "in": "query",
            "description": "Only quotes required strictly before this time. Optional; combine with\nrequired_after to bound a required-by window.",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date-time"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/QueryQuotesResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/suppliers": {
      "get": {
        "summary": "List suppliers in your company.",
        "description": "Returns a paginated list of your company's suppliers. Useful for\ndiscovering supplier ids to pass as filters on the inventory list.\nSuppliers are ordered alphabetically by name.",
        "operationId": "SupplierService_ListSuppliers",
        "tags": [
          "SupplierService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of suppliers to return per page. 0 uses the server\ndefault; the server may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "query",
            "description": "Optional: only return suppliers whose name contains this text\n(case-insensitive substring match).",
            "required": false,
            "schema": {
              "type": "string",
              "maxLength": 150
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListSuppliersResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/suppliers/{supplierId}": {
      "get": {
        "summary": "Retrieve a single supplier by id.",
        "description": "Returns the supplier matching the given id. Useful when you already\nhave a supplier id (for example from an inventory filter) and need\nits details without listing.",
        "operationId": "SupplierService_GetSupplier",
        "tags": [
          "SupplierService"
        ],
        "parameters": [
          {
            "name": "supplierId",
            "in": "path",
            "description": "The id of the supplier to retrieve. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^supplier_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetSupplierResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No supplier with the given id exists for the authenticated company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/users": {
      "get": {
        "summary": "List the users in your company.",
        "description": "A company-scoped list of users, returning active users by default. Use it\nto find a user to assign to a labour line on an order. Users are returned in\na stable order, sorted by username (the login name). User emails are\npopulated only when the caller is a company administrator; other callers\nreceive users without email addresses.",
        "operationId": "UserService_ListUsers",
        "tags": [
          "UserService"
        ],
        "parameters": [
          {
            "name": "pageSize",
            "in": "query",
            "description": "Maximum number of users to return per page. 0 uses the server default; the\nserver may cap the value.",
            "required": false,
            "schema": {
              "type": "integer",
              "format": "int32",
              "minimum": 0
            }
          },
          {
            "name": "pageToken",
            "in": "query",
            "description": "Opaque page token from a previous response, used to fetch the next page.",
            "required": false,
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "activeOnly",
            "in": "query",
            "description": "Whether to return only active, non-deleted users. When omitted, defaults\nto true; set it explicitly to false to include inactive users.",
            "required": false,
            "schema": {
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListUsersResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example an unusable page token or an invalid filter value. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/v1/users/{userId}": {
      "get": {
        "summary": "Retrieve a single company user by id.",
        "description": "Returns the user record — name, email, and active status — for the given\nid, scoped to your company.",
        "operationId": "UserService_GetUser",
        "tags": [
          "UserService"
        ],
        "parameters": [
          {
            "name": "userId",
            "in": "path",
            "description": "The id of the user to retrieve. Required.",
            "required": true,
            "schema": {
              "type": "string",
              "pattern": "^user_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            }
          },
          {
            "$ref": "#/components/parameters/fields"
          },
          {
            "$ref": "#/components/parameters/excludeFields"
          }
        ],
        "responses": {
          "200": {
            "description": "A successful response.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GetUserResponse"
                }
              }
            }
          },
          "400": {
            "description": "The request was rejected because it was malformed — for example a badly formed id. The body is a validation-failure envelope: an overall type and message and one entry per problem (each with a stable code, a message, and a param pointing at the offending parameter).",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "401": {
            "description": "The request is missing a valid bearer token, or the token is invalid or expired.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "403": {
            "description": "The authenticated user is not a company administrator. Retrieving a user by id is an administrator-only operation.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "404": {
            "description": "No user with the given id exists in the authenticated company.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "default": {
            "description": "Any other error. The body is the same error envelope every error uses: a short stable type identifying the kind of failure (for example \"rate_limited\" or \"internal\"), a human-readable message, and the request's idempotency key echoed back when one was supplied.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "Address": {
        "type": "object",
        "properties": {
          "address1": {
            "type": "string",
            "description": "First address line (street number and name).",
            "maxLength": 100
          },
          "address2": {
            "type": "string",
            "description": "Second address line (unit, suite, or similar).",
            "maxLength": 50
          },
          "city": {
            "type": "string",
            "description": "City or suburb.",
            "maxLength": 50
          },
          "state": {
            "type": "string",
            "description": "State, province, or region.",
            "maxLength": 10
          },
          "postalCode": {
            "type": "string",
            "description": "Postal code (ZIP code, postcode).",
            "maxLength": 10
          },
          "countryCode": {
            "type": "string",
            "description": "Country, as an uppercase ISO 3166-1 alpha-2 code (e.g. \"AU\"). Optional;\nwhen supplied, any other form is rejected.",
            "pattern": "^[A-Z]{2}$"
          }
        },
        "description": "A single postal address."
      },
      "Error": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "description": "A short, stable identifier for the kind of failure (for example\n\"validation_failure\", \"not_found\", \"conflict\", \"rate_limited\"). Treat an\nunrecognised type as a generic error."
          },
          "message": {
            "type": "string",
            "description": "A human-readable summary of the failure."
          },
          "requestId": {
            "type": "string",
            "description": "The request's idempotency key, echoed back when one was supplied."
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ErrorDetail"
            },
            "description": "The individual field-level problems that caused the request to fail.\nPopulated on validation failures; empty for every other kind of failure."
          }
        },
        "description": "The error envelope.\n\nThe body of every error response, whatever the HTTP status. It names the\nkind of failure, summarises it for a human, echoes the request's\nidempotency key when one was supplied, and — for validation failures —\nlists the individual field-level problems."
      },
      "ErrorDetail": {
        "type": "object",
        "properties": {
          "code": {
            "type": "string",
            "description": "A short, stable error code identifying the problem (e.g.\n\"customer_not_found\", \"product_not_found\")."
          },
          "message": {
            "type": "string",
            "description": "A human-readable explanation of the problem."
          },
          "param": {
            "type": "string",
            "description": "A pointer to the offending field (e.g.\n\"lines[2].catalogue.product_id\")."
          }
        },
        "description": "A single problem with one request field."
      },
      "Flashing": {
        "type": "object",
        "properties": {
          "templateId": {
            "type": "string",
            "description": "The template's unique id. Reference this on a flashing line item.",
            "pattern": "^flashtpl_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productType": {
            "type": "string",
            "description": "The catalogue product type of this template. Flashing templates report\n\"Flashing\"; it marks them apart from other kinds of catalogue product.",
            "maxLength": 150
          },
          "thickness": {
            "type": "string",
            "description": "The thickness this template is for, as a decimal string in the template's\nunits (e.g. \"0.55\", millimetres for metric accounts). Fixed for the\ntemplate: each thickness is a separate template with its own id, so to\norder a different thickness, select the template (template_id) that has it.",
            "maxLength": 30
          },
          "flashingId": {
            "type": "string",
            "description": "The id of the underlying flashing.",
            "pattern": "^flashing_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "flashingName": {
            "type": "string",
            "description": "The display name of the underlying flashing.",
            "maxLength": 150
          },
          "materialId": {
            "type": "string",
            "description": "The id of the template's material selection.",
            "pattern": "^material_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "materialName": {
            "type": "string",
            "description": "The display name of the template's material selection.",
            "maxLength": 150
          },
          "colours": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 100
            },
            "description": "The colours you can select for a flashing line built on this template. A\nline's colour must be one of these. Colour is the per-line choice; the\nthickness is fixed by the template id (see `thickness`)."
          }
        },
        "description": "A flashing product template — one flashing at one thickness. A flashing line\nitem must reference one of these by id; it also surfaces the selectable\ncolours you choose from when building that line."
      },
      "GetFlashingResponse": {
        "type": "object",
        "properties": {
          "flashing": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Flashing"
              }
            ],
            "description": "The requested flashing template."
          }
        },
        "description": "Result of retrieving a single flashing template."
      },
      "GetKitResponse": {
        "type": "object",
        "properties": {
          "kit": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Kit"
              }
            ],
            "description": "The requested kit, including its rows and full component tree."
          }
        },
        "description": "Result of retrieving a single product kit."
      },
      "GetProductResponse": {
        "type": "object",
        "properties": {
          "product": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Product"
              }
            ],
            "description": "The requested product."
          }
        },
        "description": "Result of retrieving a single catalogue product."
      },
      "Kit": {
        "type": "object",
        "properties": {
          "kitId": {
            "type": "string",
            "description": "The kit's id — the id of the catalogue product that fronts this kit. This\nis the id a kit line item references, and the id GetKit accepts.",
            "pattern": "^kit_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "name": {
            "type": "string",
            "description": "The kit's display name.",
            "maxLength": 150
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KitRow"
            },
            "description": "The kit's priced variant rows. Populated by GetKit; the kit listing leaves\nthis empty."
          },
          "components": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KitProductComponent"
            },
            "description": "The kit's top-level component products (those not inside a sub-assembly).\nPopulated by GetKit; the kit listing leaves this empty."
          },
          "subKits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KitSubAssembly"
            },
            "description": "The kit's sub-assemblies, each holding its own component products. Nested\none level deep (a sub-assembly cannot itself contain sub-assemblies)."
          }
        },
        "description": "A configured product kit — a product assembled from component products,\noptionally grouped into sub-assemblies."
      },
      "KitProductComponent": {
        "type": "object",
        "properties": {
          "kitProductId": {
            "type": "string",
            "description": "The component's unique id within the kit.",
            "pattern": "^kitcomp_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productId": {
            "type": "string",
            "description": "The id of the catalogue product used as this component. Additional-product\ncomponents only.",
            "pattern": "^prod_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productRowId": {
            "type": "string",
            "description": "The id of the specific product variant row used as this component.\nCatalogue-product components only.",
            "pattern": "^prodrow_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "colour": {
            "type": "string",
            "description": "The component's colour. Catalogue-product components only.",
            "maxLength": 100
          },
          "material": {
            "type": "string",
            "description": "The component's material. Catalogue-product components only.",
            "maxLength": 100
          },
          "quantity": {
            "type": "string",
            "description": "The component's quantity, as a decimal string.",
            "maxLength": 32
          },
          "productType": {
            "allOf": [
              {
                "$ref": "#/components/schemas/KitComponentType"
              }
            ],
            "description": "The component's kind: catalogue product, notes, or labour. Determines which\nof the fields below are populated. On-the-fly is never a kit-template\ncomponent, so it is not part of this set."
          },
          "notes": {
            "type": "string",
            "description": "Free-text note. Populated when this is a notes component.",
            "maxLength": 4000
          },
          "notesType": {
            "allOf": [
              {
                "$ref": "#/components/schemas/NotesType"
              }
            ],
            "description": "Whether the note is internal-only or customer-visible. Notes components\nonly."
          },
          "labourUserId": {
            "type": "string",
            "description": "The assigned company user's id. Populated when this is a labour component.",
            "pattern": "^user_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "hourlyRateCharged": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The labour charge-out rate per hour. Labour components only."
          },
          "hourlyRateCost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The labour cost rate per hour. Labour components only."
          },
          "customHourlyRateCharged": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "A custom override of the charge-out rate, when set. Labour components only."
          },
          "customHourlyRateCost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "A custom override of the cost rate, when set. Labour components only."
          },
          "productName": {
            "type": "string",
            "description": "The display name of the catalogue product used as this component.\nCatalogue-product components only. Echo this into\nfactory.api.v1.salesdocuments.KitComponent.product_name when building a\nsales-document kit line from this template — the backend stores component\nnames as given and does not derive them from product references.",
            "maxLength": 150
          }
        },
        "description": "One component within a kit or sub-assembly — the default contents of a kit. A\ncomponent is typed by product_type: by default a catalogue product, but it\nmay instead be a labour or notes entry. The same component may appear under\nmore than one sub-assembly."
      },
      "KitRow": {
        "type": "object",
        "properties": {
          "kitRowId": {
            "type": "string",
            "description": "The kit variant row's unique id.",
            "pattern": "^kitrow_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "attributes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RowAttribute"
            },
            "description": "Attribute pairs that identify this kit variant."
          },
          "markedUpPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The computed marked-up sell price, for reference."
          },
          "prices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RowPrice"
            },
            "description": "The variant's price at each of your price levels."
          }
        },
        "description": "One priced variant of a product kit, identified by its attributes."
      },
      "KitSubAssembly": {
        "type": "object",
        "properties": {
          "subKitId": {
            "type": "string",
            "description": "The sub-assembly's unique id.",
            "pattern": "^subkit_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "title": {
            "type": "string",
            "description": "The sub-assembly's display title.",
            "maxLength": 150
          },
          "index": {
            "type": "integer",
            "format": "int32",
            "description": "The sub-assembly's position in display order within the kit."
          },
          "components": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KitProductComponent"
            },
            "description": "The component products that make up this sub-assembly."
          }
        },
        "description": "A sub-assembly within a product kit, holding its own component products. A\nsub-assembly is nested one level deep and cannot itself contain\nsub-assemblies."
      },
      "ListFlashingsResponse": {
        "type": "object",
        "properties": {
          "flashings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Flashing"
            },
            "description": "The flashing templates in this page."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are no\nmore results."
          }
        },
        "description": "A page of flashing templates."
      },
      "ListKitsResponse": {
        "type": "object",
        "properties": {
          "kits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Kit"
            },
            "description": "The kits in this page."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are no\nmore results."
          }
        },
        "description": "A page of product kits."
      },
      "ListProductsResponse": {
        "type": "object",
        "properties": {
          "products": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Product"
            },
            "description": "The products in this page."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are no\nmore results."
          }
        },
        "description": "A page of catalogue products."
      },
      "Product": {
        "type": "object",
        "properties": {
          "productId": {
            "type": "string",
            "description": "The product's unique id. Use this to reference the product on a line item.",
            "pattern": "^prod_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "name": {
            "type": "string",
            "description": "The product's display name. Not guaranteed unique; not a lookup key.",
            "maxLength": 1000
          },
          "categoryId": {
            "type": "string",
            "description": "The id of the category this product belongs to.",
            "pattern": "^category_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "categoryName": {
            "type": "string",
            "description": "The display name of the product's category.",
            "maxLength": 150
          },
          "pricingStrategy": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PricingStrategy"
              }
            ],
            "description": "How this product's quantity is interpreted for pricing."
          },
          "isTaxFree": {
            "type": "boolean",
            "description": "Whether this product is exempt from tax."
          },
          "rows": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProductRow"
            },
            "description": "The product's priced variant rows."
          },
          "isInventoryTracked": {
            "type": "boolean",
            "description": "Whether this product participates in inventory tracking. When true,\nstock levels for its rows are available from the Inventory API."
          }
        },
        "description": "A catalogue product you can order. Identified by its id; the name is not\nguaranteed to be unique, so always resolve by id."
      },
      "ProductRow": {
        "type": "object",
        "properties": {
          "productRowId": {
            "type": "string",
            "description": "The variant row's unique id. Reference this on a catalogue line item.",
            "pattern": "^prodrow_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "colour": {
            "type": "string",
            "description": "The variant's own colour. Usually empty — colour is normally chosen per line\nfrom `colourOptions` rather than baked into the row.",
            "maxLength": 100
          },
          "thickness": {
            "type": "string",
            "description": "The variant's thickness.",
            "maxLength": 30
          },
          "attributes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RowAttribute"
            },
            "description": "Additional attribute pairs that identify this variant, e.g.\n[{name:\"Size\", value:\"A4\"}]."
          },
          "markedUpPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The computed marked-up sell price, for reference."
          },
          "prices": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RowPrice"
            },
            "description": "The variant's price at each of your price levels."
          },
          "colourOptions": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 100
            },
            "description": "The colours a catalogue line built on this row may use, when the product's\nmaterial offers a colour selection. A line's colour must be one of these.\nThey come from the row's material, so every row sharing that material offers\nthe same colours; the list is empty when the product has no colour choice.\nColour is the per-line choice — the attributes above identify the variant."
          }
        },
        "description": "One priced variant of a product, identified by attributes such as thickness\nand size. Colour is usually not a variant attribute but a per-line choice —\nsee `colourOptions` for the colours a line built on this row may use."
      },
      "RowAttribute": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "The attribute name, e.g. \"Size\".",
            "maxLength": 150
          },
          "value": {
            "type": "string",
            "description": "The attribute value, e.g. \"A4\".",
            "maxLength": 255
          }
        },
        "description": "A single attribute name/value pair describing a product variant, e.g.\n{name:\"Size\", value:\"A4\"}."
      },
      "RowPrice": {
        "type": "object",
        "properties": {
          "priceLevelId": {
            "type": "string",
            "description": "The id of the price level (the level's own identity).",
            "pattern": "^pricelvl_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "priceLevelName": {
            "type": "string",
            "description": "The price level's display name, e.g. \"Account\" or \"Standard\".",
            "maxLength": 150
          },
          "price": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The price for this variant at this level."
          },
          "rowPriceId": {
            "type": "string",
            "description": "The id to reference on a catalogue line item to select this variant at this\nprice level.",
            "pattern": "^rowprice_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          }
        },
        "description": "A product variant's price at one of your price levels.\n\nCarries two ids: `priceLevelId` identifies the price level itself, while\n`rowPriceId` identifies this specific row-at-this-level entry. A catalogue\nline item references `rowPriceId`; the two use different id prefixes, so\nsending the wrong one is rejected."
      },
      "Company": {
        "type": "object",
        "properties": {
          "companyId": {
            "type": "string",
            "description": "The company's unique id.",
            "readOnly": true,
            "pattern": "^company_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "name": {
            "type": "string",
            "description": "The company's display name.",
            "maxLength": 150
          },
          "domain": {
            "type": "string",
            "description": "The company's Factory domain.",
            "maxLength": 50
          },
          "countryCode": {
            "type": "string",
            "description": "The company's country code, as an uppercase ISO 3166-1 alpha-2 code\n(e.g. \"AU\"). Empty when not set.",
            "pattern": "^[A-Z]{2}$"
          },
          "timeZone": {
            "type": "string",
            "description": "The company's IANA time zone name (e.g. \"Australia/Sydney\").",
            "maxLength": 100
          },
          "currency": {
            "type": "string",
            "description": "The ISO 4217 currency code all monetary amounts use. Currently one of\n\"AUD\", \"CAD\", \"NZD\", \"USD\", \"GBP\", or \"ZAR\"; the set may grow.",
            "maxLength": 5
          },
          "currencySymbol": {
            "type": "string",
            "description": "The currency's display symbol (e.g. \"$\").",
            "maxLength": 8
          },
          "measurementSystem": {
            "allOf": [
              {
                "$ref": "#/components/schemas/MeasurementSystem"
              }
            ],
            "description": "The measurement system quantities and dimensions are expressed in."
          },
          "taxRate": {
            "type": "string",
            "description": "The tax rate applied to the company's sales documents, as a decimal\nfraction (e.g. \"0.10\" means 10%). The server computes document tax with\nthis rate; it cannot be set per document.",
            "maxLength": 32
          },
          "taxIdentifier": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxIdentifier"
              }
            ],
            "description": "The company's tax identifier, if set. On read the scheme is ABN for\nAustralian accounts."
          },
          "email": {
            "type": "string",
            "description": "The company's contact email address.",
            "maxLength": 150
          },
          "phone": {
            "type": "string",
            "description": "The company's contact phone number.",
            "maxLength": 50
          },
          "website": {
            "type": "string",
            "description": "The company's website URL.",
            "maxLength": 100
          },
          "address": {
            "type": "string",
            "description": "The company's street address (first line).",
            "maxLength": 150
          },
          "address2": {
            "type": "string",
            "description": "The company's street address (second line).",
            "maxLength": 150
          },
          "city": {
            "type": "string",
            "description": "The company's city or suburb.",
            "maxLength": 150
          },
          "state": {
            "type": "string",
            "description": "The company's state or region code (e.g. \"VIC\").",
            "maxLength": 3
          },
          "postalCode": {
            "type": "string",
            "description": "The company's postal code (ZIP code, postcode).",
            "maxLength": 10
          },
          "logoUrl": {
            "type": "string",
            "description": "A URL to the company's logo image, if one is set. Served from Factory's\nmedia CDN; the URL is stable and safe to cache.",
            "maxLength": 2000
          }
        },
        "description": "A company's settings and configuration."
      },
      "CustomFieldDefinition": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "The field's key — the identifier write endpoints accept.",
            "readOnly": true,
            "maxLength": 150
          },
          "name": {
            "type": "string",
            "description": "The field's display name.",
            "maxLength": 150
          },
          "module": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CustomFieldModule"
              }
            ],
            "description": "Which record type the field is defined for."
          },
          "type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CustomFieldType"
              }
            ],
            "description": "The field's value type."
          },
          "options": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomFieldOption"
            },
            "description": "The permitted choices, for select and multi-select fields. A written\nvalue must be an option `key` (multi-select: a list of option keys)."
          },
          "readOnly": {
            "type": "boolean",
            "description": "The field's value is computed and cannot be written."
          },
          "isHidden": {
            "type": "boolean",
            "description": "Whether the field is hidden in the Factory app. Hidden fields are still\nreturned here so values on existing documents can be interpreted."
          }
        },
        "description": "One account-configured custom field definition."
      },
      "CustomFieldModule": {
        "type": "string",
        "enum": [
          "CUSTOM_FIELD_MODULE_UNSPECIFIED",
          "CUSTOM_FIELD_MODULE_SALES_DOCUMENTS",
          "CUSTOM_FIELD_MODULE_CUSTOMERS",
          "CUSTOM_FIELD_MODULE_SUPPLIERS",
          "CUSTOM_FIELD_MODULE_PURCHASE_ORDERS"
        ],
        "default": "CUSTOM_FIELD_MODULE_UNSPECIFIED",
        "description": "The record type a custom field is defined for.\n\n - CUSTOM_FIELD_MODULE_SALES_DOCUMENTS: Quotes and orders share one set of sales-document fields."
      },
      "CustomFieldOption": {
        "type": "object",
        "properties": {
          "key": {
            "type": "string",
            "description": "The option's key — what a written value must contain.",
            "maxLength": 150
          },
          "label": {
            "type": "string",
            "description": "The option's display label.",
            "maxLength": 150
          },
          "isDefault": {
            "type": "boolean",
            "description": "Whether this option is the field's default."
          }
        },
        "description": "One permitted choice of a select or multi-select custom field."
      },
      "CustomFieldType": {
        "type": "string",
        "enum": [
          "CUSTOM_FIELD_TYPE_UNSPECIFIED",
          "CUSTOM_FIELD_TYPE_TEXT",
          "CUSTOM_FIELD_TYPE_NUMBER",
          "CUSTOM_FIELD_TYPE_CURRENCY",
          "CUSTOM_FIELD_TYPE_DATE",
          "CUSTOM_FIELD_TYPE_DATETIME",
          "CUSTOM_FIELD_TYPE_SELECT",
          "CUSTOM_FIELD_TYPE_CHECKBOX",
          "CUSTOM_FIELD_TYPE_MULTISELECT",
          "CUSTOM_FIELD_TYPE_FORMULA",
          "CUSTOM_FIELD_TYPE_RATING",
          "CUSTOM_FIELD_TYPE_EMAIL",
          "CUSTOM_FIELD_TYPE_PHONE",
          "CUSTOM_FIELD_TYPE_URL",
          "CUSTOM_FIELD_TYPE_FILE"
        ],
        "default": "CUSTOM_FIELD_TYPE_UNSPECIFIED",
        "description": "A custom field's value type."
      },
      "GetCompanyResponse": {
        "type": "object",
        "properties": {
          "company": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Company"
              }
            ],
            "description": "Your company's settings."
          }
        }
      },
      "ListCompanyCustomFieldsResponse": {
        "type": "object",
        "properties": {
          "customFields": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomFieldDefinition"
            },
            "description": "The company's custom field definitions."
          }
        }
      },
      "MeasurementSystem": {
        "type": "string",
        "enum": [
          "MEASUREMENT_SYSTEM_UNSPECIFIED",
          "MEASUREMENT_SYSTEM_METRIC",
          "MEASUREMENT_SYSTEM_IMPERIAL",
          "MEASUREMENT_SYSTEM_BOTH"
        ],
        "default": "MEASUREMENT_SYSTEM_UNSPECIFIED",
        "description": "The measurement system a company's quantities are expressed in.\n\n - MEASUREMENT_SYSTEM_BOTH: The company works in both systems; each document line's pricing strategy\ndeclares its own unit."
      },
      "CreateCustomerRequest": {
        "type": "object",
        "properties": {
          "companyName": {
            "type": "string",
            "description": "The customer's company name. Required, and must be unique within your company\n(matched case-insensitively); a duplicate is rejected.",
            "maxLength": 150,
            "minLength": 1
          },
          "taxIdentifier": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxIdentifier"
              }
            ],
            "description": "The customer's tax identifier. Optional. In v1 the only accepted scheme is\nABN (Australian Business Number); supplying any other scheme is rejected."
          },
          "email": {
            "type": "string",
            "description": "The customer's email address. Required; must be a valid email address.",
            "maxLength": 100,
            "minLength": 1
          },
          "phone": {
            "type": "string",
            "description": "The customer's phone number.",
            "maxLength": 50
          },
          "billingAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "The customer's billing address."
          },
          "deliveryAddresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Address"
            },
            "description": "The customer's delivery addresses."
          },
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerContact"
            },
            "description": "The customer's contacts. Each contact's name is required; its id is assigned by\nthe server, so omit it on create."
          },
          "requestId": {
            "type": "string",
            "description": "Idempotency key: an opaque, client-generated UUID, validated but not yet\ndeduplicated (dedupe requires backend storage that is still landing). A\nretried create cannot double-create — the duplicate company-name gate\nrejects it; recover by listing with the company_name filter. Omit it for\nno idempotency intent.",
            "format": "uuid"
          },
          "fields": {
            "type": "string",
            "description": "Comma-separated response fields to include, using camelCase JSON names\n(e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects\nand map transparently across arrays. Paths are relative to the resource,\nnot the response envelope; envelope keys are always preserved. Only 2xx\nJSON responses are filtered; error bodies pass through unmodified. Unknown\nnames are silently ignored. Takes precedence over `excludeFields` when\nboth are provided.",
            "maxLength": 2000
          },
          "excludeFields": {
            "type": "string",
            "description": "Comma-separated response fields to exclude, using camelCase JSON names.\nDot paths and array-transparency work the same as `fields`. Paths are\nrelative to the resource, not the response envelope. Only 2xx JSON\nresponses are filtered; error bodies pass through unmodified. Ignored\nwhen `fields` is also provided.",
            "maxLength": 2000
          }
        },
        "description": "Input for creating a customer: company details, addresses, and contacts. Company\nname and email are required; a tax identifier is optional.",
        "required": [
          "companyName",
          "email"
        ]
      },
      "CreateCustomerResponse": {
        "type": "object",
        "properties": {
          "customer": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Customer"
              }
            ],
            "description": "The created customer."
          }
        },
        "description": "Result of creating a customer: the created customer with server-assigned fields."
      },
      "Customer": {
        "type": "object",
        "properties": {
          "customerId": {
            "type": "string",
            "description": "The customer's unique id.",
            "pattern": "^cust_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "companyName": {
            "type": "string",
            "description": "The customer's company name. Unique within your company and matched\ncase-insensitively when resolving.",
            "maxLength": 150
          },
          "taxIdentifier": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxIdentifier"
              }
            ],
            "description": "The customer's tax identifier, if set. Not required to be unique. On read the\nscheme is ABN for existing (Australian) customers."
          },
          "email": {
            "type": "string",
            "description": "The customer's email address, if set.",
            "maxLength": 100
          },
          "phone": {
            "type": "string",
            "description": "The customer's phone number.",
            "maxLength": 50
          },
          "defaultPriceLevelId": {
            "type": "string",
            "description": "The customer's default price level, by id. Read reference only; line\npricing is supplied by the caller on each order line.",
            "pattern": "^pricelvl_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "defaultPriceLevelName": {
            "type": "string",
            "description": "The name of the customer's default price level.",
            "maxLength": 150
          },
          "billingAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "The customer's billing address."
          },
          "deliveryAddresses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Address"
            },
            "description": "The customer's delivery addresses."
          },
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CustomerContact"
            },
            "description": "The customer's contacts."
          },
          "isOnCreditHold": {
            "type": "boolean",
            "description": "Whether the customer is currently on credit hold."
          },
          "disableTaxByDefault": {
            "type": "boolean",
            "description": "Whether tax is disabled by default for this customer."
          },
          "lastOrderAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the customer most recently placed an order. Populated on ListCustomers\nentries; not currently returned by GetCustomer."
          }
        },
        "description": "A customer belonging to your company."
      },
      "CustomerContact": {
        "type": "object",
        "properties": {
          "contactId": {
            "type": "string",
            "description": "The contact's unique id.",
            "pattern": "^contact_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "name": {
            "type": "string",
            "description": "The contact's name.",
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "description": "The contact's email address.",
            "maxLength": 100
          },
          "phone": {
            "type": "string",
            "description": "The contact's phone number.",
            "maxLength": 50
          },
          "mobile": {
            "type": "string",
            "description": "The contact's mobile number.",
            "maxLength": 16
          }
        },
        "description": "A contact person associated with a customer."
      },
      "GetCustomerResponse": {
        "type": "object",
        "properties": {
          "customer": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Customer"
              }
            ],
            "description": "The requested customer."
          }
        },
        "description": "Result of retrieving a single customer."
      },
      "ListCustomersResponse": {
        "type": "object",
        "properties": {
          "customers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Customer"
            },
            "description": "The customers in this page."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are no\nmore results."
          }
        },
        "description": "A page of customers."
      },
      "GetProductInventoryResponse": {
        "type": "object",
        "properties": {
          "productName": {
            "type": "string",
            "description": "The product's display name.",
            "readOnly": true,
            "maxLength": 1000
          },
          "isColourTracked": {
            "type": "boolean",
            "description": "Whether the product is colour-tracked. When true, entries are split by\ncolour; when false, there is one entry per row.",
            "readOnly": true
          },
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StockEntry"
            },
            "description": "The stock entries for this product — one per row per colour."
          }
        },
        "description": "Result of retrieving inventory for a single product."
      },
      "SetStockLevelsBody": {
        "type": "object",
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StockLevelInput"
            },
            "description": "The stock levels to set. At least one entry is required. Each identifies\na product row and optional colour, with the new on-hand quantity.\nEntries you omit are left unchanged.",
            "minItems": 1
          }
        },
        "description": "Input for setting on-hand stock levels on a tracked product.",
        "required": [
          "entries"
        ]
      },
      "ListInventoryResponse": {
        "type": "object",
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StockEntry"
            },
            "description": "The stock entries in this page."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are\nno more results."
          }
        },
        "description": "A page of stock entries."
      },
      "SetStockLevelsResponse": {
        "type": "object",
        "properties": {
          "entries": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/StockEntry"
            },
            "description": "The stock entries for the product after the update — all entries,\nincluding those not changed by this call, with recomputed promised,\non-the-way, and available."
          }
        },
        "description": "Result of setting stock levels: the product's complete stock position."
      },
      "StockEntry": {
        "type": "object",
        "properties": {
          "productId": {
            "type": "string",
            "description": "The catalogue product this entry belongs to.",
            "readOnly": true,
            "pattern": "^prod_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productName": {
            "type": "string",
            "description": "The product's display name.",
            "readOnly": true,
            "maxLength": 1000
          },
          "productRowId": {
            "type": "string",
            "description": "The product variant row this entry is for — the same id the catalogue\nproduct's rows carry. Look the row up there when you need more than\nthis entry shows, such as the variant's prices or colour options.",
            "readOnly": true,
            "pattern": "^prodrow_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "colour": {
            "type": "string",
            "description": "The colour this entry is for. Empty when the product is not\ncolour-tracked — in that case there is one entry per row.",
            "readOnly": true,
            "maxLength": 100
          },
          "thickness": {
            "type": "string",
            "description": "The variant's thickness, when its catalogue row carries one. Empty\notherwise.",
            "readOnly": true,
            "maxLength": 30
          },
          "attributes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RowAttribute"
            },
            "description": "The attribute pairs identifying this variant, e.g.\n[{name:\"Size\", value:\"A4\"}] — the same attributes the catalogue\nproduct's row carries. Together with thickness and colour they label\nthe entry; a single-row product has no attributes to distinguish, so\nthe list may be empty.",
            "readOnly": true
          },
          "onHand": {
            "type": "string",
            "description": "Quantity currently in stock (on hand). The only directly maintained\nvalue; the others are derived from it and from order/purchase data.",
            "readOnly": true,
            "maxLength": 32
          },
          "promised": {
            "type": "string",
            "description": "Quantity committed to submitted orders that have not yet been received\nby the customer. Computed from open order line items.",
            "readOnly": true,
            "maxLength": 32
          },
          "onTheWay": {
            "type": "string",
            "description": "Quantity on submitted purchase orders that have not yet arrived.\nComputed from open purchase order items.",
            "readOnly": true,
            "maxLength": 32
          },
          "available": {
            "type": "string",
            "description": "Quantity available for new orders: on_hand minus promised. Can be\nnegative when more stock is committed than is on hand.",
            "readOnly": true,
            "maxLength": 32
          }
        },
        "description": "One stock entry: the inventory for a single product variant row at a\nsingle colour (or colourless) — the atomic unit of inventory in the\nsystem. Quantities are for that one row+colour combination alone; the API\nnever reports cumulative product-level totals. Sum a product's entries to\ncompute them. Each entry carries its variant's identifying attributes\n(thickness and name/value pairs), so entries are tellable apart without a\ncatalogue lookup."
      },
      "StockLevelInput": {
        "type": "object",
        "properties": {
          "productRowId": {
            "type": "string",
            "description": "The product variant row to set stock for. Must belong to the product\nidentified in the request.",
            "pattern": "^prodrow_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "colour": {
            "type": "string",
            "description": "The colour to set stock for. Required for colour-tracked products (one\nof the row's colour_options); leave empty for non-colour-tracked products.",
            "maxLength": 100
          },
          "onHand": {
            "type": "string",
            "description": "The on-hand quantity to record, as a decimal string. Replaces the\ncurrent value.",
            "maxLength": 32
          }
        },
        "description": "A stock level to set: identifies a product row and optional colour, and\nthe on-hand quantity to record.",
        "required": [
          "productRowId",
          "onHand"
        ]
      },
      "AddQuoteLineItemResponse": {
        "type": "object",
        "properties": {
          "quote": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Quote"
              }
            ],
            "description": "The updated quote, including the recomputed totals."
          },
          "drawings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DrawingIdMapping"
            },
            "description": "When the added line was a flashing line: the server identities of its\ncreated drawings (your temp_id paired with the assigned drawing_id, the\nother sides in the order you sent them) — use them to address\nUploadDrawingSvg. Empty for every other line kind."
          }
        },
        "description": "Result of adding a line item to a quote: the quote with recomputed totals."
      },
      "CreateQuoteRequest": {
        "type": "object",
        "properties": {
          "customerId": {
            "type": "string",
            "description": "The customer's unique id, as returned by the Customers API. Exactly one of\n`customerId` or `companyName` must be set.",
            "pattern": "^cust_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "companyName": {
            "type": "string",
            "description": "The customer's company name, matched case-insensitively within your company.\nExactly one of `customerId` or `companyName` must be set.",
            "maxLength": 150
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SalesLine"
            },
            "description": "The quote's line items. Each line is one of the supported line types\n(on-the-fly, catalogue, labour, notes, flashing, or product kit)."
          },
          "adjustments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Adjustment"
            },
            "description": "Quote-level fees, discounts, and markups applied across the whole quote."
          },
          "isSubmitted": {
            "type": "boolean",
            "description": "Finalise and send the quote immediately (true) or save it as a draft (false)."
          },
          "fulfilmentMethod": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FulfilmentMethod"
              }
            ],
            "description": "How the customer would receive the order if the quote is accepted: pickup,\ndelivery, or installation. Determines which address below applies."
          },
          "billingAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Billing address for the quote."
          },
          "deliveryAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Delivery address. Used only when fulfilment_method is DELIVERY."
          },
          "installAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Installation address. Used only when fulfilment_method is INSTALL."
          },
          "reference": {
            "type": "string",
            "description": "Free-text external reference for this quote — e.g. the quote id from your own\nsystem. Stored for lookup and audit; distinct from `requestId`.",
            "maxLength": 1000
          },
          "purchaseOrder": {
            "type": "string",
            "format": "int64",
            "description": "The purchase order (PO) number for this quote — your reference number for the\nwork being requested. In this version the server assigns the next available\nnumber for your company; a value you supply here is not yet applied on\ncreate.",
            "minimum": 0
          },
          "customFields": {
            "type": "object",
            "description": "Values for your account's custom-defined sales-document fields, keyed by\neach field's configured key. Unknown keys, keys of other modules, and\nvalues that don't match the field's type are rejected. Send number and\ncurrency values as numbers or decimal strings, checkboxes as booleans,\nmulti-selects as arrays of strings, and everything else as strings.\nSelect and multi-select values must be option keys from the field's\ndefinition, never display labels; discover your account's field keys and\noptions with GET /v1/company/custom-fields. Formula fields are computed\nand cannot be written."
          },
          "requiredAt": {
            "type": "string",
            "format": "date-time",
            "description": "The date the customer needs the order by, if the quote is accepted."
          },
          "contact": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Contact"
              }
            ],
            "description": "The point-of-contact person for this quote, distinct from the customer."
          },
          "notes": {
            "type": "string",
            "description": "Free-text notes about the quote.",
            "maxLength": 500
          },
          "quotedAt": {
            "type": "string",
            "format": "date-time",
            "description": "The date the quote was issued. If omitted, the server sets it."
          },
          "tax": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxDetail"
              }
            ],
            "description": "The tax to apply, as a rate/code/jurisdiction descriptor. Optional and\nadvisory in this version: the server applies your account's configured tax\nsettings and echoes the values actually applied (`Quote.tax`,\n`Quote.tax_amount`) — compare them to detect an override."
          },
          "requestId": {
            "type": "string",
            "description": "Idempotency key for safe retries: an opaque, client-generated UUID. If a\nprevious create with the same key already succeeded, the server returns that\noriginal quote instead of creating a duplicate; reusing the same key with a\nmaterially different body is rejected with HTTP 409 (Aborted). Keys are\nretained for at least 24 hours; after that, a reused key may create a new\nquote. Omit it for no idempotency guarantee. Distinct from `reference`.",
            "format": "uuid"
          },
          "labelIds": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^label_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            },
            "description": "Labels to attach to the quote as part of creating it, by id. Every id\nmust exist in your company's label set (discover them with\nGET /v1/company/labels): an unknown id fails the whole call with HTTP\n404 before the quote is created. Labelling happens with the create but\nnot atomically inside it — in the rare case the quote is created and\nlabelling then fails, the error names the created quote id and the quote\nexists WITHOUT labels; attach them with SetQuoteLabels. The created\nquote echoes the attached labels. Duplicate ids are rejected."
          },
          "fields": {
            "type": "string",
            "description": "Comma-separated response fields to include, using camelCase JSON names\n(e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects\nand map transparently across arrays. Paths are relative to the resource,\nnot the response envelope; envelope keys are always preserved. Only 2xx\nJSON responses are filtered; error bodies pass through unmodified. Unknown\nnames are silently ignored. Takes precedence over `excludeFields` when\nboth are provided.",
            "maxLength": 2000
          },
          "excludeFields": {
            "type": "string",
            "description": "Comma-separated response fields to exclude, using camelCase JSON names.\nDot paths and array-transparency work the same as `fields`. Paths are\nrelative to the resource, not the response envelope. Only 2xx JSON\nresponses are filtered; error bodies pass through unmodified. Ignored\nwhen `fields` is also provided.",
            "maxLength": 2000
          }
        },
        "description": "Input for creating a quote: the customer, line items, adjustments, addresses,\nand draft/sent option. Quote totals are derived by the server, not supplied\nhere."
      },
      "CreateQuoteResponse": {
        "type": "object",
        "properties": {
          "quote": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Quote"
              }
            ],
            "description": "The created quote, including server-set fields and assigned identifiers."
          },
          "drawings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DrawingIdMapping"
            },
            "description": "The server identities of the drawings created by this call, one entry\nper flashing line in the order you sent them, each pairing your temp_id\nwith the assigned drawing_id — use them to address UploadDrawingSvg.\nEmpty when the quote had no flashing lines."
          }
        },
        "description": "Result of creating a quote: the created quote with all server-assigned fields\npopulated."
      },
      "DeleteQuoteLineItemResponse": {
        "type": "object",
        "properties": {
          "quote": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Quote"
              }
            ],
            "description": "The updated quote, with the line removed and totals recomputed."
          }
        },
        "description": "Result of removing a line item: the quote with recomputed totals."
      },
      "GetQuoteResponse": {
        "type": "object",
        "properties": {
          "quote": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Quote"
              }
            ],
            "description": "The requested quote."
          }
        },
        "description": "Result of retrieving a single quote."
      },
      "ListQuotesResponse": {
        "type": "object",
        "properties": {
          "quotes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Quote"
            },
            "description": "The quotes in this page."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are no\nmore results."
          }
        },
        "description": "A page of quotes."
      },
      "QueryQuotesResponse": {
        "type": "object",
        "properties": {
          "quotes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Quote"
            },
            "description": "The quotes in this page, oldest change first. Use the last quote's update\ntime as the updated_since for your next call."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are no\nmore results."
          }
        },
        "description": "A page of quotes from the change feed, ordered by when each quote last changed\n(oldest first). The lower bound (updated_since) is inclusive, so the quote you\ncheckpoint on reappears as the first item of the next page — dedupe on quote_id\n+ last_updated_at, or skip ids you have already seen at the window boundary."
      },
      "AddQuoteLineItemBody": {
        "type": "object",
        "properties": {
          "line": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SalesLine"
              }
            ],
            "description": "The line item to add: one of the supported line types (on-the-fly,\ncatalogue, labour, notes, or product kit)."
          },
          "requestId": {
            "type": "string",
            "description": "An optional idempotency key. A repeat with the same key returns the original\nresult rather than adding the line twice; scoped to your company.",
            "format": "uuid"
          },
          "fields": {
            "type": "string",
            "description": "Comma-separated response fields to include, using camelCase JSON names\n(e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects\nand map transparently across arrays. Paths are relative to the resource,\nnot the response envelope; envelope keys are always preserved. Only 2xx\nJSON responses are filtered; error bodies pass through unmodified. Unknown\nnames are silently ignored. Takes precedence over `excludeFields` when\nboth are provided.",
            "maxLength": 2000
          },
          "excludeFields": {
            "type": "string",
            "description": "Comma-separated response fields to exclude, using camelCase JSON names.\nDot paths and array-transparency work the same as `fields`. Paths are\nrelative to the resource, not the response envelope. Only 2xx JSON\nresponses are filtered; error bodies pass through unmodified. Ignored\nwhen `fields` is also provided.",
            "maxLength": 2000
          }
        },
        "description": "Input for adding a single line item to an existing quote (build a quote up\nover time, one line per call).",
        "required": [
          "line"
        ]
      },
      "SetQuoteLabelsBody": {
        "type": "object",
        "properties": {
          "labelIds": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^label_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            },
            "description": "The quote's desired complete label set. An empty list removes every\nlabel. Duplicate ids are rejected."
          },
          "fields": {
            "type": "string",
            "description": "Comma-separated response fields to include, using camelCase JSON names\n(e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects\nand map transparently across arrays. Paths are relative to the resource,\nnot the response envelope; envelope keys are always preserved. Only 2xx\nJSON responses are filtered; error bodies pass through unmodified. Unknown\nnames are silently ignored. Takes precedence over `excludeFields` when\nboth are provided.",
            "maxLength": 2000
          },
          "excludeFields": {
            "type": "string",
            "description": "Comma-separated response fields to exclude, using camelCase JSON names.\nDot paths and array-transparency work the same as `fields`. Paths are\nrelative to the resource, not the response envelope. Only 2xx JSON\nresponses are filtered; error bodies pass through unmodified. Ignored\nwhen `fields` is also provided.",
            "maxLength": 2000
          }
        },
        "description": "Input for replacing the labels on a quote."
      },
      "QuoteServiceUploadDrawingSvgBody": {
        "type": "object",
        "properties": {
          "svgs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DrawingSvg"
            },
            "description": "The drawings and their rendered SVGs. At least one is required.",
            "minItems": 1
          },
          "fields": {
            "type": "string",
            "description": "Comma-separated response fields to include, using camelCase JSON names\n(e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects\nand map transparently across arrays. Paths are relative to the resource,\nnot the response envelope; envelope keys are always preserved. Only 2xx\nJSON responses are filtered; error bodies pass through unmodified. Unknown\nnames are silently ignored. Takes precedence over `excludeFields` when\nboth are provided.",
            "maxLength": 2000
          },
          "excludeFields": {
            "type": "string",
            "description": "Comma-separated response fields to exclude, using camelCase JSON names.\nDot paths and array-transparency work the same as `fields`. Paths are\nrelative to the resource, not the response envelope. Only 2xx JSON\nresponses are filtered; error bodies pass through unmodified. Ignored\nwhen `fields` is also provided.",
            "maxLength": 2000
          }
        },
        "description": "Input for uploading rendered SVGs for a quote's flashing drawings.",
        "required": [
          "svgs"
        ]
      },
      "SetQuoteLabelsResponse": {
        "type": "object",
        "properties": {
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Label"
            },
            "description": "The quote's labels after the change, oldest attachment first."
          }
        },
        "description": "Result of replacing the labels on a quote: the resulting label set."
      },
      "UpdateQuoteLineItemResponse": {
        "type": "object",
        "properties": {
          "quote": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Quote"
              }
            ],
            "description": "The updated quote, including the recomputed totals."
          }
        },
        "description": "Result of replacing a line item: the quote with recomputed totals."
      },
      "quotesUploadDrawingSvgResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DrawingSvgResult"
            },
            "description": "One result per submitted drawing, in request order."
          }
        },
        "description": "Result of an SVG upload: one outcome per submitted drawing."
      },
      "Quote": {
        "type": "object",
        "properties": {
          "quoteId": {
            "type": "string",
            "description": "The quote's unique id. Read-only.",
            "readOnly": true,
            "pattern": "^quote_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "isSubmitted": {
            "type": "boolean",
            "description": "Whether the quote has been finalised and sent (true) or is still a draft\n(false)."
          },
          "customerId": {
            "type": "string",
            "description": "The id of the customer this quote belongs to. Always present. Read-only.",
            "readOnly": true,
            "pattern": "^cust_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "customerCompanyName": {
            "type": "string",
            "description": "The customer's company name. Read-only.",
            "readOnly": true,
            "maxLength": 150
          },
          "subtotal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Quote subtotal — the sum of line totals before tax. Derived by the server\nfrom the line items; read-only.",
            "readOnly": true
          },
          "taxAmount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total tax for the quote, as an absolute money amount (the tax `amount`, not a\nrate). Derived by the server from your account's tax settings; read-only. See\n`tax` for the rate, code, and jurisdiction that produced it.",
            "readOnly": true
          },
          "total": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Quote grand total. Derived by the server; read-only.",
            "readOnly": true
          },
          "margin": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Quote margin, computed by the server from the line items. Read-only.",
            "readOnly": true
          },
          "totalCost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total cost of the quote, computed by the server from the line items. Read-only.",
            "readOnly": true
          },
          "discountAmount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The quote-level discount total, rolled up from the per-line discounts and the\ndiscount entries in `adjustments`. Read-only.",
            "readOnly": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SalesLine"
            },
            "description": "The quote's line items."
          },
          "adjustments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Adjustment"
            },
            "description": "Quote-level fees, discounts, and markups. At most one fee per quote is allowed."
          },
          "fulfilmentMethod": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FulfilmentMethod"
              }
            ],
            "description": "How the customer would receive the order if the quote is accepted: pickup,\ndelivery, or installation."
          },
          "billingAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Billing address for the quote."
          },
          "deliveryAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Delivery address. Applies when fulfilment_method is DELIVERY."
          },
          "installAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Installation address. Applies when fulfilment_method is INSTALL."
          },
          "deliveryFee": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The delivery fee quoted on the order. Read-only.",
            "readOnly": true
          },
          "reference": {
            "type": "string",
            "description": "Free-text external reference, e.g. the quote id from your own system.",
            "maxLength": 1000
          },
          "purchaseOrder": {
            "type": "string",
            "format": "int64",
            "description": "The purchase order (PO) number for this quote — the reference number for the\nwork being requested. Assigned by the server when not supplied at creation."
          },
          "customFields": {
            "type": "object",
            "description": "The document's custom-field values, keyed by each field's configured key."
          },
          "contact": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Contact"
              }
            ],
            "description": "The quote's point-of-contact person."
          },
          "notes": {
            "type": "string",
            "description": "Free-text notes about the quote.",
            "maxLength": 500
          },
          "requiredAt": {
            "type": "string",
            "format": "date-time",
            "description": "The date the customer needs the order by, if the quote is accepted."
          },
          "quotedAt": {
            "type": "string",
            "format": "date-time",
            "description": "The date the quote was issued."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the quote was created. Read-only.",
            "readOnly": true
          },
          "submittedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the quote was sent (finalised). Read-only.",
            "readOnly": true
          },
          "lastUpdatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the quote was last updated. Read-only.",
            "readOnly": true
          },
          "createdByUserId": {
            "type": "string",
            "description": "The id of the user who created the quote, empty when unset. Resolve the user\nvia the Users API (which can return users who have since left). Read-only.",
            "readOnly": true,
            "pattern": "^user_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "submittedByUserId": {
            "type": "string",
            "description": "The id of the user who sent the quote, empty when unset. Resolve the user via\nthe Users API (which can return users who have since left). Read-only.",
            "readOnly": true,
            "pattern": "^user_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "tax": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxDetail"
              }
            ],
            "description": "The tax applied to the quote: rate, code, and jurisdiction. Reflects your\naccount's tax settings — the values actually applied — and is the companion\ndescriptor to the `taxAmount` amount. Read-only.",
            "readOnly": true
          },
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Label"
            },
            "description": "The labels attached to this quote, oldest attachment first. Present on\ncreate responses when label_ids are supplied; absent from line-write\nresponses.",
            "readOnly": true
          }
        },
        "description": "A quote, as returned by read endpoints. Includes the quote header, its line\nitems, adjustments, addresses, contact, and server-derived totals. The same\nshape is returned whether the quote was just created or fetched later."
      },
      "Adjustment": {
        "type": "object",
        "properties": {
          "type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AdjustmentType"
              }
            ],
            "description": "The kind of adjustment: a fee, discount, or markup. Required."
          },
          "title": {
            "type": "string",
            "description": "A label describing the adjustment.",
            "maxLength": 200
          },
          "amount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The adjustment as a fixed money amount. Exactly one of `amount` or\n`percent` must be set."
          },
          "percent": {
            "type": "string",
            "description": "The adjustment as a percentage (for example, \"10.00\"). Exactly one of\n`amount` or `percent` must be set.",
            "maxLength": 32
          }
        },
        "description": "A fee, discount, or markup applied across the whole order or quote.",
        "required": [
          "type"
        ]
      },
      "AdjustmentType": {
        "type": "string",
        "enum": [
          "ADJUSTMENT_TYPE_UNSPECIFIED",
          "ADJUSTMENT_TYPE_FEE",
          "ADJUSTMENT_TYPE_DISCOUNT",
          "ADJUSTMENT_TYPE_MARKUP"
        ],
        "default": "ADJUSTMENT_TYPE_UNSPECIFIED",
        "description": "The kind of order- or quote-level adjustment: a fee, a discount, or a markup.\n\n - ADJUSTMENT_TYPE_UNSPECIFIED: Default, unset value. Not a valid choice when setting an adjustment.\n - ADJUSTMENT_TYPE_FEE: A fee added to the order or quote. At most one fee is allowed per order or\nquote.\n - ADJUSTMENT_TYPE_DISCOUNT: A discount subtracted from the order or quote.\n - ADJUSTMENT_TYPE_MARKUP: A markup added to the order or quote."
      },
      "AngleCell": {
        "type": "object",
        "properties": {
          "angle": {
            "type": "number",
            "format": "double",
            "description": "The angle at this vertex, in degrees. Omitted when no angle is set.",
            "minimum": 0,
            "maximum": 360
          },
          "hidden": {
            "type": "boolean",
            "description": "Whether this vertex's angle label is hidden. Omitted when not set."
          }
        },
        "description": "The angle entry for a single point (vertex) in the drawing."
      },
      "AngleSet": {
        "type": "object",
        "properties": {
          "values": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/AngleCell"
            },
            "description": "The angle entry for each vertex, keyed by point id."
          },
          "positions": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/LabelPosition"
            },
            "description": "The label box for each vertex's angle label, keyed by point id."
          }
        },
        "description": "The collection of vertex angles and their label positions for a drawing."
      },
      "Arrow": {
        "type": "object",
        "properties": {
          "x": {
            "type": "number",
            "format": "double",
            "description": "The x coordinate of the arrow, in canvas space. Omitted until placed."
          },
          "y": {
            "type": "number",
            "format": "double",
            "description": "The y coordinate of the arrow, in canvas space. Omitted until placed."
          },
          "angle": {
            "type": "number",
            "format": "double",
            "description": "The direction the arrow points, in degrees.",
            "minimum": 0,
            "maximum": 360
          }
        },
        "description": "The arrow that indicates the front-facing direction of the flashing."
      },
      "CatalogueLine": {
        "type": "object",
        "properties": {
          "productId": {
            "type": "string",
            "description": "The id of the catalogue product for this line. Required, and must match an\nexisting catalogue product; an unknown id rejects the whole request.",
            "pattern": "^prod_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productRowId": {
            "type": "string",
            "description": "Optional id of a specific row within the catalogue product. If set, it must\nresolve.",
            "pattern": "^prodrow_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "rowPriceId": {
            "type": "string",
            "description": "Optional id selecting the row's price at one of your price levels — the\n`rowPriceId` returned by the catalogue (not the price level's own id).\nIf set, it must resolve.",
            "pattern": "^rowprice_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productName": {
            "type": "string",
            "description": "Display name for the line.",
            "maxLength": 1000
          },
          "productDescription": {
            "type": "string",
            "description": "Longer description for the line.",
            "maxLength": 4000
          },
          "categoryName": {
            "type": "string",
            "description": "Category name for the line.",
            "maxLength": 150
          },
          "priceName": {
            "type": "string",
            "description": "Price name shown for the line.",
            "maxLength": 150
          },
          "params": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Product parameters as a JSON array of {name, value} entries (for example\n[{\"name\": \"Colour\", \"value\": \"Monument\"}]). The colour is taken from the\n\"Colour\" parameter and validated against the product's available colours."
          },
          "attributes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Additional named attributes for the line, as a JSON array of {name, value}\nentries (for example {name: \"Size\", value: \"76x38mm\"}). An entry may also\ncarry flags that hide it from specific documents or views, such as the\ninvoice PDF or the customer view."
          },
          "pricing": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LinePricing"
              }
            ],
            "description": "Pricing for this line. The catalogue does not price the line; you supply it."
          }
        },
        "description": "A catalogue line: a line for a product from your catalogue. You provide the\ncatalogue product id and the line's pricing.",
        "required": [
          "productId"
        ]
      },
      "Contact": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string",
            "description": "The contact person's name.",
            "maxLength": 100
          },
          "email": {
            "type": "string",
            "description": "The contact's email address. Optional; when provided, it must be a valid\nemail address.",
            "maxLength": 100
          },
          "phone": {
            "type": "string",
            "description": "The contact's landline phone number.",
            "maxLength": 50
          },
          "mobile": {
            "type": "string",
            "description": "The contact's mobile number, normalized to E.164 format (e.g. +61400000000).",
            "maxLength": 16
          }
        },
        "description": "The person to contact about this order or quote. Belongs to the order or quote\nitself, separate from the customer and from any address."
      },
      "DrawingIdMapping": {
        "type": "object",
        "properties": {
          "tempId": {
            "type": "string",
            "description": "The temp_id you supplied on the drawing, echoed verbatim; empty when you\ndid not supply one (position in the list still identifies the drawing).",
            "maxLength": 64
          },
          "drawingId": {
            "type": "string",
            "description": "The server-assigned id of the drawing.",
            "pattern": "^drawing_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "otherSides": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DrawingIdMapping"
            },
            "description": "The identities of the drawing's other sides, in the order you sent them."
          }
        },
        "description": "The server identity assigned to one drawing you sent in a write, paired\nwith the temp_id you supplied. Returned by the writes that create drawings\n(CreateQuote, CreateOrder, AddQuoteLineItem) in the order you sent the\nflashing lines — use it to address UploadDrawingSvg without re-reading\nthe document."
      },
      "DrawingSide": {
        "type": "string",
        "enum": [
          "DRAWING_SIDE_UNSPECIFIED",
          "DRAWING_SIDE_FAR",
          "DRAWING_SIDE_NEAR"
        ],
        "default": "DRAWING_SIDE_UNSPECIFIED",
        "description": "Which face of the flashing a drawing represents. Defaults to the near side\nwhen not specified.\n\n - DRAWING_SIDE_UNSPECIFIED: Default, unset value.\n - DRAWING_SIDE_FAR: The far side of the flashing.\n - DRAWING_SIDE_NEAR: The near side of the flashing."
      },
      "DrawingSvg": {
        "type": "object",
        "properties": {
          "drawingId": {
            "type": "string",
            "description": "The id of the drawing this SVG belongs to. Required.",
            "pattern": "^drawing_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "svg": {
            "type": "string",
            "format": "byte",
            "description": "The rendered SVG image content, up to about 2 MB. Required."
          }
        },
        "description": "One drawing's rendered SVG image.",
        "required": [
          "drawingId",
          "svg"
        ]
      },
      "DrawingSvgResult": {
        "type": "object",
        "properties": {
          "drawingId": {
            "type": "string",
            "description": "The drawing this result refers to.",
            "readOnly": true,
            "pattern": "^drawing_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "uploaded": {
            "type": "boolean",
            "description": "Whether the SVG was stored successfully.",
            "readOnly": true
          },
          "svgUrl": {
            "type": "string",
            "description": "On success, a presigned URL to the stored SVG.",
            "readOnly": true
          },
          "error": {
            "type": "string",
            "description": "On failure, the reason: the drawing isn't part of this order or quote, isn't\nan image/svg+xml, or has been deleted.",
            "readOnly": true
          }
        },
        "description": "The outcome of uploading one drawing's SVG."
      },
      "Finish": {
        "type": "object",
        "properties": {
          "type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FinishType"
              }
            ],
            "description": "The kind of end treatment."
          },
          "size": {
            "type": "number",
            "format": "double",
            "description": "The size of the end treatment.",
            "minimum": 0,
            "exclusiveMinimum": true
          },
          "label": {
            "type": "string",
            "description": "The display label for this finish, e.g. \"cf10\".",
            "maxLength": 32
          },
          "flip": {
            "type": "boolean",
            "description": "Whether the finish is flipped to the other side of the edge."
          },
          "position": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LabelPosition"
              }
            ],
            "description": "Optional bounding box for this finish's label."
          }
        },
        "description": "An end treatment applied to a point on a flashing edge (for example a hook or\nfold). Optional on a point."
      },
      "FinishType": {
        "type": "string",
        "enum": [
          "FINISH_TYPE_UNSPECIFIED",
          "FINISH_TYPE_CRUSH_FOLD",
          "FINISH_TYPE_OPEN_HOOK",
          "FINISH_TYPE_FEATHER",
          "FINISH_TYPE_DRIP_EDGE"
        ],
        "default": "FINISH_TYPE_UNSPECIFIED",
        "description": "The end-treatment applied to a flashing edge.\n\n - FINISH_TYPE_UNSPECIFIED: Unspecified end treatment.\n - FINISH_TYPE_CRUSH_FOLD: A crush fold.\n - FINISH_TYPE_OPEN_HOOK: An open hook.\n - FINISH_TYPE_FEATHER: A feathered edge.\n - FINISH_TYPE_DRIP_EDGE: A drip edge."
      },
      "FlashingDrawing": {
        "type": "object",
        "properties": {
          "drawingId": {
            "type": "string",
            "description": "Server-assigned unique id of the drawing. Read-only output.",
            "readOnly": true,
            "pattern": "^drawing_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "tempId": {
            "type": "string",
            "description": "A client-assigned correlation id supplied when creating the drawing. The\nwrite that creates the drawing returns it paired with the assigned\ndrawing_id in the response's `drawings` mapping (CreateQuote,\nCreateOrder, AddQuoteLineItem) — use that to address UploadDrawingSvg.\nIt is not stored, so reads do not return it.",
            "maxLength": 64
          },
          "drawingNumber": {
            "type": "integer",
            "format": "int32",
            "description": "The drawing's display number within the order or quote.",
            "minimum": 0
          },
          "side": {
            "allOf": [
              {
                "$ref": "#/components/schemas/DrawingSide"
              }
            ],
            "description": "Which face of the flashing this drawing represents (far or near side)."
          },
          "isFreeDrawing": {
            "type": "boolean",
            "description": "Whether this is a free-form drawing."
          },
          "isLargeBoxSize": {
            "type": "boolean",
            "description": "Whether the drawing uses the large box size."
          },
          "isDeleted": {
            "type": "boolean",
            "description": "Whether the drawing has been deleted."
          },
          "points": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/Point"
            },
            "description": "The vertices of the drawing, keyed by point id. Required when the order or\nquote is submitted."
          },
          "lines": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LineSet"
              }
            ],
            "description": "The edge lengths between points, with their label positions."
          },
          "angles": {
            "allOf": [
              {
                "$ref": "#/components/schemas/AngleSet"
              }
            ],
            "description": "The vertex angles, with their label positions."
          },
          "annotations": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/TextBox"
            },
            "description": "Free-text annotations on the drawing, keyed by annotation id."
          },
          "frontArrow": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Arrow"
              }
            ],
            "description": "The arrow indicating the front-facing direction of the flashing."
          },
          "squareAngle": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SquareAngle"
              }
            ],
            "description": "The right-angle (90°) marker placed on the drawing."
          },
          "svgUrl": {
            "type": "string",
            "description": "A presigned URL to the rendered SVG image of the drawing. Read-only output;\npopulated after the SVG is uploaded via UploadDrawingSvg on OrderService or\nQuoteService.",
            "readOnly": true,
            "maxLength": 2000
          }
        },
        "description": "A flashing drawing: its identity, metadata, geometry, and the URL of its\nrendered SVG image. Geometry is sent in the same shape the drawing tool holds\nit."
      },
      "FlashingLine": {
        "type": "object",
        "properties": {
          "templateId": {
            "type": "string",
            "description": "The id of the flashing material or template. Required when the order or\nquote is submitted.",
            "pattern": "^flashtpl_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productName": {
            "type": "string",
            "description": "The display name of the flashing material.",
            "maxLength": 1000
          },
          "priceLevel": {
            "type": "string",
            "description": "The price level applied to this line. Required when the order or quote is\nsubmitted.",
            "maxLength": 150
          },
          "colour": {
            "type": "string",
            "description": "The flashing colour. Required when the order or quote is submitted;\nvalidated against the colours available for the flashing.",
            "maxLength": 100
          },
          "thickness": {
            "type": "string",
            "description": "The material thickness, as a decimal string in the template's units\n(millimetres for metric accounts). Required when the order or quote is\nsubmitted. Echoes the thickness of the selected template (template_id) —\neach thickness is a distinct template.",
            "maxLength": 30
          },
          "bends": {
            "type": "string",
            "description": "The number of bends in the flashing, as a decimal string. Required when the\norder or quote is submitted.",
            "maxLength": 32
          },
          "totalGirth": {
            "type": "string",
            "description": "The total girth of the flashing, as a decimal string. Required when the\norder or quote is submitted.",
            "maxLength": 32
          },
          "totalLength": {
            "type": "string",
            "description": "The total length of the flashing, as a decimal string.",
            "maxLength": 32
          },
          "unitPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The unit price of the flashing."
          },
          "totalPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The total price for this line."
          },
          "customPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "A custom price for the line, overriding the calculated value."
          },
          "customPricePerLength": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "A custom price per unit of measured length (for example, per lineal\nmetre), applied instead of the unit price when set."
          },
          "subitems": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "The per-bend / per-length geometry items that make up the flashing."
          },
          "isTaxFree": {
            "type": "boolean",
            "description": "Whether this line is exempt from tax."
          },
          "drawing": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FlashingDrawing"
              }
            ],
            "description": "The main drawing for the flashing."
          },
          "otherSides": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/FlashingDrawing"
            },
            "description": "Any additional side drawings for the flashing."
          },
          "tax": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxDetail"
              }
            ],
            "description": "The tax applied to this line: rate, code, and jurisdiction. Complements the\n`isTaxFree` flag. In v1 the server applies your account's tax settings to\ntaxable lines; this object reserves the shape for richer per-line tax later."
          }
        },
        "description": "A sheet-metal flashing line item: the flashing material, its specification,\npricing, and its drawings."
      },
      "FulfilmentMethod": {
        "type": "string",
        "enum": [
          "FULFILMENT_METHOD_UNSPECIFIED",
          "FULFILMENT_METHOD_PICKUP",
          "FULFILMENT_METHOD_DELIVERY",
          "FULFILMENT_METHOD_INSTALL"
        ],
        "default": "FULFILMENT_METHOD_UNSPECIFIED",
        "description": "How the customer receives the goods on an order or quote. Determines which\naddress is used: delivery uses the delivery address, installation uses the\ninstall address.\n\n - FULFILMENT_METHOD_UNSPECIFIED: Default, unset value.\n - FULFILMENT_METHOD_PICKUP: The customer collects the goods themselves.\n - FULFILMENT_METHOD_DELIVERY: The goods are delivered to the delivery address.\n - FULFILMENT_METHOD_INSTALL: The goods are installed at the install address."
      },
      "KitComponent": {
        "type": "object",
        "properties": {
          "productType": {
            "allOf": [
              {
                "$ref": "#/components/schemas/KitComponentType"
              }
            ],
            "description": "The component's kind: catalogue product, on-the-fly, labour, or notes.\nRequired — it determines which of the fields below apply."
          },
          "productId": {
            "type": "string",
            "description": "The catalogue product id for this component. Optional.",
            "pattern": "^prod_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productRowId": {
            "type": "string",
            "description": "The catalogue product table-row (variant) id. Optional.",
            "pattern": "^prodrow_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "kitProductId": {
            "type": "string",
            "description": "The catalogue kit-product-component id this component was instantiated from. Optional.",
            "pattern": "^kitcomp_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productName": {
            "type": "string",
            "description": "Display name of the component. Set it on every write: component names\nare stored verbatim and are NEVER derived from product_id or\nproduct_row_id — a component created without a name is stored nameless\nand every read of the document returns it with an empty name. When\nbuilding this line from a catalogue kit template, echo\nfactory.api.v1.catalogue.KitProductComponent.product_name (returned by\nGetKit) into this field.",
            "maxLength": 150
          },
          "productDescription": {
            "type": "string",
            "description": "Free-text description of the component.",
            "maxLength": 4000
          },
          "quantity": {
            "type": "string",
            "description": "Quantity for this component, as a decimal string.",
            "maxLength": 32
          },
          "baseQuantity": {
            "type": "string",
            "description": "Base quantity before any per-unit multipliers, as a decimal string.",
            "maxLength": 32
          },
          "unitPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Price per unit."
          },
          "totalPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total price for this component (unit price × quantity)."
          },
          "customPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Custom override price for the component."
          },
          "customPricePerLength": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "A custom price per unit of measured length (for example, per lineal\nmetre), applied instead of the unit price on measurement-priced\ncomponents."
          },
          "discount": {
            "type": "string",
            "description": "Discount applied to this component, as a decimal string.",
            "maxLength": 32
          },
          "cost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Cost of the component."
          },
          "markup": {
            "type": "string",
            "description": "Markup applied to the component, as a decimal string. Interpreted as a\npercentage when markup_is_percentage is true.",
            "maxLength": 32
          },
          "markupIsPercentage": {
            "type": "boolean",
            "description": "Whether markup is a percentage (true) or an absolute amount (false)."
          },
          "margin": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Margin earned on the component."
          },
          "pricingStrategy": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PricingStrategy"
              }
            ],
            "description": "How the component is priced."
          },
          "isTaxFree": {
            "type": "boolean",
            "description": "Whether this component is exempt from tax."
          },
          "measurementItems": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Measurement-pricing entries for lineal/square pricing."
          },
          "actualMeasurementItems": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Actual as-built measurements used for margin and stock figures."
          },
          "initialMeasurementItems": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Initial (as-quoted) measurements before adjustment."
          },
          "sumLengths": {
            "type": "string",
            "description": "Sum of the measured lengths, as a decimal string.",
            "maxLength": 32
          },
          "attributes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Additional named attributes for the component, as a JSON array of\n{name, value} entries; for measurement-priced components they describe the\nmeasurement entries."
          },
          "notes": {
            "type": "string",
            "description": "Free-text notes — used when the component is a notes line.",
            "maxLength": 4000
          },
          "notesType": {
            "allOf": [
              {
                "$ref": "#/components/schemas/NotesType"
              }
            ],
            "description": "The kind of notes line, when this is a notes component."
          },
          "labourUserId": {
            "type": "string",
            "description": "The company user assigned, when this is a labour component.",
            "pattern": "^user_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "colour": {
            "type": "string",
            "description": "Colour of the component, populated for catalogue components from the\nproduct row.",
            "maxLength": 100
          },
          "material": {
            "type": "string",
            "description": "Material of the component, populated for catalogue components from the\nproduct row.",
            "maxLength": 100
          },
          "colourOptions": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "The selectable options for this component's variant attribute — most often\ncolour, but also other attributes such as size — as a JSON array of\n{label, value} entries, echoed from the catalogue. Display and read only."
          },
          "tax": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxDetail"
              }
            ],
            "description": "The tax applied to this component: rate, code, and jurisdiction. Complements\nthe `isTaxFree` flag. In v1 the server applies your account's tax settings\nto taxable components; this object reserves the shape for richer tax later."
          }
        },
        "description": "One component line inside a kit or sub-kit.\n\nA component is itself typed — it may be a catalogue-product, on-the-fly,\nnotes, labour, or kit-product line — and carries its own pricing and\nmeasurement details."
      },
      "Label": {
        "type": "object",
        "properties": {
          "labelId": {
            "type": "string",
            "description": "The label's id.",
            "readOnly": true,
            "pattern": "^label_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "name": {
            "type": "string",
            "description": "Display name of the label. Unique within your company.",
            "readOnly": true
          },
          "colour": {
            "type": "string",
            "description": "Display colour of the label, as a hex string (for example \"#FF69B4\").",
            "readOnly": true
          }
        },
        "description": "A label: a small company-scoped tag that can be attached to any number of\nquotes and orders, with a display name and colour. Labels are managed in\nthe Factory app; renaming one there updates it everywhere it appears, and\nits id stays stable across the rename."
      },
      "LabelPosition": {
        "type": "object",
        "properties": {
          "x": {
            "type": "number",
            "format": "double",
            "description": "The x coordinate of the label box, in canvas space."
          },
          "y": {
            "type": "number",
            "format": "double",
            "description": "The y coordinate of the label box, in canvas space."
          },
          "width": {
            "type": "number",
            "format": "double",
            "description": "The width of the label box, in canvas space.",
            "minimum": 0
          },
          "height": {
            "type": "number",
            "format": "double",
            "description": "The height of the label box, in canvas space.",
            "minimum": 0
          }
        },
        "description": "A label's bounding box in canvas (SVG) coordinate space. Used for line labels,\nangle labels, and finish labels."
      },
      "LabourLine": {
        "type": "object",
        "properties": {
          "labourUserId": {
            "type": "string",
            "description": "The id of the worker who performed the labour. Required, and must be an\nactive user in the company that owns the order or quote.",
            "pattern": "^user_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productName": {
            "type": "string",
            "description": "A short name for the labour.",
            "maxLength": 1000
          },
          "productDescription": {
            "type": "string",
            "description": "A longer description of the labour.",
            "maxLength": 4000
          },
          "pricing": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LinePricing"
              }
            ],
            "description": "Quantity and pricing for this line."
          }
        },
        "description": "A line item charging for labour performed by a team member.",
        "required": [
          "labourUserId"
        ]
      },
      "LineCell": {
        "type": "object",
        "properties": {
          "size": {
            "type": "number",
            "format": "double",
            "description": "The length of the edge. Omitted when a length has not yet been entered.",
            "minimum": 0,
            "exclusiveMinimum": true
          },
          "hidden": {
            "type": "boolean",
            "description": "Whether this edge's length label is hidden."
          }
        },
        "description": "The length entry for a single edge between two points."
      },
      "LinePricing": {
        "type": "object",
        "properties": {
          "quantity": {
            "type": "string",
            "description": "Quantity for this line, as a decimal string (e.g. \"2\" or \"2.5\"). Supports up\nto 4 decimal places. It is the first factor of the line-total formula\ndocumented on `totalPrice`.",
            "maxLength": 32
          },
          "unitPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Per-unit price. Required on priced lines. The server rounds this to 4 decimal\nplaces (half up) before pricing, so unit-price precision finer than that\n(micro amounts that are not a multiple of 100) is not preserved and can\nshift the computed line total."
          },
          "totalPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The line total, as a Money amount in micros. Required on priced lines.\n\nOn priced lines the server validates this value against the line's other\npricing fields. Compute it as:\n\n  quantity * unit_price * (1 - discount / 100)\n\nand round the result to the nearest cent — 2 decimal places, half up (0.005\nrounds up). For lineal or square measurement pricing the result is then\nmultiplied by the measured length or area the server derives from the line's\nmeasurement details before rounding. Send the amount as a whole number of\ncents (amount_micros a multiple of 10000); a sub-cent amount is rounded\nto the nearest cent before the check. If the value you send does not equal\nthe computed total after rounding, the request is rejected with HTTP 400\n(a validation error identifying the line).\n\nExample: quantity \"2.5\", unit_price $10.00 (10000000 micros), discount\n\"10.00\" gives 2.5 * 10.00 * (1 - 0.10) = $22.50 — so total_price is\n22500000 micros."
          },
          "discount": {
            "type": "string",
            "description": "Discount applied to the line, as a decimal percentage string (e.g. \"10.00\"\nmeans 10%). Supports up to 2 decimal places. In the line-total formula on\n`totalPrice` it enters as the factor (1 - discount / 100).",
            "maxLength": 32
          },
          "cost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Unit cost for this line, used to compute margin."
          },
          "markup": {
            "type": "string",
            "description": "Markup applied to the line, as a decimal string — either a money amount or a\npercentage, depending on markup_is_percentage.",
            "maxLength": 32
          },
          "markupIsPercentage": {
            "type": "boolean",
            "description": "Whether markup is a percentage (true) or a fixed amount (false). Defaults to\ntrue."
          },
          "margin": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Margin for the line. When supplied, it is validated against cost and quantity."
          },
          "pricingStrategy": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PricingStrategy"
              }
            ],
            "description": "How this line is priced (per-unit quantity, lineal, or square measurement).\nDefaults to per-unit quantity pricing."
          },
          "priceLevel": {
            "type": "string",
            "description": "Optional named price level applied to this line.",
            "maxLength": 150
          },
          "isTaxFree": {
            "type": "boolean",
            "description": "Whether this line is exempt from tax."
          },
          "measurementItems": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Per-piece measurement breakdown, as a JSON array of objects. Required for the\nlineal and square pricing strategies; ignored for per-unit quantity pricing.\nThe server sums these pieces (applying your account's minimum-length setting\nand any unit conversion) to derive the measured length or area that scales the\nline total — see the measurement note on `totalPrice`.\n\nThe object shape depends on the pricing strategy:\n  - Lineal (metres or feet): {\"length\": <millimetres>, \"amount\": <piece count>}.\n    Length is always in millimetres; amount is how many pieces are that length.\n    Example: [{\"length\": 2360, \"amount\": 8}, {\"length\": 1600, \"amount\": 1}].\n  - Square metres: {\"length\": <metres>, \"width\": <metres>}, with an optional\n    \"amount\" (piece count, default 1). Area per piece is length x width.\n    Example: [{\"length\": 1.22, \"width\": 0.92}].\n  - Square feet: each object carries the precomputed per-piece area as\n    \"total_area_in_square_feet\" (plus \"total_area_in_square_meters\" for stock).\nNumbers may be sent as JSON numbers or numeric strings."
          },
          "actualMeasurementItems": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "As-built (actual) per-piece measurements, in the same object shape as\n`measurementItems` (see its per-strategy examples). Optional. Where `measurementItems` holds the\nquoted measurements that set the line price, this holds the quantities actually\nused; the server applies it to margin and stock figures, not to the line total."
          },
          "tax": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxDetail"
              }
            ],
            "description": "The tax applied to this line: rate, code, and jurisdiction. Complements the\n`isTaxFree` flag. In v1 the server applies your account's tax settings to\ntaxable lines; this object reserves the shape for richer per-line tax later."
          }
        },
        "description": "Pricing for a single priced line: quantity, prices, discount, cost, markup,\nmargin, and pricing strategy. On priced lines the server validates the line\ntotal against the quantity, unit price, and discount; see `totalPrice` for\nthe exact formula, the rounding rule, and what happens if it does not match."
      },
      "LineSet": {
        "type": "object",
        "properties": {
          "values": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/LineCell"
            },
            "description": "The length entry for each edge, keyed by the pair of point ids it connects."
          },
          "positions": {
            "type": "object",
            "additionalProperties": {
              "$ref": "#/components/schemas/LabelPosition"
            },
            "description": "The label box for each edge's length label, keyed by edge."
          }
        },
        "description": "The collection of edge lengths and their label positions for a drawing."
      },
      "NotesLine": {
        "type": "object",
        "properties": {
          "notesType": {
            "allOf": [
              {
                "$ref": "#/components/schemas/NotesType"
              }
            ],
            "description": "The kind of note. Required."
          },
          "notes": {
            "type": "string",
            "description": "The note text.",
            "maxLength": 4000
          },
          "productName": {
            "type": "string",
            "description": "A short name for the note line.",
            "maxLength": 1000
          },
          "productDescription": {
            "type": "string",
            "description": "A longer description for the note line.",
            "maxLength": 4000
          }
        },
        "description": "A line item carrying a note rather than a priced product. Has no quantity or\nprice.",
        "required": [
          "notesType"
        ]
      },
      "OnTheFlyLine": {
        "type": "object",
        "properties": {
          "productName": {
            "type": "string",
            "description": "Name of the item. Required.",
            "maxLength": 1000,
            "minLength": 1
          },
          "productDescription": {
            "type": "string",
            "description": "Optional longer description of the item.",
            "maxLength": 4000
          },
          "colour": {
            "type": "string",
            "description": "Optional colour for the item.",
            "maxLength": 100
          },
          "pricing": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LinePricing"
              }
            ],
            "description": "Pricing for this line. Unit price and line total are required."
          },
          "attributes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Additional named attributes for the line, as a JSON array of {name, value}\nentries (for example {name: \"Size\", value: \"76x38mm\"}). An entry may also\ncarry flags that hide it from specific documents or views, such as the\ninvoice PDF or the customer view."
          }
        },
        "description": "A free-text line: an ad-hoc line not tied to any catalogue product.",
        "required": [
          "productName"
        ]
      },
      "Point": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The unique id of this point within the drawing.",
            "maxLength": 64
          },
          "x": {
            "type": "number",
            "format": "double",
            "description": "The x coordinate of the point, in canvas space."
          },
          "y": {
            "type": "number",
            "format": "double",
            "description": "The y coordinate of the point, in canvas space."
          },
          "vectors": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 64
            },
            "description": "The ids of points reached by edges leaving this point.",
            "maxItems": 64
          },
          "connect": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 64
            },
            "description": "The ids of points connected to this point by incoming edges.",
            "maxItems": 64
          },
          "finish": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Finish"
              }
            ],
            "description": "Optional end treatment at this point."
          }
        },
        "description": "A vertex in the drawing. Points are connected by edges to form the flashing\nprofile."
      },
      "ProductKitLine": {
        "type": "object",
        "properties": {
          "kitId": {
            "type": "string",
            "description": "The kit's id (Kit.kit_id from the catalogue read surface). Optional: omit\nit to define an on-the-fly kit not linked to any catalogue kit — the kit\nis described entirely by this line's name, pricing, components, and\nsub-kits.",
            "pattern": "^kit_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "kitRowId": {
            "type": "string",
            "description": "The selected kit row/variant id. Optional.",
            "pattern": "^kitrow_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "productName": {
            "type": "string",
            "description": "Display name of the kit.",
            "maxLength": 1000
          },
          "customPricing": {
            "type": "boolean",
            "description": "Whether the kit is custom-priced (true) rather than computed from its\ncomponents (false)."
          },
          "description": {
            "type": "string",
            "description": "Free-text description of the kit.",
            "maxLength": 4000
          },
          "displayKitItems": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Which documents and views the kit's components are shown on, as a JSON\narray of display-target keys — for example \"displayOnWorkOrderPdf\",\n\"displayOnInvoicePdf\", or \"displayOnAllViews\". Optional."
          },
          "displaySubKits": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Which documents and views the kit's sub-kits are shown on, as a JSON array\nof display-target keys, using the same values as display_kit_items.\nOptional."
          },
          "pricing": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LinePricing"
              }
            ],
            "description": "Overall pricing for the kit line."
          },
          "attributes": {
            "type": "array",
            "items": {
              "type": "object"
            },
            "description": "Additional named attributes for the kit line, as a JSON array of\n{name, value} entries; for measurement-priced kits they describe the\nmeasurement entries."
          },
          "components": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KitComponent"
            },
            "description": "The kit's direct components."
          },
          "subKits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SubKit"
            },
            "description": "The kit's sub-assemblies, each with its own components."
          }
        },
        "description": "A configured kit line on an order or quote.\n\nA kit groups a set of components, optionally arranged into sub-kits. The\nparent line carries the kit's overall pricing; the component tree is one level\ndeep — a kit holds its direct components and sub-kits, and each sub-kit holds\nits own components."
      },
      "SalesLine": {
        "type": "object",
        "properties": {
          "onTheFly": {
            "allOf": [
              {
                "$ref": "#/components/schemas/OnTheFlyLine"
              }
            ],
            "description": "A free-form line not tied to a catalogue product. Set exactly one of the six\nline-type fields."
          },
          "catalogue": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CatalogueLine"
              }
            ],
            "description": "A line for a product from your catalogue. Set exactly one of the six\nline-type fields."
          },
          "labour": {
            "allOf": [
              {
                "$ref": "#/components/schemas/LabourLine"
              }
            ],
            "description": "A line charging for labour. Set exactly one of the six line-type fields."
          },
          "notes": {
            "allOf": [
              {
                "$ref": "#/components/schemas/NotesLine"
              }
            ],
            "description": "A note line, with no pricing. Set exactly one of the six line-type fields."
          },
          "flashing": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FlashingLine"
              }
            ],
            "description": "A flashing line, with its drawing and geometry. Set exactly one of the six\nline-type fields."
          },
          "productKit": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ProductKitLine"
              }
            ],
            "description": "A line for a predefined product kit. Set exactly one of the six line-type\nfields."
          },
          "id": {
            "type": "string",
            "description": "The server-assigned id of this line — unique and stable for the life of the\nline. Returned when a quote is read; pass it to update or remove the line.\nNot set when creating a line (the server assigns it). This id has a different\nform from the quote id.",
            "readOnly": true,
            "pattern": "^line_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          }
        },
        "description": "A single line item on an order or quote. Each line is exactly one of the\nsupported line types."
      },
      "SquareAngle": {
        "type": "object",
        "properties": {
          "x": {
            "type": "number",
            "format": "double",
            "description": "The x coordinate of the marker, in canvas space. Omitted until placed."
          },
          "y": {
            "type": "number",
            "format": "double",
            "description": "The y coordinate of the marker, in canvas space. Omitted until placed."
          }
        },
        "description": "The right-angle (90°) marker placed on the drawing."
      },
      "SubKit": {
        "type": "object",
        "properties": {
          "title": {
            "type": "string",
            "description": "Display title of the sub-assembly.",
            "maxLength": 150
          },
          "quantity": {
            "type": "string",
            "description": "Quantity of this sub-assembly, as a decimal string.",
            "maxLength": 32
          },
          "actualQuantity": {
            "type": "string",
            "description": "Actual measured quantity, as a decimal string.",
            "maxLength": 32
          },
          "unitPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Price per unit of the sub-assembly."
          },
          "totalPrice": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total price of the sub-assembly."
          },
          "components": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/KitComponent"
            },
            "description": "The components that make up this sub-assembly."
          }
        },
        "description": "A sub-assembly within a kit.\n\nA sub-kit groups a set of components under its own title and quantity, letting\na kit be organized into named sub-assemblies."
      },
      "TextBox": {
        "type": "object",
        "properties": {
          "x": {
            "type": "number",
            "format": "double",
            "description": "The x coordinate of the annotation, in canvas space. Omitted until placed."
          },
          "y": {
            "type": "number",
            "format": "double",
            "description": "The y coordinate of the annotation, in canvas space. Omitted until placed."
          },
          "width": {
            "type": "number",
            "format": "double",
            "description": "The width of the annotation box, in canvas space.",
            "minimum": 0
          },
          "height": {
            "type": "number",
            "format": "double",
            "description": "The height of the annotation box, in canvas space.",
            "minimum": 0
          },
          "value": {
            "type": "string",
            "description": "The annotation text as a flat string. May be present alongside the\nstructured `text` form.",
            "maxLength": 4000
          },
          "text": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/TextRow"
            },
            "description": "The annotation text in structured rows. May be present alongside `value`.",
            "maxItems": 200
          },
          "rotateDeg": {
            "type": "number",
            "format": "double",
            "description": "Optional rotation of the annotation, in degrees.",
            "minimum": -360,
            "maximum": 360
          }
        },
        "description": "A free-text annotation placed on the drawing."
      },
      "TextRow": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32",
            "description": "The 0-based index of this row within the text annotation.",
            "minimum": 0
          },
          "indent": {
            "type": "integer",
            "format": "int32",
            "description": "The indentation level of this row.",
            "minimum": 0
          },
          "value": {
            "type": "array",
            "items": {
              "type": "string",
              "maxLength": 512
            },
            "description": "The text segments that make up this row, in order.",
            "maxItems": 64
          }
        },
        "description": "One row of text within a text annotation."
      },
      "AddOrderLineItemResponse": {
        "type": "object",
        "properties": {
          "order": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Order"
              }
            ],
            "description": "The updated order, including the recomputed totals."
          }
        },
        "description": "Result of adding a line item to an order: the order with recomputed totals."
      },
      "CreateOrderRequest": {
        "type": "object",
        "properties": {
          "customerId": {
            "type": "string",
            "description": "The customer's unique id, as returned by the Customers API. Exactly one of\n`customerId` or `companyName` must be set.",
            "pattern": "^cust_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "companyName": {
            "type": "string",
            "description": "The customer's company name, matched case-insensitively within your company.\nExactly one of `customerId` or `companyName` must be set.",
            "maxLength": 150
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SalesLine"
            },
            "description": "The order's line items. Each line is one of the supported line types\n(on-the-fly, catalogue, labour, notes, flashing, or product kit)."
          },
          "adjustments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Adjustment"
            },
            "description": "Order-level fees, discounts, and markups applied across the whole order."
          },
          "fulfilmentMethod": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FulfilmentMethod"
              }
            ],
            "description": "How the customer receives the order: pickup, delivery, or installation.\nDetermines which address below applies."
          },
          "billingAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Billing address for the order."
          },
          "deliveryAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Delivery address. Used only when fulfilment_method is DELIVERY."
          },
          "installAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Installation address. Used only when fulfilment_method is INSTALL."
          },
          "isSubmitted": {
            "type": "boolean",
            "description": "Submit the order immediately (true) or save it as a draft (false).\nSubmitting assigns a status and finalizes the order."
          },
          "statusId": {
            "type": "string",
            "description": "Optional explicit workflow status to place the order in, by id. If omitted,\na default status is applied when the order is submitted. Discover your\naccount's statuses with GET /v1/company/order-statuses.",
            "pattern": "^status_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "reference": {
            "type": "string",
            "description": "Free-text external reference for this order — e.g. the order id from your\nown system. Stored for lookup and audit; distinct from `requestId`.",
            "maxLength": 1000
          },
          "purchaseOrder": {
            "type": "string",
            "format": "int64",
            "description": "The purchase order (PO) number for this order — your reference number for the\nwork being requested. In this version the server assigns the next available\nnumber for your company; a value you supply here is not yet applied on\ncreate.",
            "minimum": 0
          },
          "customFields": {
            "type": "object",
            "description": "Values for your account's custom-defined sales-document fields, keyed by\neach field's configured key. Unknown keys, keys of other modules, and\nvalues that don't match the field's type are rejected. Send number and\ncurrency values as numbers or decimal strings, checkboxes as booleans,\nmulti-selects as arrays of strings, and everything else as strings.\nSelect and multi-select values must be option keys from the field's\ndefinition, never display labels; discover your account's field keys and\noptions with GET /v1/company/custom-fields. Formula fields are computed\nand cannot be written."
          },
          "syncAccounting": {
            "type": "boolean",
            "description": "Whether to sync the order to your accounting system (Xero, MYOB, or QBO)\nwhen it is submitted. Defaults to false; leave it false for bulk imports to\navoid syncing every order."
          },
          "requiredAt": {
            "type": "string",
            "format": "date-time",
            "description": "The date the order is required or needed by."
          },
          "contact": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Contact"
              }
            ],
            "description": "The point-of-contact person for this order, distinct from the customer."
          },
          "notes": {
            "type": "string",
            "description": "Free-text notes about the order.",
            "maxLength": 500
          },
          "pickupNotes": {
            "type": "string",
            "description": "Free-text notes shown when the order is picked up.",
            "maxLength": 1000
          },
          "requestId": {
            "type": "string",
            "description": "Idempotency key for safe retries: an opaque, client-generated UUID. If a\nprevious create with the same key already succeeded, the server returns that\noriginal order instead of creating a duplicate; reusing the same key with a\nmaterially different body is rejected with HTTP 409 (Aborted). Keys are\nretained for at least 24 hours; after that, a reused key may create a new\norder. Omit it for no idempotency guarantee. Distinct from `reference`.",
            "format": "uuid"
          },
          "tax": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxDetail"
              }
            ],
            "description": "The tax to apply, as a rate/code/jurisdiction descriptor. Optional and\nadvisory in this version: the server applies your account's configured tax\nrate regardless of what you send here, and the created order echoes back the\nvalues actually applied (`Order.tax`, `Order.tax_amount`) — compare them to\ndetect an override. Reserves the shape for future per-order tax support."
          },
          "labelIds": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^label_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            },
            "description": "Labels to attach to the order as part of creating it, by id. Every id\nmust exist in your company's label set (discover them with\nGET /v1/company/labels): an unknown id fails the whole call with HTTP\n404 before the order is created. Labelling happens with the create but\nnot atomically inside it — in the rare case the order is created and\nlabelling then fails, the error names the created order id and the order\nexists WITHOUT labels; attach them with SetOrderLabels. The created\norder echoes the attached labels. Duplicate ids are rejected."
          },
          "fields": {
            "type": "string",
            "description": "Comma-separated response fields to include, using camelCase JSON names\n(e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects\nand map transparently across arrays. Paths are relative to the resource,\nnot the response envelope; envelope keys are always preserved. Only 2xx\nJSON responses are filtered; error bodies pass through unmodified. Unknown\nnames are silently ignored. Takes precedence over `excludeFields` when\nboth are provided.",
            "maxLength": 2000
          },
          "excludeFields": {
            "type": "string",
            "description": "Comma-separated response fields to exclude, using camelCase JSON names.\nDot paths and array-transparency work the same as `fields`. Paths are\nrelative to the resource, not the response envelope. Only 2xx JSON\nresponses are filtered; error bodies pass through unmodified. Ignored\nwhen `fields` is also provided.",
            "maxLength": 2000
          }
        },
        "description": "Input for creating an order: the customer, line items, adjustments,\naddresses, and delivery/submission options. Order totals are derived by the\nserver, not supplied here."
      },
      "CreateOrderResponse": {
        "type": "object",
        "properties": {
          "order": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Order"
              }
            ],
            "description": "The created order, including server-set fields and assigned identifiers."
          },
          "drawings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DrawingIdMapping"
            },
            "description": "The server identities of the drawings created by this call, one entry\nper flashing line in the order you sent them, each pairing your temp_id\nwith the assigned drawing_id — use them to address UploadDrawingSvg.\nEmpty when the order had no flashing lines."
          }
        },
        "description": "Result of creating an order: the created order with all server-assigned fields\npopulated."
      },
      "DeleteOrderLineItemResponse": {
        "type": "object",
        "properties": {
          "order": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Order"
              }
            ],
            "description": "The updated order, with the line removed and totals recomputed."
          }
        },
        "description": "Result of removing a line item: the order with the line removed."
      },
      "GetOrderResponse": {
        "type": "object",
        "properties": {
          "order": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Order"
              }
            ],
            "description": "The requested order."
          }
        },
        "description": "Result of retrieving a single order."
      },
      "ListLabelsResponse": {
        "type": "object",
        "properties": {
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Label"
            },
            "description": "One page of labels, sorted by name."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are\nno more results."
          }
        },
        "description": "Result of listing your company's labels."
      },
      "ListOrderStatusesResponse": {
        "type": "object",
        "properties": {
          "statuses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OrderStatus"
            },
            "description": "The order statuses in this page, in display order."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are no\nmore results."
          }
        },
        "description": "A page of order statuses."
      },
      "ListOrdersResponse": {
        "type": "object",
        "properties": {
          "orders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Order"
            },
            "description": "The orders in this page."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are no\nmore results."
          }
        },
        "description": "A page of orders."
      },
      "AddOrderLineItemBody": {
        "type": "object",
        "properties": {
          "line": {
            "allOf": [
              {
                "$ref": "#/components/schemas/SalesLine"
              }
            ],
            "description": "The line item to add: one of the supported line types (on-the-fly,\ncatalogue, labour, notes, or product kit)."
          },
          "requestId": {
            "type": "string",
            "description": "An optional idempotency key. A repeat with the same key returns the original\nresult rather than adding the line twice; scoped to your company.",
            "format": "uuid"
          },
          "fields": {
            "type": "string",
            "description": "Comma-separated response fields to include, using camelCase JSON names\n(e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects\nand map transparently across arrays. Paths are relative to the resource,\nnot the response envelope; envelope keys are always preserved. Only 2xx\nJSON responses are filtered; error bodies pass through unmodified. Unknown\nnames are silently ignored. Takes precedence over `excludeFields` when\nboth are provided.",
            "maxLength": 2000
          },
          "excludeFields": {
            "type": "string",
            "description": "Comma-separated response fields to exclude, using camelCase JSON names.\nDot paths and array-transparency work the same as `fields`. Paths are\nrelative to the resource, not the response envelope. Only 2xx JSON\nresponses are filtered; error bodies pass through unmodified. Ignored\nwhen `fields` is also provided.",
            "maxLength": 2000
          }
        },
        "description": "Input for adding a line item to an order.",
        "required": [
          "line"
        ]
      },
      "SetOrderLabelsBody": {
        "type": "object",
        "properties": {
          "labelIds": {
            "type": "array",
            "items": {
              "type": "string",
              "pattern": "^label_[0-7][0-9a-hjkmnp-tv-z]{25}$"
            },
            "description": "The order's desired complete label set. An empty list removes every\nlabel. Duplicate ids are rejected."
          },
          "fields": {
            "type": "string",
            "description": "Comma-separated response fields to include, using camelCase JSON names\n(e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects\nand map transparently across arrays. Paths are relative to the resource,\nnot the response envelope; envelope keys are always preserved. Only 2xx\nJSON responses are filtered; error bodies pass through unmodified. Unknown\nnames are silently ignored. Takes precedence over `excludeFields` when\nboth are provided.",
            "maxLength": 2000
          },
          "excludeFields": {
            "type": "string",
            "description": "Comma-separated response fields to exclude, using camelCase JSON names.\nDot paths and array-transparency work the same as `fields`. Paths are\nrelative to the resource, not the response envelope. Only 2xx JSON\nresponses are filtered; error bodies pass through unmodified. Ignored\nwhen `fields` is also provided.",
            "maxLength": 2000
          }
        },
        "description": "Input for replacing the labels on an order."
      },
      "OrderServiceUploadDrawingSvgBody": {
        "type": "object",
        "properties": {
          "svgs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DrawingSvg"
            },
            "description": "The drawings and their rendered SVGs. At least one is required.",
            "minItems": 1
          },
          "fields": {
            "type": "string",
            "description": "Comma-separated response fields to include, using camelCase JSON names\n(e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects\nand map transparently across arrays. Paths are relative to the resource,\nnot the response envelope; envelope keys are always preserved. Only 2xx\nJSON responses are filtered; error bodies pass through unmodified. Unknown\nnames are silently ignored. Takes precedence over `excludeFields` when\nboth are provided.",
            "maxLength": 2000
          },
          "excludeFields": {
            "type": "string",
            "description": "Comma-separated response fields to exclude, using camelCase JSON names.\nDot paths and array-transparency work the same as `fields`. Paths are\nrelative to the resource, not the response envelope. Only 2xx JSON\nresponses are filtered; error bodies pass through unmodified. Ignored\nwhen `fields` is also provided.",
            "maxLength": 2000
          }
        },
        "description": "Input for uploading rendered SVGs for an order's flashing drawings.",
        "required": [
          "svgs"
        ]
      },
      "OrderStatus": {
        "type": "object",
        "properties": {
          "statusId": {
            "type": "string",
            "description": "The status's unique id. Pass this as status_id on CreateOrder.",
            "pattern": "^status_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "name": {
            "type": "string",
            "description": "The display name of the status (e.g. \"Submitted\").",
            "maxLength": 100
          },
          "index": {
            "type": "integer",
            "format": "int32",
            "description": "The status's position in the workflow, used for display ordering."
          },
          "colour": {
            "type": "string",
            "description": "The status's display colour.",
            "maxLength": 100
          },
          "isDefault": {
            "type": "boolean",
            "description": "Whether this is the default status applied when an order is submitted\nwithout an explicit status."
          }
        },
        "description": "A single order status (a workflow column)."
      },
      "QueryOrdersResponse": {
        "type": "object",
        "properties": {
          "orders": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Order"
            },
            "description": "The orders in this page, oldest change first. Use the last order's update\ntime as the updated_since for your next call."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are no\nmore results."
          }
        },
        "description": "A page of orders from the change feed, ordered by when each order last changed\n(oldest first). The lower bound (updated_since) is inclusive, so the order you\ncheckpoint on reappears as the first item of the next page — dedupe on order_id\n+ last_updated_at, or skip ids you have already seen at the window boundary."
      },
      "SetOrderLabelsResponse": {
        "type": "object",
        "properties": {
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Label"
            },
            "description": "The order's labels after the change, oldest attachment first."
          }
        },
        "description": "Result of replacing the labels on an order: the resulting label set."
      },
      "UpdateOrderLineItemResponse": {
        "type": "object",
        "properties": {
          "order": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Order"
              }
            ],
            "description": "The updated order, including the recomputed totals."
          }
        },
        "description": "Result of replacing a line item: the order with recomputed totals."
      },
      "salesordersUploadDrawingSvgResponse": {
        "type": "object",
        "properties": {
          "results": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DrawingSvgResult"
            },
            "description": "One result per submitted drawing, in request order."
          }
        },
        "description": "Result of an SVG upload: one outcome per submitted drawing."
      },
      "Order": {
        "type": "object",
        "properties": {
          "orderId": {
            "type": "string",
            "description": "The order's unique id. Read-only.",
            "readOnly": true,
            "pattern": "^order_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "isSubmitted": {
            "type": "boolean",
            "description": "Whether the order has been submitted (true) or is still a draft (false)."
          },
          "customerId": {
            "type": "string",
            "description": "The id of the customer this order belongs to. Always present. Read-only.",
            "readOnly": true,
            "pattern": "^cust_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "customerCompanyName": {
            "type": "string",
            "description": "The customer's company name. Read-only.",
            "readOnly": true,
            "maxLength": 150
          },
          "statusId": {
            "type": "string",
            "description": "The id of the order's workflow status. Defaults to a \"Submitted\" status when\nthe order is submitted without one set.",
            "pattern": "^status_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "statusName": {
            "type": "string",
            "description": "The display name of the order's workflow status. Read-only.",
            "readOnly": true,
            "maxLength": 100
          },
          "subtotal": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Order subtotal — the sum of line totals before tax. Derived by the server\nfrom the line items; read-only.",
            "readOnly": true
          },
          "taxAmount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total tax for the order, as an absolute money amount (the tax `amount`, not a\nrate). Derived by the server from your account's tax settings; read-only. See\n`tax` for the rate, code, and jurisdiction that produced it.",
            "readOnly": true
          },
          "total": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Order grand total. Derived by the server; read-only.",
            "readOnly": true
          },
          "margin": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Order margin, computed by the server from the line items. Read-only.",
            "readOnly": true
          },
          "totalCost": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "Total cost of the order, computed by the server from the line items. Read-only.",
            "readOnly": true
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SalesLine"
            },
            "description": "The order's line items."
          },
          "adjustments": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Adjustment"
            },
            "description": "Order-level fees, discounts, and markups. At most one fee per order is allowed."
          },
          "fulfilmentMethod": {
            "allOf": [
              {
                "$ref": "#/components/schemas/FulfilmentMethod"
              }
            ],
            "description": "How the customer receives the order: pickup, delivery, or installation."
          },
          "billingAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Billing address for the order."
          },
          "deliveryAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Delivery address. Applies when fulfilment_method is DELIVERY."
          },
          "installAddress": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Address"
              }
            ],
            "description": "Installation address. Applies when fulfilment_method is INSTALL."
          },
          "reference": {
            "type": "string",
            "description": "Free-text external reference, e.g. the order id from your own system.",
            "maxLength": 1000
          },
          "purchaseOrder": {
            "type": "string",
            "format": "int64",
            "description": "The purchase order (PO) number for this order — the reference number for the\nwork being requested. Assigned by the server when not supplied at creation."
          },
          "receivedStatus": {
            "allOf": [
              {
                "$ref": "#/components/schemas/ReceivedStatus"
              }
            ],
            "description": "Whether the order has been received. Read-only.",
            "readOnly": true
          },
          "paymentStatus": {
            "allOf": [
              {
                "$ref": "#/components/schemas/PaymentStatus"
              }
            ],
            "description": "The order's payment status. Read-only.",
            "readOnly": true
          },
          "customFields": {
            "type": "object",
            "description": "The document's custom-field values, keyed by each field's configured key."
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the order was created. Read-only.",
            "readOnly": true
          },
          "submittedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the order was submitted. Read-only.",
            "readOnly": true
          },
          "lastUpdatedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the order was last updated. Read-only.",
            "readOnly": true
          },
          "requiredAt": {
            "type": "string",
            "format": "date-time",
            "description": "The date the order is required or needed by."
          },
          "contact": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Contact"
              }
            ],
            "description": "The order's point-of-contact person."
          },
          "notes": {
            "type": "string",
            "description": "Free-text notes about the order.",
            "maxLength": 500
          },
          "pickupNotes": {
            "type": "string",
            "description": "Free-text notes shown when the order is picked up.",
            "maxLength": 1000
          },
          "deliveryFee": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The delivery fee charged on the order. Read-only.",
            "readOnly": true
          },
          "createdByUserId": {
            "type": "string",
            "description": "The id of the user who created the order, empty when unset. Resolve the user\nvia the Users API (which can return users who have since left). Read-only.",
            "readOnly": true,
            "pattern": "^user_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "submittedByUserId": {
            "type": "string",
            "description": "The id of the user who submitted the order, empty when unset. Resolve the user\nvia the Users API (which can return users who have since left). Read-only.",
            "readOnly": true,
            "pattern": "^user_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "discountAmount": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Money"
              }
            ],
            "description": "The order-level discount total, rolled up from the per-line discounts and the\ndiscount entries in `adjustments`. Read-only.",
            "readOnly": true
          },
          "invoiceNumber": {
            "type": "string",
            "description": "The order's invoice number as displayed: the order's own number, or the\nlinked accounting system's number when synced. Empty until the order is\ninvoiced. Read-only.",
            "readOnly": true,
            "maxLength": 32
          },
          "invoicedAt": {
            "type": "string",
            "format": "date-time",
            "description": "When the order was invoiced. Unset until the order is invoiced. Read-only.",
            "readOnly": true
          },
          "statusColour": {
            "type": "string",
            "description": "The colour of the order's workflow status, for display (defaults to\n\"transparent\"). The display companion to status_name. Read-only.",
            "readOnly": true,
            "maxLength": 100
          },
          "tax": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxDetail"
              }
            ],
            "description": "The tax applied to the order: rate, code, and jurisdiction. Reflects your\naccount's tax settings — the values actually applied — and is the companion\ndescriptor to the `taxAmount` amount. Read-only.",
            "readOnly": true
          },
          "labels": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Label"
            },
            "description": "The labels attached to this order, oldest attachment first. Present on\ncreate responses when label_ids are supplied; absent from line-write\nresponses.",
            "readOnly": true
          },
          "isArchived": {
            "type": "boolean",
            "description": "Whether the order is archived. Archived orders are hidden from every read\nby default: reach them with GetOrder's include_archived, ListOrders'\narchived, or QueryOrders' include_archived. Always false on default\nreads. Read-only.",
            "readOnly": true
          }
        },
        "description": "A sales order, as returned by read endpoints. Includes the order\nheader, its line items, adjustments, addresses, contact, totals, and all\nserver-assigned fields. The same shape is returned whether the order was just\ncreated or fetched later."
      },
      "PaymentStatus": {
        "type": "string",
        "enum": [
          "PAYMENT_STATUS_UNSPECIFIED",
          "PAYMENT_STATUS_UNPAID",
          "PAYMENT_STATUS_PARTIALLY_PAID",
          "PAYMENT_STATUS_PAID"
        ],
        "default": "PAYMENT_STATUS_UNSPECIFIED",
        "description": "Whether the order has been paid for. Read-only.\n\n - PAYMENT_STATUS_UNSPECIFIED: Default, unset value.\n - PAYMENT_STATUS_UNPAID: No payment has been received.\n - PAYMENT_STATUS_PARTIALLY_PAID: Part of the order total has been paid.\n - PAYMENT_STATUS_PAID: The order has been paid in full."
      },
      "ReceivedStatus": {
        "type": "string",
        "enum": [
          "RECEIVED_STATUS_UNSPECIFIED",
          "RECEIVED_STATUS_DELIVERED",
          "RECEIVED_STATUS_PICKED_UP",
          "RECEIVED_STATUS_NOT_RECEIVED",
          "RECEIVED_STATUS_INSTALLED"
        ],
        "default": "RECEIVED_STATUS_UNSPECIFIED",
        "description": "Whether and how the order has reached the customer. Read-only.\n\n - RECEIVED_STATUS_UNSPECIFIED: Default, unset value.\n - RECEIVED_STATUS_DELIVERED: The order was delivered.\n - RECEIVED_STATUS_PICKED_UP: The order was picked up by the customer.\n - RECEIVED_STATUS_NOT_RECEIVED: The order has not yet reached the customer.\n - RECEIVED_STATUS_INSTALLED: The order was installed."
      },
      "CatalogueEntryType": {
        "type": "string",
        "enum": [
          "CATALOGUE_ENTRY_TYPE_UNSPECIFIED",
          "CATALOGUE_ENTRY_TYPE_PRODUCT",
          "CATALOGUE_ENTRY_TYPE_KIT",
          "CATALOGUE_ENTRY_TYPE_FLASHING"
        ],
        "default": "CATALOGUE_ENTRY_TYPE_UNSPECIFIED",
        "description": "The kind of catalogue entry a search hit refers to."
      },
      "CatalogueSearchHit": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "description": "The entry's unique id. The prefix always agrees with `type`. A `prod_` id\ndrills in with GetProduct and a `kit_` id with GetKit. KNOWN LIMITATION:\na `flashing_` id is the flashing-family id and is NOT currently accepted\nby GetFlashing (which takes the fronting `flashtpl_` template id); flashing hits are\ntherefore not drillable until the upstream index carries the fronting\nproduct id (see docs/backend.improvements.md 0d in the proxy repo). Treat\na flashing hit as name-only for now.",
            "pattern": "^(prod|kit|flashing)_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/CatalogueEntryType"
              }
            ],
            "description": "What kind of catalogue entry this hit refers to."
          },
          "name": {
            "type": "string",
            "description": "The entry's display name.",
            "maxLength": 1000
          },
          "categoryId": {
            "type": "string",
            "description": "The id of the entry's category. Unset for flashings.",
            "pattern": "^category_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "categoryName": {
            "type": "string",
            "description": "The display name of the entry's category. Unset for flashings.",
            "maxLength": 150
          }
        },
        "description": "One search hit: a minimal reference into the catalogue."
      },
      "SearchCatalogueResponse": {
        "type": "object",
        "properties": {
          "hits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/CatalogueSearchHit"
            },
            "description": "The hits in this page."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are\nno more results."
          }
        },
        "description": "A page of search hits, most relevant first."
      },
      "GetSupplierResponse": {
        "type": "object",
        "properties": {
          "supplier": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Supplier"
              }
            ],
            "description": "The requested supplier."
          }
        },
        "description": "Result of retrieving a single supplier."
      },
      "ListSuppliersResponse": {
        "type": "object",
        "properties": {
          "suppliers": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Supplier"
            },
            "description": "The suppliers in this page."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are\nno more results."
          }
        },
        "description": "A page of suppliers."
      },
      "Supplier": {
        "type": "object",
        "properties": {
          "supplierId": {
            "type": "string",
            "description": "The supplier's unique id.",
            "readOnly": true,
            "pattern": "^supplier_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "name": {
            "type": "string",
            "description": "The supplier's name.",
            "readOnly": true,
            "maxLength": 150
          }
        },
        "description": "A supplier linked to your catalogue products."
      },
      "GetUserResponse": {
        "type": "object",
        "properties": {
          "user": {
            "allOf": [
              {
                "$ref": "#/components/schemas/User"
              }
            ],
            "description": "The requested user."
          }
        },
        "description": "Result of retrieving a single user."
      },
      "ListUsersResponse": {
        "type": "object",
        "properties": {
          "users": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/User"
            },
            "description": "The users in this page."
          },
          "nextPageToken": {
            "type": "string",
            "description": "Token to pass as page_token to fetch the next page; empty when there are no\nmore results."
          }
        },
        "description": "A page of users."
      },
      "User": {
        "type": "object",
        "properties": {
          "userId": {
            "type": "string",
            "description": "The user's unique id. This is the value to use as a labour line's user id\nwhen assigning the user to a labour line on an order.",
            "pattern": "^user_[0-7][0-9a-hjkmnp-tv-z]{25}$"
          },
          "firstName": {
            "type": "string",
            "description": "The user's first name.",
            "maxLength": 150
          },
          "lastName": {
            "type": "string",
            "description": "The user's last name.",
            "maxLength": 150
          },
          "email": {
            "type": "string",
            "description": "The user's email address. In ListUsers responses this is populated only\nwhen the caller is a company administrator; GetUser always returns it.",
            "maxLength": 254
          },
          "isActive": {
            "type": "boolean",
            "description": "Whether the user is active. Only active users can be assigned to a labour\nline."
          }
        },
        "description": "A user belonging to your company."
      },
      "KitComponentType": {
        "type": "string",
        "enum": [
          "KIT_COMPONENT_TYPE_UNSPECIFIED",
          "KIT_COMPONENT_TYPE_CATALOGUE_PRODUCT",
          "KIT_COMPONENT_TYPE_ON_THE_FLY_PRODUCT",
          "KIT_COMPONENT_TYPE_NOTES",
          "KIT_COMPONENT_TYPE_LABOUR"
        ],
        "default": "KIT_COMPONENT_TYPE_UNSPECIFIED",
        "description": "The kind of a component within a product kit.\n\n - KIT_COMPONENT_TYPE_UNSPECIFIED: Default, unset value. Not a valid choice for a kit component.\n - KIT_COMPONENT_TYPE_CATALOGUE_PRODUCT: A component that is a product from your catalogue (backend \"Additional\nProduct\").\n - KIT_COMPONENT_TYPE_ON_THE_FLY_PRODUCT: A free-form component not tied to a catalogue product. Occurs on order and\nquote kits only — it is not a catalogue-template component kind.\n - KIT_COMPONENT_TYPE_NOTES: A note component: carries text rather than a priced product.\n - KIT_COMPONENT_TYPE_LABOUR: A labour component."
      },
      "Money": {
        "type": "object",
        "properties": {
          "amountMicros": {
            "type": "string",
            "format": "int64",
            "description": "Amount in micros — millionths of the currency's major unit. 1.00 =\n1_000_000 micros, so $12.34 is 12_340_000 — sent and returned over JSON as\nthe string \"12340000\" (64-bit integers serialize as JSON strings). A whole\nnumber of cents (or pence, etc.) is a multiple of 10_000 micros."
          },
          "currency": {
            "type": "string",
            "description": "ISO 4217 currency code (3 letters, e.g. \"AUD\")."
          }
        },
        "description": "Money — currency amount as an integer number of micros (no float).\n\nOne micro is a millionth of the currency's major unit: 1.00 = 1_000_000\nmicros, giving 6 decimal places of precision for exact line and order\namounts in any currency. Currency is ISO 4217, e.g. \"AUD\"."
      },
      "NotesType": {
        "type": "string",
        "enum": [
          "NOTES_TYPE_UNSPECIFIED",
          "NOTES_TYPE_INTERNAL",
          "NOTES_TYPE_EXTERNAL"
        ],
        "default": "NOTES_TYPE_UNSPECIFIED",
        "description": "Whether a notes line is internal-only or visible to the customer.\n\n - NOTES_TYPE_UNSPECIFIED: Default, unset value. Not a valid choice for a notes line.\n - NOTES_TYPE_INTERNAL: An internal note, not shown to the customer.\n - NOTES_TYPE_EXTERNAL: An external note, visible to the customer."
      },
      "PricingStrategy": {
        "type": "string",
        "enum": [
          "PRICING_STRATEGY_UNSPECIFIED",
          "PRICING_STRATEGY_BASIC_QUANTITIES",
          "PRICING_STRATEGY_LINEAL_METRES",
          "PRICING_STRATEGY_CUSTOM_FORMULA",
          "PRICING_STRATEGY_SQUARE_METRES",
          "PRICING_STRATEGY_LINEAL_FEET",
          "PRICING_STRATEGY_SQUARE_FEET"
        ],
        "default": "PRICING_STRATEGY_UNSPECIFIED",
        "description": "How a quantity is interpreted for pricing — whether it means a count of\neach, a length, or an area.\n\nOn a catalogue product this is the product's pricing basis. On an order or\nquote line, measurement-based strategies (lineal or square metres/feet)\nrequire the line's measurement details to be supplied.\n\n - PRICING_STRATEGY_UNSPECIFIED: Unset, or a legacy product with no pricing strategy recorded.\n - PRICING_STRATEGY_BASIC_QUANTITIES: Quantity is a simple per-each count.\n - PRICING_STRATEGY_LINEAL_METRES: Quantity is a length in lineal metres.\n - PRICING_STRATEGY_CUSTOM_FORMULA: Quantity is priced by a custom formula.\n - PRICING_STRATEGY_SQUARE_METRES: Quantity is an area in square metres.\n - PRICING_STRATEGY_LINEAL_FEET: Quantity is a length in lineal feet.\n - PRICING_STRATEGY_SQUARE_FEET: Quantity is an area in square feet."
      },
      "TaxDetail": {
        "type": "object",
        "properties": {
          "rate": {
            "type": "string",
            "description": "The tax rate applied, as a decimal fraction (e.g. \"0.10\" means 10%).",
            "maxLength": 32
          },
          "code": {
            "type": "string",
            "description": "The tax code, e.g. \"GST\", \"VAT\", or \"SALES_TAX\". Open-ended — not a fixed set.",
            "maxLength": 32
          },
          "jurisdiction": {
            "type": "string",
            "description": "The tax jurisdiction, e.g. \"AU\", \"GB\", or \"US-CA-LOS_ANGELES\". Open-ended.",
            "maxLength": 64
          }
        },
        "description": "The tax applied to an amount: its rate, code, and jurisdiction.\n\nA structured tax descriptor. Today a single rate, code, and jurisdiction apply\nacross your whole account; this object lets the contract carry richer\nper-jurisdiction tax later without changing shape."
      },
      "TaxIdentifier": {
        "type": "object",
        "properties": {
          "value": {
            "type": "string",
            "description": "The identifier value, e.g. an 11-digit ABN. Its format depends on `type` and\nis NOT validated in v1: an ABN is accepted as any non-empty string of up to\n15 characters. The length cap may widen when more schemes are supported.",
            "maxLength": 15,
            "minLength": 1
          },
          "type": {
            "allOf": [
              {
                "$ref": "#/components/schemas/TaxIdentifierType"
              }
            ],
            "description": "Which tax / business registration scheme `value` belongs to. Must be a\nspecified scheme when a TaxIdentifier is present. v1 accepts only ABN."
          }
        },
        "description": "TaxIdentifier — a tax / business registration number paired with the scheme it\nbelongs to. This is a value object: `value` is meaningless without `type`.\n\nFuture-proofs the API for non-Australian customers. v1 accepts ONLY an ABN\n(TAX_IDENTIFIER_TYPE_ABN); the other schemes are defined in the contract but are\nreserved for future releases — the v1 server rejects them with HTTP 400\n(InvalidArgument). This restriction is documented, not enforced by\nprotovalidate, so the wire contract can add support later without a\nvalidation-rule change."
      },
      "TaxIdentifierType": {
        "type": "string",
        "enum": [
          "TAX_IDENTIFIER_TYPE_UNSPECIFIED",
          "TAX_IDENTIFIER_TYPE_ABN",
          "TAX_IDENTIFIER_TYPE_EU_VAT",
          "TAX_IDENTIFIER_TYPE_US_EIN",
          "TAX_IDENTIFIER_TYPE_US_TIN",
          "TAX_IDENTIFIER_TYPE_US_SSN",
          "TAX_IDENTIFIER_TYPE_GB_VAT",
          "TAX_IDENTIFIER_TYPE_NZ_GST"
        ],
        "default": "TAX_IDENTIFIER_TYPE_UNSPECIFIED",
        "description": "The tax / business registration scheme a TaxIdentifier belongs to. v1 accepts\nonly ABN; the remaining values are reserved for future non-AU support.\n\n - TAX_IDENTIFIER_TYPE_UNSPECIFIED: Default, unset value. Not a valid scheme when a TaxIdentifier is supplied.\n - TAX_IDENTIFIER_TYPE_ABN: Australian Business Number. The only scheme accepted in v1.\n - TAX_IDENTIFIER_TYPE_EU_VAT: EU VAT number. Does not cover the United Kingdom — a UK VAT registration\nis TAX_IDENTIFIER_TYPE_GB_VAT. Reserved — not accepted in v1.\n - TAX_IDENTIFIER_TYPE_US_EIN: US Employer Identification Number. Reserved — not accepted in v1.\n - TAX_IDENTIFIER_TYPE_US_TIN: US Taxpayer Identification Number. Reserved — not accepted in v1.\n - TAX_IDENTIFIER_TYPE_US_SSN: US Social Security Number (used by sole traders). Reserved — not accepted in v1.\n - TAX_IDENTIFIER_TYPE_GB_VAT: United Kingdom VAT registration number. Reserved — not accepted in v1.\n - TAX_IDENTIFIER_TYPE_NZ_GST: New Zealand GST number (the IRD number of a GST-registered business).\nReserved — not accepted in v1."
      },
      "NullValue": {
        "type": "string",
        "enum": [
          "NULL_VALUE"
        ],
        "default": "NULL_VALUE",
        "description": "`NullValue` is a singleton enumeration to represent the null value for the\n`Value` type union.\n\nThe JSON representation for `NullValue` is JSON `null`.\n\n - NULL_VALUE: Null value."
      }
    },
    "securitySchemes": {
      "Bearer": {
        "type": "apiKey",
        "description": "Bearer token authentication. Send your token in the `Authorization` header of every request, in the form `Authorization: Bearer <token>`. Tokens are issued by Factory; contact your Factory representative to obtain one.",
        "name": "Authorization",
        "in": "header"
      }
    },
    "parameters": {
      "fields": {
        "name": "fields",
        "in": "query",
        "description": "Comma-separated response fields to include, using camelCase JSON names\n(e.g. `orderId,total.amountMicros`). Dot paths reach into nested objects\nand map transparently across arrays. Paths are relative to the resource,\nnot the response envelope; envelope keys like `nextPageToken` are always\npreserved. Only 2xx JSON responses are filtered; error bodies pass through\nunmodified. Unknown names are silently ignored. Takes precedence over\n`excludeFields` when both are provided.",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 2000
        }
      },
      "excludeFields": {
        "name": "excludeFields",
        "in": "query",
        "description": "Comma-separated response fields to exclude, using camelCase JSON names.\nDot paths and array-transparency work the same as `fields`. Paths are\nrelative to the resource, not the response envelope. Only 2xx JSON\nresponses are filtered; error bodies pass through unmodified. Ignored\nwhen `fields` is also provided.",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 2000
        }
      }
    }
  },
  "security": [
    {
      "Bearer": []
    }
  ]
}
