{
  "openapi": "3.1.1",
  "info": {
    "title": "YourSpec WPM API",
    "description": "Build and publish wiring projects from your own tools.\n\n**Authentication:** send a personal API token as `Authorization: Bearer wpm_pat_…`. Create one on the [WPM API tokens page](https://wpm.yourspec.com/account/tokens) (account menu → API Tokens). Each token has scopes (`projects:read`, `projects:write`, `projects:delete`, `connectors:read`, `connectors:write`); every operation lists the one it needs. The catalogue takes any token.\n\n**Errors** are `application/problem+json` with a stable `code` (e.g. `insufficient_scope`, `validation_failed`); validation failures list each problem with its path in `errors`.\n\n**Workflow:** create or copy a project, save its draft (`PUT …/draft`, the whole draft each time), check it with `…/draft/validate`, then publish. Look up wires, contacts and connectors in the catalogue.",
    "version": "v1"
  },
  "servers": [
    {
      "url": "https://wpmapi.yourspec.com"
    }
  ],
  "paths": {
    "/v1/catalog": {
      "get": {
        "tags": [
          "Catalog"
        ],
        "summary": "Get the catalogue",
        "description": "Everything a draft refers to: wires and cables (with their conductors), contacts, catalogue connectors (with\npositions and allowed contacts), label types, marking intervals and limits. The same for every caller and\ncached for 5 minutes. A position's `allowedContactIds` can include contacts that are no longer sold (they\nare not in `contacts`).\n\nAny personal API token works, whatever its scopes.",
        "operationId": "getCatalog",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1Catalog"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Catalog"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Catalog"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          }
        }
      }
    },
    "/v1/connectors": {
      "get": {
        "tags": [
          "Connectors"
        ],
        "summary": "List your connectors",
        "description": "Your own connector definitions, by name, paged.\n\nPersonal API tokens need the `connectors:read` scope.",
        "operationId": "listConnectors",
        "parameters": [
          {
            "name": "page",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 0
            }
          },
          {
            "name": "pageSize",
            "in": "query",
            "schema": {
              "type": "integer",
              "format": "int32",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1PageOfV1UserConnector"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1PageOfV1UserConnector"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1PageOfV1UserConnector"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          },
          "403": {
            "description": "The token lacks the `connectors:read` scope (`insufficient_scope`), or the project isn't yours (`not_owner`)."
          }
        }
      },
      "post": {
        "tags": [
          "Connectors"
        ],
        "summary": "Create a connector",
        "description": "Positions are listed in index order.\n\nPersonal API tokens need the `connectors:write` scope.",
        "operationId": "createConnector",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1UserConnectorInput"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1UserConnectorInput"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1UserConnector"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1UserConnector"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1UserConnector"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          },
          "403": {
            "description": "The token lacks the `connectors:write` scope (`insufficient_scope`), or the project isn't yours (`not_owner`)."
          }
        }
      }
    },
    "/v1/connectors/{connectorId}": {
      "get": {
        "tags": [
          "Connectors"
        ],
        "summary": "Get a connector",
        "description": "One of your connectors, with its positions and allowed contacts.\n\nPersonal API tokens need the `connectors:read` scope.",
        "operationId": "getConnector",
        "parameters": [
          {
            "name": "connectorId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1UserConnector"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1UserConnector"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1UserConnector"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          },
          "403": {
            "description": "The token lacks the `connectors:read` scope (`insufficient_scope`), or the project isn't yours (`not_owner`)."
          }
        }
      },
      "put": {
        "tags": [
          "Connectors"
        ],
        "summary": "Update a connector",
        "description": "Replaces the definition. When a project uses it, the number of positions and the allowed contacts must stay\nthe same (409 otherwise); its name, details and position labels can change.\n\nPersonal API tokens need the `connectors:write` scope.",
        "operationId": "updateConnector",
        "parameters": [
          {
            "name": "connectorId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1UserConnectorInput"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1UserConnectorInput"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1UserConnector"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1UserConnector"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1UserConnector"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          },
          "403": {
            "description": "The token lacks the `connectors:write` scope (`insufficient_scope`), or the project isn't yours (`not_owner`)."
          }
        }
      },
      "delete": {
        "tags": [
          "Connectors"
        ],
        "summary": "Delete a connector",
        "description": "Refused (409) while any project revision uses it.\n\nPersonal API tokens need the `connectors:write` scope.",
        "operationId": "deleteConnector",
        "parameters": [
          {
            "name": "connectorId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          },
          "403": {
            "description": "The token lacks the `connectors:write` scope (`insufficient_scope`), or the project isn't yours (`not_owner`)."
          }
        }
      }
    },
    "/v1/projects/{projectId}/draft": {
      "get": {
        "tags": [
          "Drafts"
        ],
        "summary": "Get the draft",
        "description": "Returns the draft, or the published content when there is no draft (`hasDraft: false`; the first save\ncreates the draft). The `ETag` header can be sent back as `If-Match` on save.\n\nPersonal API tokens need the `projects:read` scope.",
        "operationId": "getDraft",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1Draft"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Draft"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1Draft"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          },
          "403": {
            "description": "The token lacks the `projects:read` scope (`insufficient_scope`), or the project isn't yours (`not_owner`)."
          }
        }
      },
      "put": {
        "tags": [
          "Drafts"
        ],
        "summary": "Save the draft",
        "description": "The body replaces the whole draft: wires, connectors and labels left out are removed. Rows are kept when their\nIDs are repeated. Send `If-Match` with the ETag from a GET to refuse the save (412) if the draft changed\nsince. 422 when the body has errors (nothing is saved). A saved draft can still have \"incomplete\" issues that\nblock publishing; they're returned with the result.\n\nPersonal API tokens need the `projects:write` scope.",
        "operationId": "saveDraft",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1DraftInput"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1DraftInput"
              }
            }
          },
          "required": true
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1DraftSaveResult"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1DraftSaveResult"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1DraftSaveResult"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "412": {
            "description": "Precondition Failed",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          }
        }
      },
      "delete": {
        "tags": [
          "Drafts"
        ],
        "summary": "Discard the draft",
        "description": "Goes back to the published revision. A never-published project can't discard its only draft (409): delete the\nproject instead.\n\nPersonal API tokens need the `projects:write` scope.",
        "operationId": "discardDraft",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          }
        }
      }
    },
    "/v1/projects/{projectId}/draft/validate": {
      "post": {
        "tags": [
          "Drafts"
        ],
        "summary": "Validate a draft without saving",
        "description": "Checks the body if given, otherwise the current draft, and returns every issue and, when valid, the price.\nNothing is written. Always 200 when the project is yours: read `valid` and `publishable`.\n\nPersonal API tokens need the `projects:read` scope.",
        "operationId": "validateDraft",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/V1DraftInput"
                  }
                ]
              }
            },
            "text/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/V1DraftInput"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/V1DraftInput"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1ValidationResult"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1ValidationResult"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1ValidationResult"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          },
          "403": {
            "description": "The token lacks the `projects:read` scope (`insufficient_scope`), or the project isn't yours (`not_owner`)."
          }
        }
      }
    },
    "/v1/projects": {
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Create a project",
        "description": "It starts as an empty, unpublished draft (revision 1). Add content with a draft save.\n\nPersonal API tokens need the `projects:write` scope.",
        "operationId": "createProject",
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/V1ProjectCreateRequest"
              }
            },
            "application/*+json": {
              "schema": {
                "$ref": "#/components/schemas/V1ProjectCreateRequest"
              }
            }
          },
          "required": true
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1ProjectSummary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1ProjectSummary"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1ProjectSummary"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          },
          "403": {
            "description": "The token lacks the `projects:write` scope (`insufficient_scope`), or the project isn't yours (`not_owner`)."
          }
        }
      }
    },
    "/v1/projects/{projectId}/clone": {
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Copy a project",
        "description": "Copies a published project (yours, a public or unlisted one, or a template) into a new private project of\nyours. The copy starts published at revision 1 with no draft. Optional body: `{ \"name\": \"…\" }`; default\nname \"Source name (Copy)\".\n\nPersonal API tokens need the `projects:write` scope.",
        "operationId": "cloneProject",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "requestBody": {
          "content": {
            "application/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/V1CloneRequest"
                  }
                ]
              }
            },
            "text/json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/V1CloneRequest"
                  }
                ]
              }
            },
            "application/*+json": {
              "schema": {
                "oneOf": [
                  {
                    "type": "null"
                  },
                  {
                    "$ref": "#/components/schemas/V1CloneRequest"
                  }
                ]
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1ProjectSummary"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1ProjectSummary"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1ProjectSummary"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          },
          "403": {
            "description": "The token lacks the `projects:write` scope (`insufficient_scope`), or the project isn't yours (`not_owner`)."
          }
        }
      }
    },
    "/v1/projects/{projectId}/publish": {
      "post": {
        "tags": [
          "Projects"
        ],
        "summary": "Publish the draft",
        "description": "Validates the draft and makes it the live revision. Errors and incomplete wires block publishing (422 with the\nissues); warnings don't.\n\nPersonal API tokens need the `projects:write` scope.",
        "operationId": "publishProject",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "text/plain": {
                "schema": {
                  "$ref": "#/components/schemas/V1PublishResult"
                }
              },
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1PublishResult"
                }
              },
              "text/json": {
                "schema": {
                  "$ref": "#/components/schemas/V1PublishResult"
                }
              }
            }
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "422": {
            "description": "Unprocessable Entity",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          }
        }
      }
    },
    "/v1/projects/{projectId}": {
      "delete": {
        "tags": [
          "Projects"
        ],
        "summary": "Delete a project",
        "description": "Deletes the project with all its revisions. Projects that have been ordered are kept for the order history and\ncan't be deleted (409).\n\nPersonal API tokens need the `projects:delete` scope.",
        "operationId": "deleteProject",
        "parameters": [
          {
            "name": "projectId",
            "in": "path",
            "required": true,
            "schema": {
              "pattern": "^-?(?:0|[1-9]\\d*)$",
              "type": "integer",
              "format": "int32"
            }
          }
        ],
        "responses": {
          "204": {
            "description": "No Content"
          },
          "403": {
            "description": "Forbidden",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "404": {
            "description": "Not Found",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "409": {
            "description": "Conflict",
            "content": {
              "application/problem+json": {
                "schema": {
                  "$ref": "#/components/schemas/ProblemDetails"
                }
              }
            }
          },
          "401": {
            "description": "No token, or the token is malformed, unknown, expired or revoked (`unauthorized` / `invalid_token`)."
          }
        }
      }
    }
  },
  "components": {
    "schemas": {
      "ProblemDetails": {
        "type": "object",
        "properties": {
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "title": {
            "type": [
              "null",
              "string"
            ]
          },
          "status": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "detail": {
            "type": [
              "null",
              "string"
            ]
          },
          "instance": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "V1CableConductor": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "description": "Use as `conductors[].conductorId` in a draft.",
            "format": "int32"
          },
          "number": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string"
          },
          "awg": {
            "type": "integer",
            "format": "int32"
          },
          "strippable": {
            "type": "boolean",
            "description": "False for e.g. a drain or shield that can't be stripped."
          },
          "crimpable": {
            "type": "boolean"
          }
        }
      },
      "V1Catalog": {
        "required": [
          "limits",
          "wireTypes",
          "wireGauges",
          "wireColors",
          "wires",
          "contacts",
          "connectors",
          "labelTypes",
          "markIntervals"
        ],
        "type": "object",
        "properties": {
          "generatedAt": {
            "type": "string",
            "format": "date-time"
          },
          "limits": {
            "$ref": "#/components/schemas/V1CatalogLimits"
          },
          "wireTypes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1WireType"
            }
          },
          "wireGauges": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1WireGauge"
            }
          },
          "wireColors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1WireColor"
            }
          },
          "wires": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1CatalogWire"
            }
          },
          "contacts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1Contact"
            }
          },
          "connectors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1CatalogConnector"
            }
          },
          "labelTypes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1LabelType"
            }
          },
          "markIntervals": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1MarkInterval"
            }
          }
        },
        "description": "Everything needed to build a project: the IDs a draft refers to, with availability, prices and limits. Only items\nthat can be added to a project are listed."
      },
      "V1CatalogConnector": {
        "required": [
          "name",
          "positions"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string"
          },
          "mpn": {
            "type": [
              "null",
              "string"
            ]
          },
          "family": {
            "type": [
              "null",
              "string"
            ]
          },
          "gender": {
            "type": [
              "null",
              "string"
            ]
          },
          "keyway": {
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "positionCount": {
            "type": "integer",
            "format": "int32"
          },
          "positions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1ConnectorPosition"
            }
          },
          "price": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "inStock": {
            "type": "boolean"
          },
          "lowStock": {
            "type": "boolean"
          },
          "specialOrder": {
            "type": "boolean"
          },
          "sku": {
            "type": [
              "null",
              "string"
            ]
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "productUrl": {
            "type": [
              "null",
              "string"
            ]
          }
        }
      },
      "V1CatalogLimits": {
        "required": [
          "stripLengthMm",
          "cableJacketStripLengthMm"
        ],
        "type": "object",
        "properties": {
          "projectNameMaxLength": {
            "type": "integer",
            "format": "int32"
          },
          "wireNameMaxLength": {
            "type": "integer",
            "format": "int32"
          },
          "markingTextMaxLength": {
            "type": "integer",
            "format": "int32"
          },
          "labelTextMaxLength": {
            "type": "integer",
            "format": "int32"
          },
          "connectorNameMaxLength": {
            "type": "integer",
            "format": "int32"
          },
          "userConnectorNameMaxLength": {
            "type": "integer",
            "format": "int32"
          },
          "userConnectorPositionLabelMaxLength": {
            "type": "integer",
            "format": "int32"
          },
          "userConnectorMaxPositions": {
            "type": "integer",
            "format": "int32"
          },
          "maxWiresPerProject": {
            "type": "integer",
            "format": "int32"
          },
          "maxQuantity": {
            "type": "integer",
            "format": "int32"
          },
          "minWireLengthIn": {
            "type": "integer",
            "format": "int32"
          },
          "minWireLengthCm": {
            "type": "integer",
            "format": "int32"
          },
          "stripLengthMm": {
            "description": "The web editor's strip-length range for single wires (mm). Contacts have their own range.",
            "$ref": "#/components/schemas/V1Range"
          },
          "cableJacketStripLengthMm": {
            "description": "The web editor's strip-length range for a cable's outer jacket (mm).",
            "$ref": "#/components/schemas/V1Range"
          }
        }
      },
      "V1CatalogWire": {
        "required": [
          "name",
          "conductors"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32"
          },
          "typeId": {
            "type": "integer",
            "format": "int32"
          },
          "gaugeId": {
            "type": "integer",
            "format": "int32"
          },
          "colorId": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string",
            "description": "E.g. \"M22759/16 20 AWG White\"."
          },
          "pricePerFoot": {
            "type": "number",
            "description": "Base price per foot.",
            "format": "double"
          },
          "inStock": {
            "type": "boolean"
          },
          "lowStock": {
            "type": "boolean"
          },
          "specialOrder": {
            "type": "boolean",
            "description": "Ordered on demand; availability and price confirmed when ordering."
          },
          "poorMarkingContrast": {
            "type": "boolean",
            "description": "Printed marking may be hard to read on this wire."
          },
          "conductors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1CableConductor"
            },
            "description": "Empty for single-conductor wire. For a cable, one entry per inner conductor."
          }
        },
        "description": "A wire (or multi-conductor cable) that can be used in a project."
      },
      "V1CloneRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ],
            "description": "Optional name for the copy. Default: \"Source name (Copy)\", \"(Copy 1)\", … whichever is free."
          }
        }
      },
      "V1Conductor": {
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "conductorId": {
            "type": "integer",
            "description": "The conductor definition (catalogue `wires[].conductors[].id`).",
            "format": "int32"
          },
          "name": {
            "type": [
              "null",
              "string"
            ],
            "description": "At most 50 characters."
          },
          "end1": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/V1WireEnd"
              }
            ]
          },
          "end2": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/V1WireEnd"
              }
            ]
          }
        }
      },
      "V1ConnectorInstance": {
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "null",
              "integer"
            ],
            "description": "Set to keep an existing connector (from a GET); omit for a new one.",
            "format": "int32"
          },
          "key": {
            "type": [
              "null",
              "string"
            ],
            "description": "Your reference for this connector within the draft, used by wire ends (`connector.key`). Required and unique.\nConnectors returned by the API have keys like \"c123\"."
          },
          "name": {
            "type": [
              "null",
              "string"
            ],
            "description": "Required, at most 25 characters."
          },
          "standardConnectorId": {
            "type": [
              "null",
              "integer"
            ],
            "description": "A catalogue connector (`connectors[].id`). Set this or int? V1ConnectorInstance.UserConnectorId.",
            "format": "int32"
          },
          "userConnectorId": {
            "type": [
              "null",
              "integer"
            ],
            "description": "One of your own connector definitions (`/v1/connectors`).",
            "format": "int32"
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          },
          "includeWithPurchase": {
            "type": "boolean",
            "description": "Buy the connector housing with the order (catalogue connectors only)."
          }
        }
      },
      "V1ConnectorLink": {
        "type": "object",
        "properties": {
          "key": {
            "type": [
              "null",
              "string"
            ],
            "description": "The `key` of one of the draft's connectors."
          },
          "positionIndex": {
            "type": "integer",
            "description": "Zero-based position (cavity) index; see the connector's `positions[].index` and `label`.",
            "format": "int32"
          },
          "assemble": {
            "type": [
              "null",
              "boolean"
            ],
            "description": "Insert the crimped contact into the connector. Default: true when the connector is purchased\n(`includeWithPurchase`) and this end is crimped, as in the web editor."
          }
        }
      },
      "V1ConnectorPosition": {
        "required": [
          "allowedContactIds"
        ],
        "type": "object",
        "properties": {
          "index": {
            "type": "integer",
            "description": "Zero-based index, as used by wire ends' `positionIndex`.",
            "format": "int32"
          },
          "label": {
            "type": [
              "null",
              "string"
            ]
          },
          "allowedContactIds": {
            "type": "array",
            "items": {
              "type": "integer",
              "format": "int32"
            },
            "description": "Contacts allowed in this position (catalogue `contacts[].id`). Empty = no restriction."
          }
        }
      },
      "V1ConnectorPositionInput": {
        "type": "object",
        "properties": {
          "label": {
            "type": [
              "null",
              "string"
            ],
            "description": "At most 10 characters, e.g. \"A1\"."
          },
          "allowedContactIds": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "type": "integer",
              "format": "int32"
            },
            "description": "Contacts allowed in this position. Empty or omitted = no restriction."
          }
        }
      },
      "V1Contact": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string"
          },
          "sku": {
            "type": [
              "null",
              "string"
            ]
          },
          "type": {
            "type": [
              "null",
              "string"
            ]
          },
          "family": {
            "type": [
              "null",
              "string"
            ]
          },
          "wireAwgMin": {
            "type": "integer",
            "description": "Thickest wire accepted (smallest AWG number).",
            "format": "int32"
          },
          "wireAwgMax": {
            "type": "integer",
            "description": "Thinnest wire accepted (largest AWG number).",
            "format": "int32"
          },
          "crimpStripLengthMm": {
            "type": "number",
            "description": "Strip length used when this contact is crimped (mm).",
            "format": "double"
          },
          "price": {
            "type": "number",
            "format": "double"
          },
          "inStock": {
            "type": "boolean"
          },
          "lowStock": {
            "type": "boolean"
          },
          "specialOrder": {
            "type": "boolean"
          },
          "imageUrl": {
            "type": [
              "null",
              "string"
            ]
          },
          "productUrl": {
            "type": [
              "null",
              "string"
            ]
          }
        },
        "description": "A crimp contact (pin or socket)."
      },
      "V1Draft": {
        "type": "object",
        "properties": {
          "projectId": {
            "type": "integer",
            "format": "int32"
          },
          "hasDraft": {
            "type": "boolean",
            "description": "False when the project has no unpublished changes: the content shown is the published revision, and the first\nsave creates the draft."
          },
          "revisionNumber": {
            "type": "integer",
            "description": "The revision number this draft will get when published.",
            "format": "int32"
          },
          "publishedRevision": {
            "type": [
              "null",
              "integer"
            ],
            "description": "The live revision's number, or null if never published.",
            "format": "int32"
          },
          "name": {
            "type": [
              "null",
              "string"
            ],
            "description": "Required, at most 100 characters, unique among your projects."
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "isPublic": {
            "type": "boolean"
          },
          "isUnlisted": {
            "type": "boolean"
          },
          "units": {
            "type": [
              "null",
              "string"
            ],
            "description": "\"in\" or \"cm\". Applies to every wire length."
          },
          "wires": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/V1Wire"
            },
            "description": "At most 300 wires (a cable counts as one)."
          },
          "connectors": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/V1ConnectorInstance"
            }
          },
          "labels": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/V1Label"
            }
          }
        },
        "description": "A draft as returned by the API: the editable content plus read-only state."
      },
      "V1DraftInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ],
            "description": "Required, at most 100 characters, unique among your projects."
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "isPublic": {
            "type": "boolean"
          },
          "isUnlisted": {
            "type": "boolean"
          },
          "units": {
            "type": [
              "null",
              "string"
            ],
            "description": "\"in\" or \"cm\". Applies to every wire length."
          },
          "wires": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/V1Wire"
            },
            "description": "At most 300 wires (a cable counts as one)."
          },
          "connectors": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/V1ConnectorInstance"
            }
          },
          "labels": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/V1Label"
            }
          }
        },
        "description": "The editable content of a project: its details plus every wire, connector and label. A draft save (PUT) replaces\nthe whole draft with this: anything left out is removed."
      },
      "V1DraftSaveResult": {
        "required": [
          "draft",
          "issues"
        ],
        "type": "object",
        "properties": {
          "draft": {
            "$ref": "#/components/schemas/V1Draft"
          },
          "publishable": {
            "type": "boolean",
            "description": "True when nothing blocks publishing (no \"incomplete\" issues)."
          },
          "issues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1Issue"
            },
            "description": "Incomplete items and warnings (errors would have rejected the save)."
          },
          "pricing": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/V1Pricing"
              }
            ]
          }
        },
        "description": "Result of a successful draft save."
      },
      "V1Issue": {
        "required": [
          "severity",
          "code",
          "message"
        ],
        "type": "object",
        "properties": {
          "severity": {
            "type": "string",
            "description": "\"error\", \"incomplete\" or \"warning\" (see V1IssueSeverity)."
          },
          "code": {
            "type": "string",
            "description": "Machine-readable kind, e.g. \"too_long\", \"unknown_reference\", \"wire_rule\"."
          },
          "message": {
            "type": "string"
          },
          "path": {
            "type": [
              "null",
              "string"
            ],
            "description": "Where in the request body, e.g. \"wires[2].end1.connector.positionIndex\". Null = the project as a whole."
          },
          "wireIndex": {
            "type": [
              "null",
              "integer"
            ],
            "description": "Zero-based index into `wires`, when the issue is about one wire (or one of its conductors).",
            "format": "int32"
          },
          "wireId": {
            "type": [
              "null",
              "integer"
            ],
            "description": "The wire's ID, when it has one.",
            "format": "int32"
          }
        },
        "description": "One problem with a request or a project."
      },
      "V1Label": {
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "null",
              "integer"
            ],
            "format": "int32"
          },
          "labelTypeId": {
            "type": "integer",
            "description": "A catalogue label type (`labelTypes[].id`).",
            "format": "int32"
          },
          "quantity": {
            "type": "integer",
            "format": "int32"
          },
          "text": {
            "type": [
              "null",
              "string"
            ],
            "description": "Required, at most 100 characters."
          },
          "bordered": {
            "type": "boolean"
          }
        }
      },
      "V1LabelType": {
        "required": [
          "type"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32"
          },
          "type": {
            "type": "string"
          },
          "material": {
            "type": [
              "null",
              "string"
            ]
          },
          "baseColor": {
            "type": [
              "null",
              "string"
            ]
          },
          "fontColor": {
            "type": [
              "null",
              "string"
            ]
          },
          "widthIn": {
            "type": "number",
            "format": "double"
          },
          "diameterBeforeShrink": {
            "type": [
              "null",
              "string"
            ]
          },
          "diameterAfterShrink": {
            "type": [
              "null",
              "string"
            ]
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "priceEach": {
            "type": "number",
            "format": "double"
          },
          "inStock": {
            "type": "boolean"
          },
          "lowStock": {
            "type": "boolean"
          }
        }
      },
      "V1Marking": {
        "type": "object",
        "properties": {
          "text": {
            "type": [
              "null",
              "string"
            ],
            "description": "Required, at most 50 characters. Upper case marks best."
          },
          "intervalIn": {
            "type": [
              "null",
              "number"
            ],
            "description": "Repeat spacing in inches; one of the catalogue's `markIntervals`. Omit for the default.",
            "format": "double"
          },
          "vertical": {
            "type": "boolean",
            "description": "Characters read along the wire's length."
          }
        }
      },
      "V1MarkInterval": {
        "type": "object",
        "properties": {
          "intervalIn": {
            "type": "number",
            "format": "double"
          },
          "isDefault": {
            "type": "boolean"
          },
          "pricePerFoot": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "V1PageOfV1UserConnector": {
        "required": [
          "items"
        ],
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1UserConnector"
            }
          },
          "page": {
            "type": "integer",
            "description": "Zero-based page index.",
            "format": "int32"
          },
          "pageSize": {
            "type": "integer",
            "format": "int32"
          },
          "totalCount": {
            "type": "integer",
            "format": "int32"
          }
        },
        "description": "One page of a list."
      },
      "V1Pricing": {
        "required": [
          "model"
        ],
        "type": "object",
        "properties": {
          "model": {
            "type": "string"
          },
          "total": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "wire": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "contacts": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "connectors": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "connectorAssembly": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "crimping": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "stripping": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "marking": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "tinning": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "labels": {
            "type": [
              "null",
              "number"
            ],
            "format": "double"
          },
          "services": {
            "type": [
              "null",
              "number"
            ],
            "description": "All processing services together (crimping, stripping, marking, tinning, assembly).",
            "format": "double"
          }
        },
        "description": "The price of a project under its best qualifying pricing model."
      },
      "V1ProjectCreateRequest": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ],
            "description": "Required, at most 100 characters, unique among your projects."
          },
          "description": {
            "type": [
              "null",
              "string"
            ],
            "description": "Optional. Basic HTML is allowed and sanitised."
          },
          "isPublic": {
            "type": "boolean"
          },
          "isUnlisted": {
            "type": "boolean"
          },
          "units": {
            "type": [
              "null",
              "string"
            ],
            "description": "\"in\" (default) or \"cm\"."
          }
        }
      },
      "V1ProjectSummary": {
        "required": [
          "name",
          "units"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "isPublic": {
            "type": "boolean"
          },
          "isUnlisted": {
            "type": "boolean"
          },
          "units": {
            "type": "string"
          },
          "isPublished": {
            "type": "boolean",
            "description": "True once any revision has been published."
          },
          "publishedRevision": {
            "type": [
              "null",
              "integer"
            ],
            "description": "The live revision's number, or null if never published.",
            "format": "int32"
          },
          "hasDraft": {
            "type": "boolean",
            "description": "True when there are unpublished changes (a draft exists)."
          }
        },
        "description": "A project's details and state."
      },
      "V1PublishResult": {
        "required": [
          "warnings"
        ],
        "type": "object",
        "properties": {
          "projectId": {
            "type": "integer",
            "format": "int32"
          },
          "revision": {
            "type": "integer",
            "format": "int32"
          },
          "publishedAt": {
            "type": "string",
            "format": "date-time"
          },
          "warnings": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1Issue"
            },
            "description": "Warnings that did not block publishing."
          }
        }
      },
      "V1Range": {
        "type": "object",
        "properties": {
          "min": {
            "type": "number",
            "format": "double"
          },
          "max": {
            "type": "number",
            "format": "double"
          },
          "default": {
            "type": "number",
            "format": "double"
          }
        }
      },
      "V1UserConnector": {
        "required": [
          "name",
          "positions"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string"
          },
          "description": {
            "type": [
              "null",
              "string"
            ]
          },
          "mpn": {
            "type": [
              "null",
              "string"
            ]
          },
          "gender": {
            "type": [
              "null",
              "string"
            ]
          },
          "keyway": {
            "type": [
              "null",
              "string"
            ]
          },
          "positionCount": {
            "type": "integer",
            "format": "int32"
          },
          "positions": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1ConnectorPosition"
            }
          },
          "inUse": {
            "type": "boolean",
            "description": "True when any project revision uses it. Its number of positions and allowed contacts are then fixed."
          }
        },
        "description": "One of your own connector definitions, usable in your projects."
      },
      "V1UserConnectorInput": {
        "type": "object",
        "properties": {
          "name": {
            "type": [
              "null",
              "string"
            ],
            "description": "Required, at most 100 characters, unique among your connectors."
          },
          "description": {
            "type": [
              "null",
              "string"
            ],
            "description": "At most 2,000 characters."
          },
          "mpn": {
            "type": [
              "null",
              "string"
            ],
            "description": "Manufacturer part number, at most 100 characters."
          },
          "gender": {
            "type": [
              "null",
              "string"
            ],
            "description": "At most 100 characters."
          },
          "keyway": {
            "type": [
              "null",
              "string"
            ],
            "description": "At most 100 characters."
          },
          "positions": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/V1ConnectorPositionInput"
            },
            "description": "1–128 positions, in index order."
          }
        }
      },
      "V1ValidationResult": {
        "required": [
          "issues"
        ],
        "type": "object",
        "properties": {
          "valid": {
            "type": "boolean",
            "description": "True when the draft could be saved (no \"error\" issues)."
          },
          "publishable": {
            "type": "boolean",
            "description": "True when it could also be published (no \"error\" or \"incomplete\" issues)."
          },
          "issues": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/V1Issue"
            }
          },
          "pricing": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "The price, when valid.",
                "$ref": "#/components/schemas/V1Pricing"
              }
            ]
          }
        },
        "description": "Result of validating a draft without saving it."
      },
      "V1Wire": {
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "null",
              "integer"
            ],
            "description": "Set to keep an existing wire (from a GET); omit for a new wire. IDs change when a draft is first created.",
            "format": "int32"
          },
          "name": {
            "type": [
              "null",
              "string"
            ],
            "description": "At most 50 characters."
          },
          "baseWireId": {
            "type": [
              "null",
              "integer"
            ],
            "description": "A wire from the catalogue (`wires[].id`). Null = not chosen yet (the wire is then incomplete).",
            "format": "int32"
          },
          "length": {
            "type": "integer",
            "description": "Whole number in the project's units. Minimum 9 in / 23 cm.",
            "format": "int32"
          },
          "quantity": {
            "type": "integer",
            "description": "How many of this wire, 1–10,000.",
            "format": "int32"
          },
          "marking": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "Printed marking, or null for none.",
                "$ref": "#/components/schemas/V1Marking"
              }
            ]
          },
          "end1": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/V1WireEnd"
              }
            ]
          },
          "end2": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "$ref": "#/components/schemas/V1WireEnd"
              }
            ]
          },
          "conductors": {
            "type": [
              "null",
              "array"
            ],
            "items": {
              "$ref": "#/components/schemas/V1Conductor"
            },
            "description": "Cables only: one entry per conductor of the cable (catalogue `wires[].conductors`). Leave out to get every\nconductor with unprocessed ends. Must be absent for single-conductor wires."
          }
        }
      },
      "V1WireColor": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string"
          }
        }
      },
      "V1WireEnd": {
        "type": "object",
        "properties": {
          "stripLengthMm": {
            "type": "number",
            "description": "Insulation to strip in millimetres; 0 = not stripped. Ignored when crimping (the contact's strip length is used).",
            "format": "double"
          },
          "keepTail": {
            "type": "boolean",
            "description": "Leave the stripped insulation on as a tail."
          },
          "tin": {
            "type": "boolean",
            "description": "Tin the stripped conductor."
          },
          "contactId": {
            "type": [
              "null",
              "integer"
            ],
            "description": "A contact from the catalogue (`contacts[].id`) supplied with this end.",
            "format": "int32"
          },
          "crimp": {
            "type": "boolean",
            "description": "Crimp the contact onto this end (needs int? V1WireEnd.ContactId)."
          },
          "connector": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "description": "The connector position this end goes into, or null.",
                "$ref": "#/components/schemas/V1ConnectorLink"
              }
            ]
          }
        }
      },
      "V1WireGauge": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string"
          },
          "awg": {
            "type": "integer",
            "description": "AWG number (smaller = thicker).",
            "format": "int32"
          }
        }
      },
      "V1WireType": {
        "required": [
          "name"
        ],
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int32"
          },
          "name": {
            "type": "string"
          },
          "shortName": {
            "type": [
              "null",
              "string"
            ]
          },
          "specialOrder": {
            "type": "boolean"
          }
        }
      }
    },
    "securitySchemes": {
      "bearer": {
        "type": "http",
        "description": "A personal API token (wpm_pat_…). A WPM sign-in token also works.",
        "scheme": "bearer"
      }
    }
  },
  "security": [
    {
      "bearer": [ ]
    }
  ],
  "tags": [
    {
      "name": "Catalog",
      "description": "Wires, cables, contacts, connectors, label types and marking options a project can use. Any token can read it."
    },
    {
      "name": "Projects",
      "description": "Create, copy, publish and delete your projects."
    },
    {
      "name": "Drafts",
      "description": "A project's editable content: read, save (full replace), validate without saving, discard."
    },
    {
      "name": "Connectors",
      "description": "Your own connector definitions, for housings that aren't in the catalogue."
    }
  ]
}