{
  "openapi": "3.1.0",
  "info": {
    "title": "Tetradka Open API",
    "version": "1.0.0",
    "description": "Profile-scoped integration API. Obtain a profile API key from its owner and send it in X-Api-Key. Each operation requires its documented scopes."
  },
  "servers": [
    {
      "url": "https://open-api.beautybox-stage.ru/api",
      "description": "Stage API"
    }
  ],
  "paths": {
    "/open/crm/v1/booking/available-dates": {
      "get": {
        "tags": [
          "CRM Open API / booking"
        ],
        "summary": "Find dates available for an ordered service sequence",
        "description": "Requires booking.availability.read. Inclusive address-local dates; maximum 31 days by default. All explicit pairs must fit online settings, assignments, schedules, occupation and existing resource rules. Past dates or disabled booking/selected employees give an empty result after validation. Foreign/deleted or inactive/offline service references and unknown fields give 422. No reservation or data wrapper.",
        "operationId": "openCrmBookingAvailableDates",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiBookingServices"
          },
          {
            "name": "from",
            "in": "query",
            "description": "Inclusive first date",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2030-01-15"
            }
          },
          {
            "name": "to",
            "in": "query",
            "description": "Inclusive last date, not before from; the configurable limit counts both endpoints",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2030-02-14"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Available dates, possibly empty",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiBookingDates"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked API key; JWT cannot replace it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing booking.availability.read scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid range, selection, references, unknown fields or missing profile catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Request limit reached before key lookup",
            "headers": {
              "Retry-After": {
                "description": "Seconds before retry",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error or invalid server limit configuration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "booking.availability.read"
        ]
      }
    },
    "/open/crm/v1/booking/time-slots": {
      "get": {
        "tags": [
          "CRM Open API / booking"
        ],
        "summary": "Get detailed available starts for an ordered service sequence",
        "description": "Requires booking.availability.read. Uses the same selection/settings as available-dates. Initial starts follow the first employee onlineTime.aliquot, minimum five minutes. Later services keep input order and may wait for their own buffers, never for external conflicts. Past dates or disabled booking/selected employees give an empty list. Count precedes pagination; GET reserves nothing. Unknown fields give 422.",
        "operationId": "openCrmBookingTimeSlots",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiBookingServices"
          },
          {
            "name": "date",
            "in": "query",
            "description": "Calendar date in the profile address timezone",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2030-01-15"
            }
          },
          {
            "name": "skip",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0,
              "example": 0
            }
          },
          {
            "name": "take",
            "in": "query",
            "description": "Default min(50, configured maximum). Maximum defaults to 100 via PROFILE_API_BOOKING_MAX_SLOTS_PER_PAGE",
            "schema": {
              "type": "integer",
              "default": 50,
              "maximum": 100,
              "minimum": 1,
              "example": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Detailed slots and pagination; an out-of-range page can be empty with a nonzero count",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiBookingSlots"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing booking.availability.read scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid selection, date, pagination, references, unknown fields or missing profile catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Request limit reached before key lookup",
            "headers": {
              "Retry-After": {
                "description": "Seconds before retry",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected error or invalid server limit configuration",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "booking.availability.read"
        ]
      }
    },
    "/open/crm/v1/booking/visits": {
      "post": {
        "tags": [
          "CRM Open API / booking"
        ],
        "summary": "Create one online visit after checking the selected slots",
        "description": "Requires visits.write only. Creates one online visit using catalog prices/durations, with no repeats or manual overrides. Rechecks every selected interval before writing; blocked clients and unavailable starts give 422 without silently moving services. Concurrent CRM/booking writers are not locked. Uses the existing visit save/event lifecycle. Cancel with visits/{visitID}/cancel or status=cancel using booking.id.",
        "operationId": "openCrmBookingVisitStore",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileOpenApiBookingCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Created booking; booking.id is the CRM visit ID",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiBookingCreated"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing visits.write scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Same profile/booking operation checksum is in flight or recently completed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 409,
                    "message": "Request conflict"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Field errors: booking.clientID for an invalid/blocked client, booking.appointment.datetime for a past/invalid start, booking.appointment.services for an unavailable sequence, or indexed pair fields for references. Unknown fields and manual overrides are rejected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Request limit reached before key lookup",
            "headers": {
              "Retry-After": {
                "description": "Seconds before retry",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected runtime failure; the existing save/event lifecycle can already have persisted a visit. Reconcile before retrying after an ambiguous failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "visits.write"
        ]
      }
    },
    "/open/crm/v1/clients": {
      "get": {
        "tags": [
          "CRM Open API / clients"
        ],
        "summary": "List clients of the API key profile",
        "description": "Requires clients.read. name and phone retain substring matching against the stored text. phoneExact matches a full international number independently of spaces, parentheses and hyphens. It uses country calling code plus national number, with a formatted-phone fallback only for incomplete legacy details. Structured details take precedence.\n     *         No default country is inferred. Filters combine with AND. Deleted and foreign-profile clients are excluded. Duplicate matches are returned as a list; no match is 200 with count:0 and clients:[]. Results are ordered by name, then clientID; count is computed before pagination. phone and phoneExact cannot be supplied together, even when empty.",
        "operationId": "openCrmClientIndex",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "skip",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0
            },
            "example": 0
          },
          {
            "name": "take",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 15,
              "maximum": 100,
              "minimum": 1
            },
            "example": 15
          },
          {
            "name": "name",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 255
            },
            "example": "Anna"
          },
          {
            "name": "phone",
            "in": "query",
            "description": "Legacy substring filter against the stored phone text; formatting remains significant. Mutually exclusive with phoneExact.",
            "schema": {
              "type": "string",
              "maxLength": 30
            },
            "example": "7700"
          },
          {
            "name": "phoneExact",
            "in": "query",
            "description": "Full international number beginning with + and a country calling code, at most 64 characters before normalization and 15 digits in E.164. Spaces (including nonbreaking), parentheses and hyphens are allowed. Number length must be possible for its calling code;\n     *         subscriber existence is not checked. National-only numbers, extensions, letters, wildcards and simultaneous phone are 422. Encode + as %2B in the query string.",
            "schema": {
              "type": "string",
              "maxLength": 64
            },
            "example": "+447700900123"
          }
        ],
        "responses": {
          "200": {
            "description": "Profile clients",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiClientList"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing clients.read scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid query parameters: malformed/ambiguous phoneExact, impossible length/calling code, or simultaneous phone and phoneExact. errors.fields maps invalid field names to arrays of code/message entries or business-validation strings.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 422,
                    "message": "Invalid input"
                  }
                }
              }
            }
          },
          "429": {
            "description": "200 requests per 60 seconds per presented key, plus configurable per-IP limit; Retry-After is seconds until available",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "integer",
                  "example": 30
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "clients.read"
        ]
      },
      "post": {
        "tags": [
          "CRM Open API / clients"
        ],
        "summary": "Create a client in the API key profile",
        "description": "Requires clients.write. The profile is derived from X-Api-Key. Use multipart/form-data to upload an avatar; phone and phoneDetails are mutually exclusive.",
        "operationId": "openCrmClientStore",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ProfileOpenApiClientCreateInput"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileOpenApiClientCreateInput"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Created client",
            "headers": {
              "Location": {
                "description": "Path to the created client",
                "schema": {
                  "type": "string",
                  "example": "/api/open/crm/v1/clients/42"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiClient"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing clients.write scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid client data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 422,
                    "message": "Invalid input"
                  }
                }
              }
            }
          },
          "429": {
            "description": "200 requests per 60 seconds per presented key, plus configurable per-IP limit; Retry-After is seconds until available",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "integer",
                  "example": 30
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "clients.write"
        ]
      }
    },
    "/open/crm/v1/clients/{clientId}": {
      "get": {
        "tags": [
          "CRM Open API / clients"
        ],
        "summary": "Get one client from the API key profile",
        "description": "Requires clients.read. A client in another profile returns 404.",
        "operationId": "openCrmClientShow",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ],
        "responses": {
          "200": {
            "description": "Client",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiClient"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing clients.read scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Client absent from this profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "429": {
            "description": "200 requests per 60 seconds per presented key, plus configurable per-IP limit; Retry-After is seconds until available",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "integer",
                  "example": 30
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "clients.read"
        ]
      },
      "put": {
        "tags": [
          "CRM Open API / clients"
        ],
        "summary": "Update a client in the API key profile",
        "description": "Requires clients.write. Omitted fields are unchanged. For multipart avatar updates, send POST with _method=PUT because PHP does not parse multipart PUT request bodies.",
        "operationId": "openCrmClientUpdate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ProfileOpenApiClientInput"
              }
            },
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileOpenApiClientInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated client",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiClient"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing clients.write scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Client absent from this profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid client data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 422,
                    "message": "Invalid input"
                  }
                }
              }
            }
          },
          "429": {
            "description": "200 requests per 60 seconds per presented key, plus configurable per-IP limit; Retry-After is seconds until available",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "integer",
                  "example": 30
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "clients.write"
        ]
      },
      "delete": {
        "tags": [
          "CRM Open API / clients"
        ],
        "summary": "Soft delete a client in the API key profile",
        "description": "Requires clients.delete. A client in another profile returns 404.",
        "operationId": "openCrmClientDestroy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "clientId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            },
            "example": 42
          }
        ],
        "responses": {
          "204": {
            "description": "Client deleted"
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing clients.delete scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Client absent from this profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "429": {
            "description": "200 requests per 60 seconds per presented key, plus configurable per-IP limit; Retry-After is seconds until available",
            "headers": {
              "Retry-After": {
                "description": "Seconds to wait",
                "schema": {
                  "type": "integer",
                  "example": 30
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "clients.delete"
        ]
      }
    },
    "/open/crm/v1/employees": {
      "get": {
        "tags": [
          "CRM Open API / employees"
        ],
        "summary": "List profile employees and owner",
        "description": "Requires employees.read. Includes undeleted profile employees and the owner exactly once without attaching them. Independent of schedules or available windows. Filters combine with AND; sorting is name then userID. Eligible assignments use the same active service/category rules as nested services. Missing catalog: unfiltered directory remains readable with zero servicesCount; serviceID or hasServices requires a catalog.\n     *         profileID/addressID/templateID are prohibited. No data wrapper.",
        "operationId": "openCrmEmployeeIndex",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/EmployeeDirectoryName"
          },
          {
            "$ref": "#/components/parameters/EmployeeDirectoryOnline"
          },
          {
            "$ref": "#/components/parameters/EmployeeDirectorySkip"
          },
          {
            "$ref": "#/components/parameters/EmployeeDirectoryTake"
          },
          {
            "name": "serviceID",
            "in": "query",
            "description": "Current assignment to a service in this profile catalog; foreign or missing reference is 422",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          },
          {
            "name": "hasServices",
            "in": "query",
            "description": "Whether eligible assigned services exist; requires a catalog",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "example": "true"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiDirectoryEmployeeList"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid query, protected selectors, references or required catalog; errors map fields to message arrays",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Open API request limit",
            "headers": {
              "Retry-After": {
                "description": "Seconds before retry",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "employees.read"
        ]
      }
    },
    "/open/crm/v1/employees/{employeeID}": {
      "get": {
        "tags": [
          "CRM Open API / employees"
        ],
        "summary": "Get an employee directory card",
        "description": "Requires employees.read. Returns the same fields as a list item at the JSON root, without administrative/contact data or embedded services. Owner is readable without membership; no GET attaches users. Foreign/missing/deleted IDs return the same 404 before query validation. Missing catalog gives servicesCount=0. profileID/addressID/templateID are prohibited.",
        "operationId": "openCrmEmployeeShow",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "employeeID",
            "in": "path",
            "description": "Owner or employee of the key profile",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 45
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiDirectoryEmployee"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Employee absent from this profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid query, protected selectors, references or required catalog; errors map fields to message arrays",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Open API request limit",
            "headers": {
              "Retry-After": {
                "description": "Seconds before retry",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "employees.read"
        ]
      }
    },
    "/open/crm/v1/employees/{employeeID}/services": {
      "get": {
        "tags": [
          "CRM Open API / employees"
        ],
        "summary": "List services assigned to an employee",
        "description": "Requires BOTH employees.read AND services.read. Uses the first active address catalog of the key profile. Only current assignments to undeleted active/status=1 services in undeleted status=1 categories qualify. Duplicate assignment rows count once. No implicit owner assignments or employee-specific price/duration overrides. Filters combine with AND; sorting is service sort then serviceID. Owner price permissions apply. Count precedes\n     *         pagination. Empty assignments return count:0, services:[]. Missing catalog is 422. Foreign/missing/deleted employee is 404 before query validation. profileID/addressID/templateID are prohibited.",
        "operationId": "openCrmEmployeeServices",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "employeeID",
            "in": "path",
            "description": "Owner or employee of the key profile",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 45
            }
          },
          {
            "$ref": "#/components/parameters/EmployeeDirectoryName"
          },
          {
            "$ref": "#/components/parameters/EmployeeDirectoryOnline"
          },
          {
            "$ref": "#/components/parameters/EmployeeDirectorySkip"
          },
          {
            "$ref": "#/components/parameters/EmployeeDirectoryTake"
          },
          {
            "name": "categoryID",
            "in": "query",
            "description": "Category in the selected catalog; foreign or missing reference is 422",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 10
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiDirectoryServiceList"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing required scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Employee absent from this profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid query, protected selectors, references or required catalog; errors map fields to message arrays",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Open API request limit",
            "headers": {
              "Retry-After": {
                "description": "Seconds before retry",
                "schema": {
                  "type": "integer"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "employees.read",
          "services.read"
        ]
      }
    },
    "/open/crm/v1/employees/free-windows": {
      "get": {
        "tags": [
          "CRM Open API / free windows"
        ],
        "summary": "List employees with free windows today",
        "description": "Requires employees.free-windows.read. Root JSON array with employee/text/times, without data or separate link. Main-address timezone. Candidates have schedules today or tomorrow; windows are calculated for today. Owner's existing all-show-employees permission controls selection. An optional employeeID only checks profile membership and does not filter the list. Empty result is [].",
        "operationId": "openCrmEmployeeFreeWindowsIndex",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "employeeID",
            "in": "query",
            "description": "If supplied, must identify the profile owner or an undeleted employee in the key profile; foreign, missing or malformed references return 403",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 8
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Root employee list, possibly empty",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProfileOpenApiFreeWindowsListItem"
                  }
                }
              }
            }
          },
          "401": {
            "description": "Key missing, invalid, expired or revoked; JWT does not replace the key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Scope denied, employeeID outside profile, or main address missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "CRM validation-error structure; index currently has no period validator and invalid employeeID returns 403",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiFreeWindowsValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Existing request limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "employees.free-windows.read"
        ]
      }
    },
    "/open/crm/v1/employees/free-windows/{free_window}": {
      "get": {
        "tags": [
          "CRM Open API / free windows"
        ],
        "summary": "Get free windows of one profile employee",
        "description": "Requires employees.free-windows.read. Supply both dateFrom/dateTo or days; the complete date pair takes priority. Partial date pair falls back to days. No upper period limit; days <= 0 means today. Preserves CRM range boundaries and formatting. Root object employee/text/link, without times or data. Missing employee selection after access check\n     *         returns []; a selected employee without windows returns text:null. URL employee and optional query employeeID are checked independently before period validation.",
        "operationId": "openCrmEmployeeFreeWindowsShow",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "free_window",
            "in": "path",
            "description": "Employee userID, not a window or schedule ID; must belong to the key profile",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 8
            }
          },
          {
            "name": "employeeID",
            "in": "query",
            "description": "Additional membership check within the key profile; never substitutes the URL employee or selects another profile",
            "required": false,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 8
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "schema": {
              "type": "integer",
              "example": 2
            }
          },
          {
            "name": "dateFrom",
            "in": "query",
            "description": "Start date in the CRM YYYY-MM-DD format",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date",
              "maxLength": 10,
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
              "example": "2030-01-15"
            }
          },
          {
            "name": "dateTo",
            "in": "query",
            "description": "End date; CRM adds one day at midnight when constructing the period",
            "required": false,
            "schema": {
              "type": "string",
              "format": "date",
              "maxLength": 10,
              "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}$",
              "example": "2030-01-16"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Employee free windows, or [] for an empty employee selection",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ProfileOpenApiFreeWindowsShow"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileOpenApiFreeWindowsEmptyList"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "Key missing, invalid, expired or revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Scope denied, URL/query employee not in profile, or period not supplied",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid CRM period fields",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiFreeWindowsValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Existing request limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error; includes missing main-address configuration as in CRM show",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "employees.free-windows.read"
        ]
      }
    },
    "/open/crm/v1/analytics/future-profit": {
      "get": {
        "tags": [
          "CRM Open API / future profit"
        ],
        "summary": "Future profit for today, week and month",
        "description": "Requires analytics.future-profit.read. Reuses CRM futureProfit: inclusive dates, statuses new/working/done/completed, existing owner employee permissions, sum of all bid prices. Today uses the server application timezone. No query filters, pagination or data wrapper. JWT and profileID cannot select another profile.",
        "operationId": "openCrmFutureProfitSummary",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "X-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "example": "Flutter"
            }
          },
          {
            "name": "X-Meta-App-Version-Major",
            "in": "header",
            "required": false,
            "schema": {
              "type": "integer",
              "example": 38
            }
          }
        ],
        "responses": {
          "200": {
            "description": "CRM summary contract",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiFutureProfitSummary"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Key lacks analytics.future-profit.read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Open API throttle exceeded before key validation",
            "headers": {
              "Retry-After": {
                "description": "Seconds before retry",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "analytics.future-profit.read"
        ]
      }
    },
    "/open/crm/v1/analytics/future-profit/period": {
      "get": {
        "tags": [
          "CRM Open API / future profit"
        ],
        "summary": "Future profit for an inclusive custom period",
        "description": "Requires analytics.future-profit.read. Reuses CRM futureProfit and period validator. Historical dates are allowed; reversed periods return zero. Sum of all bid prices on visits in statuses new/working/done/completed using existing owner employee permissions. Extra query parameters are ignored. No data wrapper.",
        "operationId": "openCrmFutureProfitPeriod",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "dateFrom",
            "in": "query",
            "description": "Inclusive first day, same CRM validation",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "maxLength": 10,
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "example": "2030-04-01"
            }
          },
          {
            "name": "dateTo",
            "in": "query",
            "description": "Inclusive last day, no date order validation",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "maxLength": 10,
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$",
              "example": "2030-04-30"
            }
          },
          {
            "name": "X-Client",
            "in": "header",
            "required": false,
            "schema": {
              "type": "string",
              "example": "Flutter"
            }
          },
          {
            "name": "X-Meta-App-Version-Major",
            "in": "header",
            "required": false,
            "schema": {
              "type": "integer",
              "example": 38
            }
          }
        ],
        "responses": {
          "200": {
            "description": "CRM period contract",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiFutureProfitPeriod"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Key lacks analytics.future-profit.read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Missing or invalid dateFrom/dateTo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiFutureProfitValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Open API throttle exceeded before key validation",
            "headers": {
              "Retry-After": {
                "description": "Seconds before retry",
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "analytics.future-profit.read"
        ]
      }
    },
    "/open/crm/v1/schedules": {
      "get": {
        "tags": [
          "CRM Open API / schedules"
        ],
        "summary": "List profile schedules in an inclusive date range",
        "description": "Requires schedules.read. Stable order: date, userID, addressID, scheduleID. Empty result is an object with data: [].",
        "operationId": "openCrmSchedulesIndex",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "dateFrom",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2030-10-10"
            }
          },
          {
            "name": "dateTo",
            "in": "query",
            "description": "Must be on or after dateFrom",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "example": "2030-10-31"
            }
          },
          {
            "name": "userID",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 789
            }
          },
          {
            "name": "addressID",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 456
            }
          },
          {
            "name": "skip",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0,
              "example": 0
            }
          },
          {
            "name": "take",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 100,
              "maximum": 100,
              "minimum": 1,
              "example": 31
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Schedule list wrapped in data",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiScheduleList"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Required schedules scope missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid fields, foreign references, disconnected employee or forbidden past creation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 422,
                    "message": "Invalid input"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "schedules.read"
        ]
      },
      "post": {
        "tags": [
          "CRM Open API / schedules"
        ],
        "summary": "Create or update one schedule date",
        "description": "Requires schedules.write. Find one row by userID + addressID + date using CRM first() semantics; keep other duplicates. type=0 deletes one found row or does nothing and returns {}.",
        "operationId": "openCrmSchedulesStore",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileOpenApiScheduleInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated schedule, or empty object for type=0",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ProfileOpenApiSchedule"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileOpenApiEmptyObject"
                    }
                  ]
                }
              }
            }
          },
          "201": {
            "description": "Created schedule",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiSchedule"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Required schedules scope missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid fields, foreign references, disconnected employee or forbidden past creation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 422,
                    "message": "Invalid input"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "schedules.write"
        ]
      }
    },
    "/open/crm/v1/schedules/{scheduleId}": {
      "get": {
        "tags": [
          "CRM Open API / schedules"
        ],
        "summary": "Get one schedule",
        "description": "Requires schedules.read. Foreign IDs return 404.",
        "operationId": "openCrmSchedulesShow",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "scheduleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Schedule",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiSchedule"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Required schedules scope missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Schedule not found in the key profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid fields, foreign references, disconnected employee or forbidden past creation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 422,
                    "message": "Invalid input"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "schedules.read"
        ]
      },
      "put": {
        "tags": [
          "CRM Open API / schedules"
        ],
        "summary": "Edit one schedule by ID",
        "description": "Requires schedules.write. Edits only the selected row, including past dates. Identity fields are immutable. type=0 deletes the selected row and returns {}. Other duplicates are preserved.",
        "operationId": "openCrmSchedulesUpdate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "scheduleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileOpenApiScheduleUpdate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Updated schedule, or empty object for type=0",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ProfileOpenApiSchedule"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileOpenApiEmptyObject"
                    }
                  ]
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Required schedules scope missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Schedule not found in the key profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid fields, foreign references, disconnected employee or forbidden past creation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 422,
                    "message": "Invalid input"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "schedules.write"
        ]
      },
      "delete": {
        "tags": [
          "CRM Open API / schedules"
        ],
        "summary": "Delete one schedule by ID",
        "description": "Requires schedules.delete. Visits on this day do not block deletion. Keeps observer logging and existing payroll jobs. Repeat deletion returns 404.",
        "operationId": "openCrmSchedulesDestroy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "scheduleId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted; no response body"
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Required schedules scope missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Schedule not found in the key profile",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid fields, foreign references, disconnected employee or forbidden past creation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 422,
                    "message": "Invalid input"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "schedules.delete"
        ]
      }
    },
    "/open/crm/v1/schedules/bulk": {
      "post": {
        "tags": [
          "CRM Open API / schedules"
        ],
        "summary": "Create or update several schedule dates atomically",
        "description": "Requires schedules.write. Require unique dates within the payload; duplicates return 422 before any writes. Execute dates in order using CRM first()/update/create/delete semantics. Existing past dates can be updated; a missing past date for type=1/2 rejects the whole batch before writes. type=0 on a missing past date is a no-op. Never attaches employees.",
        "operationId": "openCrmSchedulesBulk",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileOpenApiScheduleBulkInput"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Empty JSON object; no summary",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiEmptyObject"
                }
              }
            }
          },
          "401": {
            "description": "API key missing, invalid, expired or revoked",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Required schedules scope missing",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid fields, duplicate dates, bulk limit exceeded, foreign references, disconnected employee or forbidden past creation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 422,
                    "message": "Invalid input"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Request limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "schedules.write"
        ]
      }
    },
    "/open/crm/v1/service-categories": {
      "get": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "List service categories with CRM v4 filters",
        "description": "Requires service-categories.read. CRM v4 envelope, service-title search, isOnline filter and exclusion. Empty categories are omitted. count precedes pagination; page size is configured by the server (15 by default). Category photos are never returned.",
        "operationId": "openCrmServiceCategoryIndex",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "skip",
            "in": "query",
            "description": "Offset; page size is configured by the server (15 by default)",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0,
              "example": 0
            }
          },
          {
            "name": "exclude[]",
            "in": "query",
            "description": "IDs to exclude",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            }
          },
          {
            "name": "name",
            "in": "query",
            "description": "Search nested service title/customerTitle",
            "schema": {
              "type": "string",
              "example": "Haircut"
            }
          },
          {
            "name": "isOnline",
            "in": "query",
            "schema": {
              "type": "string",
              "enum": [
                "true",
                "false"
              ],
              "example": "true"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful catalog response",
            "content": {
              "application/json": {
                "schema": {
                  "required": [
                    "count",
                    "categories"
                  ],
                  "properties": {
                    "count": {
                      "type": "integer",
                      "example": 1
                    },
                    "categories": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OpenApiCatalogCategory"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or references",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "service-categories.read"
        ]
      },
      "post": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "Create category without photos",
        "description": "Requires service-categories.write. First active profile address and template are server selected; profileID/addressID/templateID/moderation/moderationComment are forbidden. POST checkSum/timeCache use the same per-profile/operation rules as service creation (default 1440 seconds); updates do not reserve a checksum. Duplicate name returns 422. Photo fields are forbidden by presence, including null. Existing legacy photos are preserved.",
        "operationId": "openCrmServiceCategoryStore",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiLegacyClient"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenApiCategoryWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Category saved",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "categoryID": {
                      "type": "integer",
                      "example": 123
                    },
                    "name": {
                      "type": "string",
                      "example": "Haircuts"
                    },
                    "sort": {
                      "type": "integer",
                      "example": 1
                    },
                    "templateID": {
                      "type": "integer",
                      "readOnly": true,
                      "example": 12
                    },
                    "colorID": {
                      "type": "integer",
                      "example": 1
                    },
                    "status": {
                      "type": "integer",
                      "example": 1
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Duplicate checkSum",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 409,
                    "message": "Request conflict"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input, forbidden photo fields or duplicate name",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "service-categories.write"
        ]
      }
    },
    "/open/crm/v1/service-categories/{categoryID}": {
      "get": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "Get a category with its services",
        "description": "Requires service-categories.read. Uses the CRM category show representation, with nested service previews and no category photo. Missing, foreign and deleted IDs return the same 404.",
        "operationId": "openCrmServiceCategoryShow",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiLegacyClient"
          },
          {
            "name": "categoryID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful catalog response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogCategory"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or references",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "service-categories.read"
        ]
      },
      "put": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "Update category without photos",
        "description": "Requires service-categories.write. First active profile address and template are server selected; profileID/addressID/templateID/moderation/moderationComment are forbidden. POST checkSum/timeCache use the same per-profile/operation rules as service creation (default 1440 seconds); updates do not reserve a checksum. Duplicate name returns 422. Photo fields are forbidden by presence, including null. Existing legacy photos are preserved.",
        "operationId": "openCrmServiceCategoryUpdate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiLegacyClient"
          },
          {
            "name": "categoryID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenApiCategoryWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Category saved",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "example": "/categories/123"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "categoryID": {
                      "type": "integer",
                      "example": 123
                    },
                    "name": {
                      "type": "string",
                      "example": "Haircuts"
                    },
                    "sort": {
                      "type": "integer",
                      "example": 1
                    },
                    "color": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "example": "#0096fb"
                    },
                    "status": {
                      "type": "integer",
                      "example": 1
                    },
                    "isHidden": {
                      "type": "boolean",
                      "example": false
                    },
                    "isEmpty": {
                      "type": "boolean",
                      "example": true
                    },
                    "servicesCount": {
                      "type": "integer",
                      "example": 0
                    },
                    "services": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      },
                      "example": []
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input, forbidden photo fields or duplicate name",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "service-categories.write"
        ]
      },
      "delete": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "Soft-delete a category and its services",
        "description": "Requires service-categories.delete. Rechecks membership restrictions without relying on verify-delete. Database changes and durable tasks commit atomically; file cleanup, indexing and notifications run after commit. Historical visit links are preserved.",
        "operationId": "openCrmServiceCategoryDestroy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "categoryID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted; no response body"
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected profile catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input, foreign references, membership restriction or invalid photo ownership",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "service-categories.delete"
        ]
      },
      "patch": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "Update category without photos",
        "description": "Requires service-categories.write. First active profile address and template are server selected; profileID/addressID/templateID/moderation/moderationComment are forbidden. POST checkSum/timeCache use the same per-profile/operation rules as service creation (default 1440 seconds); updates do not reserve a checksum. Duplicate name returns 422. Photo fields are forbidden by presence, including null. Existing legacy photos are preserved.",
        "operationId": "openCrmServiceCategoryUpdatePatch",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiLegacyClient"
          },
          {
            "name": "categoryID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenApiCategoryWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Category saved",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "example": "/categories/123"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "categoryID": {
                      "type": "integer",
                      "example": 123
                    },
                    "name": {
                      "type": "string",
                      "example": "Haircuts"
                    },
                    "sort": {
                      "type": "integer",
                      "example": 1
                    },
                    "color": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "example": "#0096fb"
                    },
                    "status": {
                      "type": "integer",
                      "example": 1
                    },
                    "isHidden": {
                      "type": "boolean",
                      "example": false
                    },
                    "isEmpty": {
                      "type": "boolean",
                      "example": true
                    },
                    "servicesCount": {
                      "type": "integer",
                      "example": 0
                    },
                    "services": {
                      "type": "array",
                      "items": {
                        "type": "object"
                      },
                      "example": []
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input, forbidden photo fields or duplicate name",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "service-categories.write"
        ]
      }
    },
    "/open/crm/v1/service-categories/bulk": {
      "post": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "Create multiple service-categories",
        "description": "Requires service-categories.write. Atomic bulk creation without category photos. Root JSON array; checkSum/timeCache are query metadata. Default maximum 100 items, shared with services through PROFILE_API_SERVICES_BULK_MAX_ITEMS. Empty arrays are accepted. Oversized batches, repeated names in the batch and existing names return 422. No partial DB writes are retained after failure; notifications and indexing run after commit.",
        "operationId": "openCrmServiceCategoryBulk",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiLegacyClient"
          },
          {
            "name": "checkSum",
            "in": "query",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "example": "bulk-create-123"
            }
          },
          {
            "name": "timeCache",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1440,
              "maximum": 2147483647,
              "minimum": 0,
              "example": 1440
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "required": [
                    "name",
                    "color"
                  ],
                  "properties": {
                    "name": {
                      "type": "string",
                      "maxLength": 255,
                      "example": "Haircuts"
                    },
                    "color": {
                      "type": "string",
                      "maxLength": 11,
                      "example": "#0096fb"
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Batch created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "categories": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OpenApiCatalogCategory"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing write scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Duplicate checkSum",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 409,
                    "message": "Request conflict"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid batch, references, photos, metadata, size or category names; no records created",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "service-categories.write"
        ]
      }
    },
    "/open/crm/v1/service-categories/{categoryID}/verify-delete": {
      "get": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "Check category deletion restrictions",
        "description": "Requires service-categories.read. Lists memberships of this profile using services in the category. DELETE independently rechecks all restrictions.",
        "operationId": "openCrmServiceCategoryVerifyDelete",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "categoryID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful catalog response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogDeleteIssues"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or references",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "service-categories.read"
        ]
      }
    },
    "/open/crm/v1/service-categories/sortable": {
      "put": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "Sort service-categories",
        "description": "Requires service-categories.write. All IDs must belong to the selected profile catalog and are rechecked under lock before any write. The ordered list is limited by PROFILE_API_SERVICES_BULK_MAX_ITEMS (default 100). The entire sort commits atomically; indexing runs after commit. Omitted or empty categories is a no-op; duplicate IDs retain their final position.",
        "operationId": "openCrmServiceCategorySortable",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "categories": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "maximum": 9999999999,
                      "minimum": 1
                    },
                    "example": [
                      123,
                      124
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Empty JSON array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  },
                  "maxItems": 0
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected profile catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input, foreign references, membership restriction or invalid photo ownership",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "service-categories.write"
        ]
      }
    },
    "/open/crm/v1/service-categories/{categoryID}/services": {
      "get": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "List services of a category with pagination",
        "description": "Requires services.read, independently of service-categories.read. CRM paginated category services representation; page size is configured by the server (15 by default). count precedes pagination; service photos/employees are empty in this short representation.",
        "operationId": "openCrmServiceCategoryServices",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "categoryID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          },
          {
            "name": "skip",
            "in": "query",
            "description": "Offset; page size is configured by the server (15 by default)",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0,
              "example": 0
            }
          },
          {
            "name": "exclude[]",
            "in": "query",
            "description": "IDs to exclude",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful catalog response",
            "content": {
              "application/json": {
                "schema": {
                  "required": [
                    "count",
                    "services"
                  ],
                  "properties": {
                    "count": {
                      "type": "integer",
                      "example": 1
                    },
                    "services": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OpenApiCatalogService"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or references",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.read"
        ]
      }
    },
    "/open/crm/v1/service-categories/{categoryID}/hide": {
      "put": {
        "tags": [
          "CRM Open API / service categories"
        ],
        "summary": "Hide or show a category and its services",
        "description": "Requires service-categories.write. Sets hidden on the category and active on its services within the selected profile catalog atomically. hidden accepts a boolean, 0/1 or true/false strings; null or an omitted value shows the category. Category photo fields are forbidden.",
        "operationId": "openCrmServiceCategoryHide",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "categoryID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "hidden": {
                    "type": [
                      "boolean",
                      "null"
                    ],
                    "default": false,
                    "example": true
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Empty JSON array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  },
                  "maxItems": 0
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected profile catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input, foreign references, membership restriction or invalid photo ownership",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "service-categories.write"
        ]
      }
    },
    "/open/crm/v1/services": {
      "get": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "List services in one category",
        "description": "Requires services.read. categoryID must belong to the first active address catalog of the key profile. No pagination; skip and exclude are validated but do not filter this legacy list. Price visibility follows owner permissions.",
        "operationId": "openCrmServiceIndex",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "categoryID",
            "in": "query",
            "description": "Category in the selected profile catalog",
            "required": true,
            "schema": {
              "type": "integer",
              "maximum": 9999999999,
              "example": 12
            }
          },
          {
            "name": "skip",
            "in": "query",
            "description": "Validated legacy parameter; does not paginate this list",
            "schema": {
              "type": "integer",
              "default": 0,
              "minimum": 0,
              "example": 0
            }
          },
          {
            "name": "exclude[]",
            "in": "query",
            "description": "Validated legacy parameter; does not exclude services from this list",
            "style": "form",
            "explode": true,
            "schema": {
              "type": "array",
              "items": {
                "type": "integer"
              }
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful catalog response",
            "content": {
              "application/json": {
                "schema": {
                  "required": [
                    "services"
                  ],
                  "properties": {
                    "services": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OpenApiCatalogService"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or references",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.read"
        ]
      },
      "post": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "Create one service with optional signed photos",
        "description": "Requires services.write. categoryID, title, price and time are required on both POST and PUT/PATCH. JSON savePhotos descriptors replace multipart; obtain them from upload-url, PUT bytes with the returned headers, then attach the unchanged descriptor. Employees/resources must belong to this profile. profileID, addressID, templateID, albumID,\n     *         moderation and moderationComment cannot be supplied. POST checkSum uses a profile and operation namespace, with a configurable timeCache in seconds (default 1440); empty or zero checkSum disables it. Updates do not reserve a checksum. Database writes commit atomically; cleanup/index/notifications are durable after-commit tasks.",
        "operationId": "openCrmServiceStore",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiLegacyClient"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenApiServiceWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Full service card at root",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "example": "/categories/123"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogService"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Duplicate checksum",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 409,
                    "message": "Request conflict"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or references",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.write"
        ]
      }
    },
    "/open/crm/v1/services/{serviceID}": {
      "get": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "Get the full service card",
        "description": "Requires services.read. Full CRM edit representation is returned under service; there is no edit endpoint. Foreign, missing and deleted services return the same 404. Employee/resource/photo references are restricted to the catalog profile.",
        "operationId": "openCrmServiceShow",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "serviceID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful catalog response",
            "content": {
              "application/json": {
                "schema": {
                  "required": [
                    "service"
                  ],
                  "properties": {
                    "service": {
                      "$ref": "#/components/schemas/OpenApiCatalogService"
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or references",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.read"
        ]
      },
      "put": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "Update one service with optional signed photos",
        "description": "Requires services.write. categoryID, title, price and time are required on both POST and PUT/PATCH. JSON savePhotos descriptors replace multipart; obtain them from upload-url, PUT bytes with the returned headers, then attach the unchanged descriptor. Employees/resources must belong to this profile. profileID, addressID, templateID, albumID,\n     *         moderation and moderationComment cannot be supplied. POST checkSum uses a profile and operation namespace, with a configurable timeCache in seconds (default 1440); empty or zero checkSum disables it. Updates do not reserve a checksum. Database writes commit atomically; cleanup/index/notifications are durable after-commit tasks.",
        "operationId": "openCrmServiceUpdate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiLegacyClient"
          },
          {
            "name": "serviceID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenApiServiceWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Full service card at root",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "example": "/categories/123"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogService"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or references",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.write"
        ]
      },
      "delete": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "Soft-delete a service",
        "description": "Requires services.delete. Rechecks membership restrictions without relying on verify-delete. Database changes and durable tasks commit atomically; file cleanup, indexing and notifications run after commit. Historical visit links are preserved.",
        "operationId": "openCrmServiceDestroy",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "serviceID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "responses": {
          "204": {
            "description": "Deleted; no response body"
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected profile catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input, foreign references, membership restriction or invalid photo ownership",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.delete"
        ]
      },
      "patch": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "Update one service with optional signed photos",
        "description": "Requires services.write. categoryID, title, price and time are required on both POST and PUT/PATCH. JSON savePhotos descriptors replace multipart; obtain them from upload-url, PUT bytes with the returned headers, then attach the unchanged descriptor. Employees/resources must belong to this profile. profileID, addressID, templateID, albumID,\n     *         moderation and moderationComment cannot be supplied. POST checkSum uses a profile and operation namespace, with a configurable timeCache in seconds (default 1440); empty or zero checkSum disables it. Updates do not reserve a checksum. Database writes commit atomically; cleanup/index/notifications are durable after-commit tasks.",
        "operationId": "openCrmServiceUpdatePatch",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiLegacyClient"
          },
          {
            "name": "serviceID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenApiServiceWrite"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Full service card at root",
            "headers": {
              "Location": {
                "schema": {
                  "type": "string",
                  "example": "/categories/123"
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogService"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or references",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.write"
        ]
      }
    },
    "/open/crm/v1/services/bulk": {
      "post": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "Create multiple services",
        "description": "Atomic bulk creation without photos. Root JSON array; checkSum/timeCache are query metadata. Requires services.write. PROFILE_API_SERVICES_BULK_MAX_ITEMS defaults to 100. An empty array is accepted; repeated service entries create separate services. Description, memo and resources are validated but not persisted by short creation. Photos and server-managed fields are forbidden. A runtime DB failure rolls back the whole batch; external tasks run after commit.",
        "operationId": "openCrmServiceBulk",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiLegacyClient"
          },
          {
            "name": "checkSum",
            "in": "query",
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "example": "bulk-create-123"
            }
          },
          {
            "name": "timeCache",
            "in": "query",
            "schema": {
              "type": "integer",
              "default": 1440,
              "maximum": 2147483647,
              "minimum": 0,
              "example": 1440
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "array",
                "items": {
                  "required": [
                    "categoryID",
                    "title",
                    "price",
                    "time"
                  ],
                  "properties": {
                    "categoryID": {
                      "type": "integer",
                      "example": 123
                    },
                    "title": {
                      "type": "string",
                      "maxLength": 255,
                      "example": "Haircut"
                    },
                    "price": {
                      "type": "integer",
                      "maximum": 9999999999,
                      "example": 1500
                    },
                    "priceType": {
                      "example": 0,
                      "oneOf": [
                        {
                          "type": "integer",
                          "enum": [
                            0,
                            1,
                            2
                          ]
                        },
                        {
                          "type": "string",
                          "enum": [
                            "fixed",
                            "free",
                            "from"
                          ]
                        }
                      ]
                    },
                    "time": {
                      "type": "integer",
                      "maximum": 9999999999,
                      "example": 60
                    },
                    "description": {
                      "description": "Validated, not saved by CRM short creation",
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 1000,
                      "example": "Details"
                    },
                    "memo": {
                      "description": "Validated, not saved by CRM short creation",
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 2000,
                      "example": "Memo"
                    },
                    "employees": {
                      "type": [
                        "string",
                        "null"
                      ],
                      "maxLength": 10000,
                      "example": "45,46"
                    },
                    "resources": {
                      "description": "Validated, not saved by CRM short creation",
                      "example": "12",
                      "oneOf": [
                        {
                          "type": "string"
                        },
                        {
                          "type": "integer"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "type": "object"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Batch created",
            "content": {
              "application/json": {
                "schema": {
                  "properties": {
                    "services": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/OpenApiCatalogService"
                      }
                    }
                  },
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing write scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Duplicate checkSum",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 409,
                    "message": "Request conflict"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid batch, references, photos, metadata or size; no records created",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.write"
        ]
      }
    },
    "/open/crm/v1/services/{serviceID}/verify-delete": {
      "get": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "Check service deletion restrictions",
        "description": "Requires services.read. Lists profile memberships using this service. An empty list means no current membership restriction; DELETE rechecks restrictions before writing.",
        "operationId": "openCrmServiceVerifyDelete",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "serviceID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 123
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful catalog response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OpenApiCatalogDeleteIssues"
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input or references",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.read"
        ]
      }
    },
    "/open/crm/v1/services/sortable": {
      "put": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "Sort services",
        "description": "Requires services.write. All IDs must belong to the selected profile catalog and are rechecked under lock before any write. The ordered list is limited by PROFILE_API_SERVICES_BULK_MAX_ITEMS (default 100). The entire sort commits atomically; indexing runs after commit.",
        "operationId": "openCrmServiceSortable",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "services"
                ],
                "properties": {
                  "services": {
                    "type": "array",
                    "items": {
                      "type": "integer",
                      "maximum": 9999999999,
                      "minimum": 1
                    },
                    "minItems": 1,
                    "example": [
                      123,
                      124
                    ]
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Empty JSON array",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "object"
                  },
                  "maxItems": 0
                }
              }
            }
          },
          "401": {
            "description": "Invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Object not found in the selected profile catalog",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input, foreign references, membership restriction or invalid photo ownership",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.write"
        ]
      }
    },
    "/open/crm/v1/services/photos/upload-url": {
      "post": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "Issue temporary PUT URLs for service photos",
        "description": "Requires services.write. Uploads belong to the API key profile and its first active address/catalog. For 1–10 files, PUT bytes to the returned URL using its headers, then pass name/hash/mimeType/createdAt to service savePhotos. URL expires in one hour. Attach validates static JPEG/PNG/WebP, actual size up to 20 MiB, pixel limit 20 million and total\n     *         service photo count 10; the signed PUT itself is not a size reservation. Consumption prevents a second attach. profileID/addressID/templateID/disk/path/target are forbidden.",
        "operationId": "openCrmServicePhotoUploadUrl",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ServicePhotoUploadRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Upload metadata",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ServicePhotoUploadResponse"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input, references or upload",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Technical failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.write"
        ]
      }
    },
    "/open/crm/v1/services/photos/upload": {
      "delete": {
        "tags": [
          "CRM Open API / services"
        ],
        "summary": "Revoke a pending service photo upload",
        "description": "Requires services.write. Revokes a pending upload owned by the current profile; an unknown, foreign or consumed hash returns 422. Repeated revocation succeeds. The storage PUT URL can remain usable until its one-hour expiry, but revoked metadata can never be attached. Stage cleanup runs later.",
        "operationId": "openCrmServicePhotoDeleteUpload",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "hash"
                ],
                "properties": {
                  "hash": {
                    "type": "string",
                    "pattern": "^[a-f0-9]{64}$",
                    "example": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Revoked; empty JSON object",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiEmptyObject"
                }
              }
            }
          },
          "401": {
            "description": "Authentication required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing permissions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid input, references or upload",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/OpenApiCatalogValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Too many requests",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Technical failure",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "services.write"
        ]
      }
    },
    "/open/crm/v1/visits": {
      "get": {
        "tags": [
          "CRM Open API / visits"
        ],
        "summary": "List visits using the CRM v3 index contract",
        "description": "Requires visits.read. Reuses CRM v3 selection, grouping and serializer within the key profile. userIDs overrides userID; both are validated. Each employee User belongs to one profile. No data wrapper. Owner permissions control employees and phones; prices remain visible as in v3 index. scheduled=planned and skip currently do not narrow or paginate results, matching CRM.",
        "operationId": "openCrmVisitsIndex",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date",
              "maxLength": 10,
              "example": "2030-04-10"
            }
          },
          {
            "name": "userID",
            "in": "query",
            "description": "Owner or employee in key profile; explicitly selected soft-deleted employees remain supported. Does not attach owner to employees.",
            "required": false,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "maximum": 999999999,
              "example": 8
            }
          },
          {
            "name": "userIDs[]",
            "in": "query",
            "description": "Overrides userID. Empty array means no filter. All IDs must belong to key profile, including explicitly selected soft-deleted employees. Duplicates are accepted.",
            "required": false,
            "schema": {
              "type": [
                "array",
                "null"
              ],
              "items": {
                "type": "integer",
                "maximum": 999999999,
                "example": 8
              }
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "Preserves CRM ends_with validation and exact SQL status filter before normalization; suffixes new,working,done,cancel,completed accepted. completed becomes done in output; noShow becomes cancel.",
            "required": false,
            "schema": {
              "type": [
                "string",
                "null"
              ],
              "pattern": "(new|working|done|cancel|completed)$",
              "example": "new"
            }
          },
          {
            "name": "showCompleted",
            "in": "query",
            "description": "Include the completed group, which stays empty after normalization into done",
            "required": false,
            "schema": {
              "type": "string",
              "default": "false",
              "enum": [
                "true",
                "false"
              ],
              "example": "true"
            }
          },
          {
            "name": "scheduled",
            "in": "query",
            "description": "planned filter is currently disabled in CRM; both values select the same employees",
            "required": false,
            "schema": {
              "type": "string",
              "default": "all",
              "enum": [
                "all",
                "planned"
              ],
              "example": "planned"
            }
          },
          {
            "name": "skip",
            "in": "query",
            "description": "Integer passed through to CRM method. Current CRM query does not apply offset; negative integers are accepted.",
            "required": false,
            "schema": {
              "type": [
                "integer",
                "null"
              ],
              "example": 0
            }
          },
          {
            "name": "X-Client",
            "in": "header",
            "description": "Preserves CRM serializer client behavior",
            "required": false,
            "schema": {
              "type": "string",
              "example": "Flutter"
            }
          },
          {
            "name": "X-Meta-App-Version-Major",
            "in": "header",
            "required": false,
            "schema": {
              "type": "integer",
              "example": 38
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Status groups, including empty groups, without pagination or data wrapper",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiVisitsIndex"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked API key; JWT cannot replace it",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Key lacks visits.read",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "422": {
            "description": "CRM validation errors; also malformed arrays/skip and missing or foreign employee references",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiVisitsValidationError"
                }
              }
            }
          },
          "429": {
            "description": "Existing throttle before key lookup",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "visits.read"
        ]
      },
      "post": {
        "tags": [
          "CRM Open API / visits"
        ],
        "summary": "Create a visit or repeat series using sequential CRM v3 writes",
        "description": "Requires visits.write. Profile comes exclusively from the API key. Sequential production CRM v3 execution; no series transaction/outbox. Mid-operation failure can leave partial writes and immediate side effects. CRM endpoints are unchanged.",
        "operationId": "openCrmVisitsStore",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiVisitClient"
          },
          {
            "$ref": "#/components/parameters/OpenApiVisitClientVersion"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileOpenApiVisitCreate"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One root object, same fields as CRM v3 show",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiVisitShow"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Scope visits.write required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "409": {
            "description": "Duplicate POST reservation or completed checksum; no checksum reflected",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 409,
                    "message": "Request conflict"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation or CRM mutation rejection",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ProfileOpenApiVisitsValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Existing throttle before key lookup",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error; partial v3 writes may exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "visits.write"
        ]
      }
    },
    "/open/crm/v1/visits/{visitID}": {
      "get": {
        "tags": [
          "CRM Open API / visits"
        ],
        "summary": "One visit using the CRM v3 show serializer",
        "description": "Requires visits.read. Profile comes exclusively from the API key. Uses the CRM v3 show serializer. Foreign, missing, deleted and damaged cross-profile relations return the same 404. This request performs no visit mutations.",
        "operationId": "openCrmVisitsShow",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiVisitClient"
          },
          {
            "$ref": "#/components/parameters/OpenApiVisitClientVersion"
          },
          {
            "name": "visitID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "One root object, same fields as CRM v3 show",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiVisitShow"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Scope visits.read required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Missing, deleted, foreign visit or damaged cross-profile relation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Existing throttle before key lookup",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "visits.read"
        ]
      },
      "put": {
        "tags": [
          "CRM Open API / visits"
        ],
        "summary": "Edit a visit or series using the shared CRM v3 branches",
        "description": "Requires visits.write. Profile comes exclusively from the API key. Sequential production CRM v3 execution; no series transaction/outbox. Mid-operation failure can leave partial writes and immediate side effects. CRM endpoints are unchanged.",
        "operationId": "openCrmVisitsUpdate",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "$ref": "#/components/parameters/OpenApiVisitClient"
          },
          {
            "$ref": "#/components/parameters/OpenApiVisitClientVersion"
          },
          {
            "name": "visitID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 100
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ProfileOpenApiVisitWrite"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "One root object, same fields as CRM v3 show",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiVisitShow"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Scope visits.write required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Missing, deleted, foreign visit or damaged cross-profile relation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Validation or CRM mutation rejection; update may return errors.code/message/count/alias=ask_update_type",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ProfileOpenApiVisitsValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Existing throttle before key lookup",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error; partial v3 writes may exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "visits.write"
        ]
      }
    },
    "/open/crm/v1/visits/{visitID}/status": {
      "put": {
        "tags": [
          "CRM Open API / visits"
        ],
        "summary": "Change status; cancel is used instead of DELETE",
        "description": "Requires visits.write. Profile comes exclusively from the API key. Sequential production CRM v3 execution; no series transaction/outbox. Mid-operation failure can leave partial writes and immediate side effects. CRM endpoints are unchanged.",
        "operationId": "openCrmVisitsStatus",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "visitID",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "example": 100
            }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "required": [
                  "status"
                ],
                "properties": {
                  "status": {
                    "type": "string",
                    "enum": [
                      "new",
                      "working",
                      "done",
                      "cancel",
                      "completed",
                      "noShow"
                    ],
                    "example": "cancel"
                  },
                  "cancelUpcoming": {
                    "description": "Optional Laravel boolean; null is invalid",
                    "example": true,
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "integer",
                        "enum": [
                          0,
                          1
                        ]
                      },
                      {
                        "type": "string",
                        "enum": [
                          "0",
                          "1"
                        ]
                      }
                    ]
                  },
                  "updateType": {
                    "description": "future forces cancelUpcoming even when false",
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "single",
                      "future"
                    ],
                    "example": "future"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Status updated; empty body"
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Scope visits.write required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Missing, deleted, foreign visit or damaged cross-profile relation",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid status/options, completed visit, or missing eligible sale when requesting completed",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ProfileOpenApiVisitsValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Existing throttle before key lookup",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error; partial v3 writes may exist",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "visits.write"
        ]
      }
    },
    "/open/crm/v1/visits/{visitID}/cancel": {
      "post": {
        "tags": [
          "CRM Open API / visits"
        ],
        "summary": "Cancel a visit created by ordinary or booking store",
        "description": "Requires visits.write only. Same cancellation rules for ordinary and booking-store visits; status is fixed to cancel. No body is required; only the two documented options are consumed. By default, cancel and detach this occurrence; future options include later profile-owned occurrences. Completed visits return 422. Repeating cancellation retains existing history/event behavior. No DELETE or availability check.",
        "operationId": "openCrmVisitsCancel",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          },
          {
            "name": "visitID",
            "in": "path",
            "description": "Use booking.id from booking store, or visitID from ordinary visit store",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1,
              "example": 301
            }
          }
        ],
        "requestBody": {
          "required": false,
          "content": {
            "application/json": {
              "schema": {
                "properties": {
                  "cancelUpcoming": {
                    "description": "Optional Laravel boolean: true/false, 1/0 or their numeric string forms; null is invalid",
                    "example": false,
                    "oneOf": [
                      {
                        "type": "boolean"
                      },
                      {
                        "type": "integer",
                        "enum": [
                          0,
                          1
                        ]
                      },
                      {
                        "type": "string",
                        "enum": [
                          "0",
                          "1"
                        ]
                      }
                    ]
                  },
                  "updateType": {
                    "description": "future forces cancellation of later occurrences; single does not override cancelUpcoming=true",
                    "type": [
                      "string",
                      "null"
                    ],
                    "enum": [
                      "single",
                      "future"
                    ],
                    "example": "single"
                  }
                },
                "type": "object"
              }
            }
          }
        },
        "responses": {
          "204": {
            "description": "Canceled; empty body"
          },
          "401": {
            "description": "Missing, invalid, expired or revoked API key; JWT alone is insufficient",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Missing visits.write scope",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "404": {
            "description": "Visit absent from this profile or damaged cross-profile references",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 404,
                    "message": "Not found"
                  }
                }
              }
            }
          },
          "422": {
            "description": "Invalid cancellation options or completed visit",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/ProfileOpenApiVisitsValidationError"
                    },
                    {
                      "$ref": "#/components/schemas/ProfileApiError"
                    }
                  ]
                }
              }
            }
          },
          "429": {
            "description": "Request limit reached before key lookup",
            "headers": {
              "Retry-After": {
                "schema": {
                  "type": "integer",
                  "example": 60
                }
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Unexpected failure; existing status/event lifecycle can already have made changes",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "visits.write"
        ]
      }
    },
    "/open/crm/v1/visits/repeats": {
      "get": {
        "tags": [
          "CRM Open API / visits"
        ],
        "summary": "CRM repeatsV2 options; request payload uses aliases",
        "description": "Requires visits.read. Returns the localized repeat frequency/end dictionaries used by ordinary visit store/update, not existing series or visit data. Use aliases in mutation payloads. Booking store does not accept repeat options.",
        "operationId": "openCrmVisitsRepeats",
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenApiLocale"
          }
        ],
        "responses": {
          "200": {
            "description": "Localized options with value/text/alias; untilDate first in end",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileOpenApiVisitRepeats"
                }
              }
            }
          },
          "401": {
            "description": "Missing, invalid, expired or revoked key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 401,
                    "message": "Authentication required"
                  }
                }
              }
            }
          },
          "403": {
            "description": "Scope visits.read required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 403,
                    "message": "Forbidden"
                  }
                }
              }
            }
          },
          "429": {
            "description": "Existing throttle before key lookup",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 429,
                    "message": "Request limit reached"
                  }
                }
              }
            }
          },
          "500": {
            "description": "Internal error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProfileApiError"
                },
                "example": {
                  "errors": {
                    "code": 500,
                    "message": "Internal server error"
                  }
                }
              }
            }
          }
        },
        "security": [
          {
            "profileApiKey": []
          }
        ],
        "x-required-scopes": [
          "visits.read"
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "profileApiKey": {
        "type": "apiKey",
        "description": "CRM profile API key",
        "name": "X-Api-Key",
        "in": "header"
      }
    },
    "parameters": {
      "OpenApiLocale": {
        "name": "X-Locale",
        "in": "header",
        "description": "Supported two-letter message locale. Defaults to ru; unsupported or malformed values fall back to ru. Does not change dates, IDs or response envelopes.",
        "required": false,
        "schema": {
          "type": "string",
          "default": "ru",
          "pattern": "^[a-z]{2}$",
          "example": "en"
        }
      },
      "OpenApiBookingServices": {
        "name": "services",
        "in": "query",
        "description": "Ordered indexed query array: services[0][serviceID]=101&services[0][employeeID]=8&services[1][serviceID]=102&services[1][employeeID]=9. Do not send a JSON string. Every occurrence is required; repeated pairs are allowed and count toward the limit. Default maximum 10, configurable by PROFILE_API_BOOKING_MAX_SERVICES, capped at the visit limit of 10. Numeric query values use the normal Laravel integer rule.",
        "required": true,
        "style": "deepObject",
        "explode": true,
        "schema": {
          "maxProperties": 10,
          "minProperties": 1,
          "type": "object",
          "additionalProperties": {
            "$ref": "#/components/schemas/ProfileOpenApiBookingPair"
          }
        },
        "example": {
          "1": {
            "serviceID": 101,
            "employeeID": 8
          },
          "2": {
            "serviceID": 102,
            "employeeID": 9
          }
        }
      },
      "EmployeeDirectoryName": {
        "name": "name",
        "in": "query",
        "description": "Name substring; service reads also search customerTitle",
        "schema": {
          "type": "string",
          "maxLength": 255,
          "example": "Anna"
        }
      },
      "EmployeeDirectoryOnline": {
        "name": "isOnline",
        "in": "query",
        "description": "Filter the employee online setting on index, or service online flag on assigned-service reads",
        "schema": {
          "type": "string",
          "enum": [
            "true",
            "false"
          ],
          "example": "true"
        }
      },
      "EmployeeDirectorySkip": {
        "name": "skip",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 0,
          "maximum": 2147483647,
          "minimum": 0,
          "example": 0
        }
      },
      "EmployeeDirectoryTake": {
        "name": "take",
        "in": "query",
        "schema": {
          "type": "integer",
          "default": 15,
          "maximum": 100,
          "minimum": 1,
          "example": 15
        }
      },
      "OpenApiLegacyClient": {
        "name": "X-Client",
        "in": "header",
        "description": "Flutter preserves null fields; legacy catalog mutation/category show responses otherwise convert null to an empty string.",
        "schema": {
          "type": "string",
          "example": "Flutter"
        }
      },
      "OpenApiVisitClient": {
        "name": "X-Client",
        "in": "header",
        "description": "Optional existing CRM serializer compatibility. Flutter preserves nullable fields and applies its visit status/date representation",
        "schema": {
          "type": "string",
          "example": "Flutter"
        }
      },
      "OpenApiVisitClientVersion": {
        "name": "X-Meta-App-Version-Major",
        "in": "header",
        "description": "Optional Flutter major version; 38+ uses the first bid's local SQL datetime in visit.date",
        "schema": {
          "type": "integer",
          "example": 38
        }
      }
    },
    "schemas": {
      "ProfileOpenApiBookingPair": {
        "required": [
          "serviceID",
          "employeeID"
        ],
        "properties": {
          "serviceID": {
            "description": "Active online service in the profile's first active address catalog and an active category",
            "type": "integer",
            "minimum": 1,
            "example": 101
          },
          "employeeID": {
            "description": "Explicit undeleted employee of the key profile or its owner. No automatic selection or attachment",
            "type": "integer",
            "minimum": 1,
            "example": 8
          }
        },
        "type": "object",
        "additionalProperties": false
      },
      "ProfileOpenApiBookingDates": {
        "required": [
          "availableDates"
        ],
        "properties": {
          "availableDates": {
            "description": "Unique ascending dates with at least one complete available sequence; empty array when none qualify",
            "type": "array",
            "items": {
              "required": [
                "date"
              ],
              "properties": {
                "date": {
                  "type": "string",
                  "format": "date",
                  "example": "2030-01-15"
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "ProfileApiError": {
        "required": [
          "errors"
        ],
        "properties": {
          "errors": {
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "integer",
                "example": 403
              },
              "message": {
                "type": "string",
                "example": "Forbidden"
              },
              "fields": {
                "description": "Validation messages grouped by field; present for wrapped 422 responses. FormRequest endpoints instead put field errors directly under errors",
                "type": "object",
                "additionalProperties": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ProfileApiValidationMessage"
                  }
                }
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "ProfileApiValidationMessage": {
        "description": "Existing validation entry: standard CustomValidator code/message object or an explicit business-validation string",
        "oneOf": [
          {
            "type": "string",
            "example": "Invalid value"
          },
          {
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "example": "0"
              },
              "message": {
                "type": "string",
                "example": "The field is required"
              }
            },
            "type": "object"
          }
        ]
      },
      "OpenApiCatalogValidationError": {
        "required": [
          "errors"
        ],
        "properties": {
          "errors": {
            "description": "FormRequest messages keyed by field or bulk item path; bulk duplicate names can use CRM code/message entries",
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "oneOf": [
                  {
                    "type": "string"
                  },
                  {
                    "properties": {
                      "code": {
                        "type": "string"
                      },
                      "message": {
                        "type": "string"
                      }
                    },
                    "type": "object"
                  }
                ]
              }
            }
          }
        },
        "type": "object"
      },
      "ProfileOpenApiBookingSlots": {
        "required": [
          "availableTimeSlots",
          "meta"
        ],
        "properties": {
          "availableTimeSlots": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProfileOpenApiBookingSlot"
            }
          },
          "meta": {
            "required": [
              "count",
              "skip",
              "take"
            ],
            "properties": {
              "count": {
                "description": "Number of all valid starts on this date before pagination",
                "type": "integer",
                "minimum": 0,
                "example": 9
              },
              "skip": {
                "type": "integer",
                "minimum": 0,
                "example": 0
              },
              "take": {
                "description": "Applied page size",
                "type": "integer",
                "minimum": 1,
                "example": 50
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiBookingSlot": {
        "description": "The same employee waits for their previous blockTime; a different employee may start at the preceding service end if available. endAt excludes the last trailing buffer. Every requested occurrence is retained in order.",
        "required": [
          "datetime",
          "endAt",
          "services"
        ],
        "properties": {
          "datetime": {
            "type": "string",
            "format": "date-time",
            "example": "2030-01-15T09:00:00+03:00"
          },
          "endAt": {
            "description": "End of the last service; includes waits between services but not the last trailing buffer",
            "type": "string",
            "format": "date-time",
            "example": "2030-01-15T10:15:00+03:00"
          },
          "services": {
            "description": "One interval for each selected occurrence, in input order",
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProfileOpenApiBookingInterval"
            },
            "maxItems": 10,
            "minItems": 1
          }
        },
        "type": "object"
      },
      "ProfileOpenApiBookingInterval": {
        "required": [
          "serviceID",
          "employeeID",
          "datetime",
          "endAt"
        ],
        "properties": {
          "serviceID": {
            "type": "integer",
            "example": 101
          },
          "employeeID": {
            "type": "integer",
            "example": 8
          },
          "datetime": {
            "description": "Service start in the main address timezone, with offset",
            "type": "string",
            "format": "date-time",
            "example": "2030-01-15T09:00:00+03:00"
          },
          "endAt": {
            "description": "Client-visible service end, excluding its trailing blockTime",
            "type": "string",
            "format": "date-time",
            "example": "2030-01-15T09:30:00+03:00"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiBookingCreate": {
        "required": [
          "booking"
        ],
        "properties": {
          "checkSum": {
            "description": "Optional profile/operation reservation shared by keys of the same profile. Null/empty disables it; the string 0 is a real checksum. Success is protected for 15 seconds, an in-flight lock has a finite 300-second lease. No response replay",
            "type": [
              "string",
              "null"
            ],
            "example": "booking-request-123"
          },
          "booking": {
            "required": [
              "clientID",
              "appointment"
            ],
            "properties": {
              "clientID": {
                "description": "Existing undeleted client of the key profile; online-blocked clients cannot book",
                "type": "integer",
                "minimum": 1,
                "example": 42
              },
              "comment": {
                "type": [
                  "string",
                  "null"
                ],
                "maxLength": 2000,
                "example": "First visit"
              },
              "appointment": {
                "required": [
                  "datetime",
                  "services"
                ],
                "properties": {
                  "datetime": {
                    "description": "Selected first slot start; seconds and an explicit offset or Z are required. Converted to the address timezone. Fractional seconds are not accepted",
                    "type": "string",
                    "format": "date-time",
                    "pattern": "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}(Z|[+-](0[0-9]|1[0-4]):[0-5][0-9])$",
                    "example": "2030-01-15T09:00:00+03:00"
                  },
                  "services": {
                    "description": "Same ordered explicit pairs as availability reads, including any repetitions. Configurable maximum, capped at 10",
                    "type": "array",
                    "items": {
                      "$ref": "#/components/schemas/ProfileOpenApiBookingPair"
                    },
                    "maxItems": 10,
                    "minItems": 1
                  }
                },
                "type": "object",
                "additionalProperties": false
              }
            },
            "type": "object",
            "additionalProperties": false
          }
        },
        "type": "object",
        "additionalProperties": false
      },
      "ProfileOpenApiBookingCreated": {
        "required": [
          "booking"
        ],
        "properties": {
          "booking": {
            "required": [
              "id",
              "status",
              "serviceIDs",
              "datetime",
              "endAt",
              "services"
            ],
            "properties": {
              "id": {
                "description": "Ordinary visit ID for visits show/update/status/cancel",
                "type": "integer",
                "example": 301
              },
              "status": {
                "description": "Booking response alias, not the visit status used by the visits namespace",
                "type": "string",
                "enum": [
                  "created"
                ],
                "example": "created"
              },
              "serviceIDs": {
                "description": "Selected service IDs in occurrence order; repetitions are retained",
                "type": "array",
                "items": {
                  "type": "integer",
                  "example": 101
                },
                "maxItems": 10,
                "minItems": 1
              },
              "employeeID": {
                "description": "Present only when all occurrences have the same employee",
                "type": "integer",
                "example": 8
              },
              "datetime": {
                "type": "string",
                "format": "date-time",
                "example": "2030-01-15T09:00:00+03:00"
              },
              "endAt": {
                "type": "string",
                "format": "date-time",
                "example": "2030-01-15T10:15:00+03:00"
              },
              "services": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ProfileOpenApiBookingInterval"
                },
                "maxItems": 10,
                "minItems": 1
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiClientList": {
        "required": [
          "count",
          "clients"
        ],
        "properties": {
          "count": {
            "description": "Number of matching clients before skip and take",
            "type": "integer",
            "example": 1
          },
          "clients": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProfileOpenApiClient"
            }
          }
        },
        "type": "object"
      },
      "ProfileOpenApiClient": {
        "description": "Client representation shared with CRM: gender and avatar have the same values and URL format.",
        "required": [
          "clientID",
          "name",
          "phone",
          "phoneDetails",
          "email",
          "type",
          "birthday",
          "gender",
          "avatar"
        ],
        "properties": {
          "clientID": {
            "type": "integer",
            "example": 42
          },
          "name": {
            "type": "string",
            "example": "Anna Ivanova"
          },
          "phone": {
            "type": [
              "string",
              "null"
            ],
            "example": "+447700900123"
          },
          "phoneDetails": {
            "description": "Normalized international phone components, or null when the client has no number",
            "properties": {
              "countryCode": {
                "type": "string",
                "example": "GB"
              },
              "phoneCode": {
                "type": "string",
                "example": "44"
              },
              "number": {
                "type": "string",
                "example": "7700900123"
              }
            },
            "type": [
              "object",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "example": "anna@example.com"
          },
          "type": {
            "type": [
              "integer",
              "null"
            ],
            "example": 12
          },
          "birthday": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "example": "1996-03-23"
          },
          "gender": {
            "description": "CRM gender value",
            "type": [
              "integer",
              "null"
            ],
            "enum": [
              0,
              1,
              2
            ],
            "example": 2
          },
          "avatar": {
            "description": "Absolute avatar URL, or null",
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "example": "https://example.com/avatars/client.jpg"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiClientCreateInput": {
        "description": "Client creation requires name. Use either phone or phoneDetails.",
        "allOf": [
          {
            "$ref": "#/components/schemas/ProfileOpenApiClientInput"
          },
          {
            "required": [
              "name"
            ],
            "type": "object"
          }
        ]
      },
      "ProfileOpenApiClientInput": {
        "description": "On update, omitted fields stay unchanged. Unknown fields, including profileID, addressID, number, and avatar, are ignored.",
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255,
            "example": "Anna Ivanova"
          },
          "phone": {
            "description": "CRM Russian phone format; use phoneDetails for international numbers",
            "type": [
              "string",
              "null"
            ],
            "example": "+7 (927) 927-92-79"
          },
          "phoneDetails": {
            "description": "Use instead of phone for international numbers. countryCode must exist in the CRM country table; phoneCode, when provided, must match that country. Cannot be sent together with a nonempty phone.",
            "required": [
              "countryCode",
              "number"
            ],
            "properties": {
              "countryCode": {
                "type": "string",
                "maxLength": 2,
                "minLength": 2,
                "example": "GB"
              },
              "phoneCode": {
                "type": "string",
                "maxLength": 6,
                "example": "44"
              },
              "number": {
                "type": "string",
                "pattern": "^[0-9]{4,20}$",
                "example": "7700900123"
              }
            },
            "type": [
              "object",
              "null"
            ]
          },
          "email": {
            "type": [
              "string",
              "null"
            ],
            "format": "email",
            "maxLength": 255,
            "example": "anna@example.com"
          },
          "type": {
            "description": "Client type ID from this profile",
            "type": [
              "integer",
              "null"
            ],
            "example": 12
          },
          "birthday": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "example": "1996-03-23"
          },
          "gender": {
            "type": [
              "integer",
              "null"
            ],
            "enum": [
              0,
              1,
              2
            ],
            "example": 2
          },
          "avatarUpdate": {
            "description": "When true, upload or transform avatar; requires crop and rotate, plus file when no avatar exists",
            "type": "string",
            "enum": [
              "true",
              "false",
              "1",
              "0"
            ],
            "example": "false"
          },
          "deleteFile": {
            "description": "When true, remove avatar",
            "type": "string",
            "enum": [
              "true",
              "false",
              "1",
              "0"
            ],
            "example": "false"
          },
          "file": {
            "description": "Multipart avatar upload: JPG, JPEG, PNG, or WebP, at most 20,000 KB",
            "type": [
              "string",
              "null"
            ],
            "format": "binary"
          },
          "crop": {
            "description": "JSON string with crop.x, crop.y, crop.width, crop.height; nonnegative x/y and positive width/height",
            "type": "string"
          },
          "rotate": {
            "type": "integer",
            "maximum": 360,
            "minimum": -360,
            "example": 0
          }
        },
        "type": "object"
      },
      "OpenApiDirectoryEmployeeList": {
        "required": [
          "count",
          "employees"
        ],
        "properties": {
          "count": {
            "description": "Matches before pagination",
            "type": "integer",
            "example": 1
          },
          "employees": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OpenApiDirectoryEmployee"
            }
          }
        },
        "type": "object"
      },
      "OpenApiDirectoryEmployee": {
        "required": [
          "userID",
          "name",
          "avatar",
          "positionName",
          "publicComment",
          "isOwner",
          "isOnline",
          "servicesCount"
        ],
        "properties": {
          "userID": {
            "description": "User ID, also used as masterID in visits",
            "type": "integer",
            "example": 45
          },
          "name": {
            "type": "string",
            "example": "Anna"
          },
          "avatar": {
            "description": "Absolute cropped 120x120 avatar URL",
            "type": [
              "string",
              "null"
            ],
            "format": "uri",
            "example": "https://example.com/avatar.jpg"
          },
          "positionName": {
            "type": [
              "string",
              "null"
            ],
            "example": "Hairdresser"
          },
          "publicComment": {
            "type": [
              "string",
              "null"
            ],
            "example": "Haircuts and colouring"
          },
          "isOwner": {
            "type": "boolean",
            "example": false
          },
          "isOnline": {
            "description": "Online-booking setting, false if missing; earliest onlineTimeID wins for legacy duplicates. Does not guarantee a free slot",
            "type": "boolean",
            "example": true
          },
          "servicesCount": {
            "description": "Distinct eligible assigned services; independent of schedules and service pagination. Zero without a catalog",
            "type": "integer",
            "minimum": 0,
            "example": 4
          }
        },
        "type": "object"
      },
      "OpenApiDirectoryServiceList": {
        "required": [
          "count",
          "services"
        ],
        "properties": {
          "count": {
            "description": "Distinct matches before pagination",
            "type": "integer",
            "example": 1
          },
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OpenApiDirectoryService"
            }
          }
        },
        "type": "object"
      },
      "OpenApiDirectoryService": {
        "required": [
          "serviceID",
          "categoryID",
          "title",
          "customerTitle",
          "price",
          "priceType",
          "priceTypeAlias",
          "currency",
          "time",
          "isBlockTime",
          "blockTime",
          "isOnline"
        ],
        "properties": {
          "serviceID": {
            "type": "integer",
            "example": 123
          },
          "categoryID": {
            "type": "integer",
            "example": 10
          },
          "title": {
            "type": "string",
            "example": "Haircut"
          },
          "customerTitle": {
            "type": [
              "string",
              "null"
            ],
            "example": "Ladies haircut"
          },
          "price": {
            "description": "Catalog price; null when owner price permissions hide it",
            "type": [
              "integer",
              "null"
            ],
            "example": 1500
          },
          "priceType": {
            "type": [
              "integer",
              "null"
            ],
            "enum": [
              0,
              1,
              2
            ],
            "example": 0
          },
          "priceTypeAlias": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "fixed",
              "free",
              "from"
            ],
            "example": "fixed"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "example": "RUB"
          },
          "time": {
            "description": "Service duration in minutes, excluding the separate blockTime",
            "type": "integer",
            "example": 60
          },
          "isBlockTime": {
            "type": "boolean",
            "example": true
          },
          "blockTime": {
            "description": "Separate block duration in minutes",
            "type": "integer",
            "example": 15
          },
          "isOnline": {
            "description": "Service online-booking flag, not slot availability",
            "type": "boolean",
            "example": true
          }
        },
        "type": "object"
      },
      "ProfileOpenApiFreeWindowsListItem": {
        "required": [
          "employee",
          "text",
          "times"
        ],
        "properties": {
          "employee": {
            "$ref": "#/components/schemas/ProfileOpenApiFreeWindowsEmployee"
          },
          "text": {
            "description": "Localized CRM text including employee, available times and booking URL; newlines and emoji are preserved",
            "type": "string",
            "example": "Свободные окна для записи на\\nсегодня👇\\n\\nАнна, Мастер:\\n\\n09:00 09:15\\n\\nЗаписаться онлайн:\\nhttps://example.com/online/studio?employeeID=8"
          },
          "times": {
            "type": "array",
            "items": {
              "type": "string",
              "example": "09:00"
            },
            "example": [
              "09:00",
              "09:15"
            ]
          }
        },
        "type": "object"
      },
      "ProfileOpenApiFreeWindowsEmployee": {
        "description": "CRM employee after enrichment. Raw access_token and passwords are hidden. Roles, categories, positions and links are limited to the API key profile. inviteLink reads an existing invitation; no invitation is created.",
        "required": [
          "userID",
          "name",
          "phoneActive",
          "associatePhone",
          "isOnline",
          "avatar",
          "colorID",
          "payrollRuleID",
          "position",
          "positionName",
          "inviteLink",
          "employeeCategories",
          "role",
          "roleName",
          "roles"
        ],
        "properties": {
          "userID": {
            "type": "integer",
            "example": 8
          },
          "name": {
            "type": "string",
            "example": "Анна"
          },
          "phoneActive": {
            "type": "integer",
            "enum": [
              0,
              1
            ],
            "example": 1
          },
          "associatePhone": {
            "type": [
              "string",
              "null"
            ],
            "example": "+7 (999) 123-45-67"
          },
          "isOnline": {
            "type": "boolean",
            "example": true
          },
          "avatar": {
            "description": "Absolute CRM avatar URL (120x120), or null",
            "type": [
              "string",
              "null"
            ],
            "example": "https://example.com/avatar/120x120/anna.png"
          },
          "colorID": {
            "type": [
              "integer",
              "null"
            ],
            "example": 3
          },
          "payrollRuleID": {
            "type": [
              "integer",
              "null"
            ],
            "example": 5
          },
          "position": {
            "type": [
              "integer",
              "null"
            ],
            "example": 2
          },
          "positionName": {
            "type": "string",
            "example": "Мастер"
          },
          "inviteLink": {
            "description": "Empty string for a confirmed phone; existing invitation URL or null otherwise",
            "type": [
              "string",
              "null"
            ],
            "example": ""
          },
          "employeeCategories": {
            "type": "array",
            "items": {
              "required": [
                "text",
                "value"
              ],
              "properties": {
                "text": {
                  "type": "string",
                  "example": "Стрижки"
                },
                "value": {
                  "type": "integer",
                  "example": 12
                }
              },
              "type": "object"
            }
          },
          "role": {
            "type": [
              "integer",
              "null"
            ],
            "example": 13
          },
          "roleName": {
            "type": "string",
            "example": "Сотрудник"
          },
          "roles": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProfileOpenApiFreeWindowsRole"
            }
          }
        },
        "type": "object"
      },
      "ProfileOpenApiFreeWindowsRole": {
        "properties": {
          "id": {
            "type": "integer",
            "example": 13
          },
          "name": {
            "type": "string",
            "example": "studio_master"
          },
          "guard_name": {
            "type": "string",
            "example": "web"
          },
          "profileID": {
            "type": "integer",
            "example": 3
          },
          "ru_name": {
            "type": "string",
            "example": "Сотрудник"
          },
          "created_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-03T08:00:00.000000Z"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2026-10-03T08:00:00.000000Z"
          },
          "pivot": {
            "properties": {
              "role_id": {
                "type": "integer",
                "example": 13
              },
              "model_type": {
                "type": "string",
                "example": "Beauty\\Modules\\Common\\Models\\User"
              },
              "model_id": {
                "type": "integer",
                "example": 8
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiFreeWindowsValidationError": {
        "required": [
          "errors"
        ],
        "properties": {
          "errors": {
            "description": "CRM FormRequest messages grouped by query field; there is no errors.code or errors.fields wrapper",
            "type": "object",
            "example": {
              "dateFrom": [
                {
                  "code": "8",
                  "message": "Invalid date"
                }
              ]
            },
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/ProfileApiValidationMessage"
              }
            }
          }
        },
        "type": "object"
      },
      "ProfileOpenApiFreeWindowsShow": {
        "required": [
          "employee",
          "text",
          "link"
        ],
        "properties": {
          "employee": {
            "$ref": "#/components/schemas/ProfileOpenApiFreeWindowsShowEmployee"
          },
          "text": {
            "description": "Localized dated CRM text; null when there are no windows",
            "type": [
              "string",
              "null"
            ],
            "example": "⏰ Свободные окна для записи\\n\\nАнна, Мастер:\\n\\nВт, 15 января\\n09:00 09:15"
          },
          "link": {
            "description": "With windows: localized label plus newline and URL. Without windows: raw booking URL or null",
            "type": [
              "string",
              "null"
            ],
            "example": "Записаться онлайн:\\nhttps://example.com/online/studio?employeeID=8"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiFreeWindowsShowEmployee": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProfileOpenApiFreeWindowsEmployee"
          },
          {
            "required": [
              "new_blocking_times"
            ],
            "properties": {
              "new_blocking_times": {
                "description": "Blocked times at the main address; preserves CRM serialization without an additional period filter",
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ProfileOpenApiFreeWindowsBlockingTime"
                }
              }
            },
            "type": "object"
          }
        ]
      },
      "ProfileOpenApiFreeWindowsBlockingTime": {
        "description": "Existing CRM blocking-time serialization, exposed by show only; timestamps are hidden.",
        "properties": {
          "blockTimeID": {
            "type": "integer",
            "example": 7
          },
          "userID": {
            "type": [
              "integer",
              "null"
            ],
            "example": 8
          },
          "addressID": {
            "type": [
              "integer",
              "null"
            ],
            "example": 4
          },
          "date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "example": "2030-01-15"
          },
          "timeFrom": {
            "type": [
              "string",
              "null"
            ],
            "example": "13:00:00"
          },
          "timeTo": {
            "type": [
              "string",
              "null"
            ],
            "example": "14:00:00"
          },
          "colorID": {
            "type": [
              "integer",
              "null"
            ],
            "example": 3
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Перерыв"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "example": "Обед"
          },
          "onlineIsBlocked": {
            "type": "boolean",
            "example": true
          },
          "pivot": {
            "properties": {
              "userID": {
                "type": "integer",
                "example": 8
              },
              "blockTimeID": {
                "type": "integer",
                "example": 7
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiFreeWindowsEmptyList": {
        "type": "array",
        "items": {
          "type": "object"
        },
        "maxItems": 0
      },
      "ProfileOpenApiFutureProfitSummary": {
        "required": [
          "day",
          "week",
          "month",
          "currency"
        ],
        "properties": {
          "day": {
            "description": "Sum of bid prices for today",
            "type": "integer",
            "example": 1200
          },
          "week": {
            "description": "Today through today + 1 week - 1 day, inclusive",
            "type": "integer",
            "example": 8400
          },
          "month": {
            "description": "Today through today + 1 month - 1 day; preserves CRM Carbon month overflow",
            "type": "integer",
            "example": 36000
          },
          "currency": {
            "description": "Profile currency code; prices are not converted",
            "type": "string",
            "example": "RUB"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiFutureProfitPeriod": {
        "required": [
          "profit",
          "currency"
        ],
        "properties": {
          "profit": {
            "description": "Sum of all bid prices on matching visits; retains CRM integer return type",
            "type": "integer",
            "example": 1200
          },
          "currency": {
            "description": "Profile currency code; prices are not converted",
            "type": "string",
            "example": "RUB"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiFutureProfitValidationError": {
        "required": [
          "errors"
        ],
        "properties": {
          "errors": {
            "description": "Unchanged CRM FormRequest error shape, messages grouped by dateFrom/dateTo",
            "type": "object",
            "example": {
              "dateFrom": [
                {
                  "code": "8",
                  "message": "Invalid date"
                }
              ]
            },
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/ProfileApiValidationMessage"
              }
            }
          }
        },
        "type": "object"
      },
      "ProfileOpenApiScheduleList": {
        "required": [
          "data"
        ],
        "properties": {
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProfileOpenApiSchedule"
            }
          }
        },
        "type": "object"
      },
      "ProfileOpenApiSchedule": {
        "description": "CRM v2 schedule fields plus date and userID. No time zone conversion. time[0] is the working interval; optional time[1] is the break.",
        "required": [
          "scheduleID",
          "addressID",
          "date",
          "userID",
          "type",
          "color",
          "time"
        ],
        "properties": {
          "scheduleID": {
            "type": "integer",
            "example": 123
          },
          "addressID": {
            "type": "integer",
            "example": 456
          },
          "date": {
            "type": "string",
            "format": "date",
            "example": "2030-10-10"
          },
          "userID": {
            "type": "integer",
            "example": 789
          },
          "type": {
            "type": "integer",
            "enum": [
              0,
              1,
              2
            ],
            "example": 1
          },
          "color": {
            "description": "Address color, with the same default as CRM v2",
            "type": "string",
            "example": "#FF5733"
          },
          "time": {
            "type": "array",
            "items": {
              "required": [
                "start",
                "end"
              ],
              "properties": {
                "start": {
                  "type": "string",
                  "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
                  "example": "09:00"
                },
                "end": {
                  "type": "string",
                  "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
                  "example": "18:00"
                }
              },
              "type": "object"
            },
            "maxItems": 2,
            "minItems": 1
          }
        },
        "type": "object"
      },
      "ProfileOpenApiScheduleInput": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProfileOpenApiScheduleFields"
          },
          {
            "required": [
              "userID",
              "addressID",
              "type",
              "date"
            ],
            "properties": {
              "date": {
                "description": "YYYY-MM-DD. New records on past dates are forbidden; an existing record can be updated.",
                "type": "string",
                "format": "date",
                "example": "2030-10-10"
              }
            },
            "type": "object"
          }
        ]
      },
      "ProfileOpenApiScheduleFields": {
        "description": "Write fields use CRM names. type=0 removes one matching record, type=1 is a working day, type=2 a weekend. Working times are required for type=1; omitted weekend times default to 00:00. Break endpoints must be supplied together and lie within working hours. profileID and other unknown fields are ignored.",
        "properties": {
          "userID": {
            "description": "Employee from the key profile, already attached to address; profile owner is also allowed without attachment",
            "type": "integer",
            "minimum": 1,
            "example": 789
          },
          "addressID": {
            "description": "Undeleted address of the key profile",
            "type": "integer",
            "minimum": 1,
            "example": 456
          },
          "type": {
            "type": "integer",
            "enum": [
              0,
              1,
              2
            ],
            "example": 1
          },
          "timeFrom": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
            "example": "09:00"
          },
          "timeTo": {
            "description": "After timeFrom for type=1",
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
            "example": "18:00"
          },
          "timeBreakFrom": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
            "example": "13:00"
          },
          "timeBreakTo": {
            "type": [
              "string",
              "null"
            ],
            "pattern": "^([01][0-9]|2[0-3]):[0-5][0-9]$",
            "example": "14:00"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiEmptyObject": {
        "maxProperties": 0,
        "type": "object",
        "additionalProperties": false
      },
      "ProfileOpenApiScheduleUpdate": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProfileOpenApiScheduleFields"
          },
          {
            "description": "Partial update of the selected ID. Omitted fields retain their values. userID, addressID and date are immutable; supplying different values returns 422. Past records may be edited. Set both break endpoints to null to clear the break.",
            "properties": {
              "date": {
                "type": "string",
                "format": "date",
                "example": "2030-10-10"
              }
            },
            "type": "object"
          }
        ]
      },
      "ProfileOpenApiScheduleBulkInput": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProfileOpenApiScheduleFields"
          },
          {
            "required": [
              "userID",
              "addressID",
              "type",
              "dates"
            ],
            "properties": {
              "dates": {
                "description": "Up to 31 dates by default (PROFILE_API_SCHEDULE_BULK_MAX_DATES). Dates must be unique within the payload; duplicates return 422 before any writes. The limit applies to the full array before item validation.",
                "type": "array",
                "items": {
                  "type": "string",
                  "format": "date"
                },
                "minItems": 1,
                "uniqueItems": true,
                "example": [
                  "2030-10-10",
                  "2030-10-11"
                ]
              }
            },
            "type": "object"
          }
        ]
      },
      "OpenApiCatalogCategory": {
        "description": "Category without an own photo. Index uses categoryID/name/color/servicesCount/sort. Show adds status/isHidden/isEmpty/services. Mutation responses retain their CRM-specific fields.",
        "properties": {
          "categoryID": {
            "type": "integer",
            "example": 12
          },
          "name": {
            "type": "string",
            "example": "Haircuts"
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "example": "#0096fb"
          },
          "sort": {
            "type": "integer",
            "example": 1
          },
          "status": {
            "type": "integer",
            "example": 1
          },
          "isHidden": {
            "type": "boolean",
            "example": false
          },
          "isEmpty": {
            "type": "boolean",
            "example": false
          },
          "isOpen": {
            "description": "Included in bulk category responses",
            "type": "boolean",
            "example": true
          },
          "servicesCount": {
            "type": "integer",
            "example": 3
          },
          "services": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OpenApiCatalogService"
            }
          }
        },
        "type": "object"
      },
      "OpenApiCatalogService": {
        "description": "CRM catalog representation. Fields vary between list, full show and mutation responses; show wraps the card in service, mutations return the card at the root. Price fields may be null when owner permissions hide prices. Photo previews in lists are URLs; full cards contain photo objects.",
        "required": [
          "serviceID",
          "title",
          "price",
          "time",
          "photos",
          "photosCount"
        ],
        "properties": {
          "serviceID": {
            "type": "integer",
            "example": 123
          },
          "categoryID": {
            "type": "integer",
            "example": 12
          },
          "templateID": {
            "description": "Present in selected CRM list representations",
            "type": "integer",
            "example": 3
          },
          "categoryColor": {
            "type": [
              "string",
              "null"
            ],
            "example": "#0096fb"
          },
          "title": {
            "type": "string",
            "example": "Haircut"
          },
          "customerTitle": {
            "type": [
              "string",
              "null"
            ],
            "example": "Haircut"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "example": "Service details"
          },
          "memo": {
            "type": [
              "string",
              "null"
            ],
            "example": "Internal memo"
          },
          "price": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1500
          },
          "priceType": {
            "type": [
              "integer",
              "null"
            ],
            "enum": [
              0,
              1,
              2
            ],
            "example": 0
          },
          "priceTypeAlias": {
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "fixed",
              "free",
              "from"
            ],
            "example": "fixed"
          },
          "currency": {
            "type": [
              "string",
              "null"
            ],
            "example": "RUB"
          },
          "time": {
            "type": "integer",
            "example": 60
          },
          "sort": {
            "type": [
              "integer",
              "null"
            ],
            "example": 1
          },
          "status": {
            "type": "boolean",
            "example": true
          },
          "active": {
            "type": "boolean",
            "example": true
          },
          "isOnline": {
            "type": "boolean",
            "example": true
          },
          "isCustomService": {
            "type": "boolean",
            "example": false
          },
          "isBlockTime": {
            "type": "boolean",
            "example": false
          },
          "blockTime": {
            "example": 0,
            "oneOf": [
              {
                "type": "integer"
              },
              {
                "type": "string",
                "enum": [
                  ""
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "employees": {
            "type": "array",
            "items": {
              "type": "integer",
              "example": 45
            }
          },
          "resources": {
            "type": "array",
            "items": {
              "type": "integer",
              "example": 7
            }
          },
          "photosCount": {
            "type": "integer",
            "example": 1
          },
          "photos": {
            "type": "array",
            "items": {
              "oneOf": [
                {
                  "type": "string",
                  "format": "uri",
                  "example": "https://storage.example.test/photo.jpg"
                },
                {
                  "required": [
                    "uploadFilesID",
                    "image",
                    "originalImage",
                    "imageOriginal",
                    "crop"
                  ],
                  "properties": {
                    "uploadFilesID": {
                      "type": "integer",
                      "example": 321
                    },
                    "image": {
                      "type": "string",
                      "example": "https://storage.example.test/photo_180x180.jpg"
                    },
                    "originalImage": {
                      "type": "string",
                      "example": "https://storage.example.test/photo.jpg"
                    },
                    "imageOriginal": {
                      "type": "string",
                      "example": "https://storage.example.test/photo.jpg"
                    },
                    "crop": {
                      "oneOf": [
                        {
                          "$ref": "#/components/schemas/ServicePhotoCrop"
                        },
                        {
                          "type": "null"
                        }
                      ]
                    }
                  },
                  "type": "object"
                }
              ]
            }
          }
        },
        "type": "object"
      },
      "ServicePhotoCrop": {
        "properties": {
          "x": {
            "type": "integer",
            "minimum": 0,
            "example": 0
          },
          "y": {
            "type": "integer",
            "minimum": 0,
            "example": 0
          },
          "width": {
            "type": "integer",
            "minimum": 1,
            "example": 800
          },
          "height": {
            "type": "integer",
            "minimum": 1,
            "example": 600
          }
        },
        "type": "object"
      },
      "OpenApiCategoryWrite": {
        "description": "Category has no photo fields. Caller profile/address/template and moderation fields are forbidden.",
        "required": [
          "name",
          "color"
        ],
        "properties": {
          "name": {
            "type": "string",
            "maxLength": 255,
            "example": "Haircuts"
          },
          "color": {
            "type": "string",
            "maxLength": 11,
            "example": "#0096fb"
          },
          "checkSum": {
            "description": "Optional POST deduplication; empty and 0 disable it",
            "type": [
              "string",
              "null"
            ],
            "example": "category-create-123"
          },
          "timeCache": {
            "description": "POST completion window in seconds",
            "type": [
              "integer",
              "null"
            ],
            "default": 1440,
            "maximum": 2147483647,
            "minimum": 0,
            "example": 1440
          }
        },
        "type": "object"
      },
      "OpenApiCatalogDeleteIssues": {
        "required": [
          "issues"
        ],
        "properties": {
          "issues": {
            "required": [
              "memberships"
            ],
            "properties": {
              "memberships": {
                "type": "array",
                "items": {
                  "required": [
                    "text",
                    "value"
                  ],
                  "properties": {
                    "text": {
                      "type": "string",
                      "example": "Monthly membership"
                    },
                    "value": {
                      "type": "integer",
                      "example": 10
                    }
                  },
                  "type": "object"
                }
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "OpenApiServiceWrite": {
        "required": [
          "categoryID",
          "title",
          "price",
          "time"
        ],
        "properties": {
          "categoryID": {
            "type": "integer",
            "example": 123
          },
          "title": {
            "type": "string",
            "maxLength": 255,
            "example": "Haircut"
          },
          "customerTitle": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 255,
            "example": "Haircut"
          },
          "description": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 1000,
            "example": "Service description"
          },
          "memo": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 2000,
            "example": "Internal memo"
          },
          "price": {
            "type": "integer",
            "maximum": 9999999999,
            "example": 1500
          },
          "priceType": {
            "example": 0,
            "oneOf": [
              {
                "type": "integer",
                "enum": [
                  0,
                  1,
                  2
                ]
              },
              {
                "type": "string",
                "enum": [
                  "fixed",
                  "free",
                  "from"
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "time": {
            "type": "integer",
            "maximum": 9999999999,
            "example": 60
          },
          "employees": {
            "description": "Comma-separated employee IDs in this profile; owner is allowed without attachment. Omitted or empty value clears assignments on update.",
            "type": [
              "string",
              "null"
            ],
            "maxLength": 10000,
            "example": "45,46"
          },
          "resources": {
            "description": "Comma-separated resource IDs in this profile; omitted or empty value clears assignments on update.",
            "example": "12",
            "oneOf": [
              {
                "type": "string"
              },
              {
                "type": "integer"
              },
              {
                "type": "null"
              }
            ]
          },
          "isOnline": {
            "example": true,
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "integer",
                "enum": [
                  0,
                  1
                ]
              },
              {
                "type": "string",
                "enum": [
                  "true",
                  "false",
                  "1",
                  "0",
                  "on",
                  "off",
                  "yes",
                  "no",
                  ""
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "active": {
            "example": true,
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "integer",
                "enum": [
                  0,
                  1
                ]
              },
              {
                "type": "string",
                "enum": [
                  "true",
                  "false",
                  "1",
                  "0",
                  "on",
                  "off",
                  "yes",
                  "no",
                  ""
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "isBlockTime": {
            "example": false,
            "oneOf": [
              {
                "type": "boolean"
              },
              {
                "type": "integer",
                "enum": [
                  0,
                  1
                ]
              },
              {
                "type": "string",
                "enum": [
                  "true",
                  "false",
                  "1",
                  "0",
                  "on",
                  "off",
                  "yes",
                  "no",
                  ""
                ]
              },
              {
                "type": "null"
              }
            ]
          },
          "blockTime": {
            "type": [
              "integer",
              "null"
            ],
            "maximum": 9999999999,
            "example": 0
          },
          "savePhotos": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "$ref": "#/components/schemas/ServicePhotoDescriptor"
            },
            "maxItems": 10
          },
          "updatedPhotos": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "required": [
                "uploadFilesID"
              ],
              "properties": {
                "uploadFilesID": {
                  "type": "integer",
                  "minimum": 1,
                  "example": 321
                },
                "crop": {
                  "$ref": "#/components/schemas/ServicePhotoCrop"
                }
              },
              "type": "object"
            }
          },
          "deletedPhotos": {
            "type": [
              "array",
              "null"
            ],
            "items": {
              "type": "integer",
              "minimum": 1,
              "example": 321
            }
          },
          "checkSum": {
            "description": "Optional CRM checksum; empty or zero string disables reservation",
            "type": [
              "string",
              "null"
            ],
            "example": "request-123"
          },
          "timeCache": {
            "description": "Completed reservation TTL in seconds, POST only",
            "type": [
              "integer",
              "null"
            ],
            "default": 1440,
            "maximum": 2147483647,
            "minimum": 0,
            "example": 15
          }
        },
        "type": "object"
      },
      "ServicePhotoDescriptor": {
        "required": [
          "hash",
          "name",
          "mimeType",
          "createdAt"
        ],
        "properties": {
          "hash": {
            "type": "string",
            "pattern": "^[a-f0-9]{64}$",
            "example": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
          },
          "name": {
            "type": "string",
            "maxLength": 255,
            "example": "photo.jpg"
          },
          "mimeType": {
            "type": "string",
            "enum": [
              "image/jpeg",
              "image/png",
              "image/webp"
            ],
            "example": "image/jpeg"
          },
          "createdAt": {
            "type": "string",
            "example": "2026-10-14 12:00:00"
          },
          "crop": {
            "$ref": "#/components/schemas/ServicePhotoCrop"
          },
          "sort": {
            "type": "integer",
            "minimum": 1,
            "example": 1
          },
          "size": {
            "description": "Optional hint; actual size is determined by server",
            "type": "integer",
            "minimum": 0,
            "example": 1024
          }
        },
        "type": "object",
        "additionalProperties": false
      },
      "ServicePhotoUploadRequest": {
        "required": [
          "files"
        ],
        "properties": {
          "files": {
            "type": "array",
            "items": {
              "required": [
                "name",
                "mimeType"
              ],
              "properties": {
                "name": {
                  "type": "string",
                  "maxLength": 255,
                  "example": "photo.jpg"
                },
                "mimeType": {
                  "type": "string",
                  "enum": [
                    "image/jpeg",
                    "image/png",
                    "image/webp"
                  ],
                  "example": "image/jpeg"
                }
              },
              "type": "object",
              "additionalProperties": false
            },
            "maxItems": 10,
            "minItems": 1
          }
        },
        "type": "object"
      },
      "ServicePhotoUploadResponse": {
        "properties": {
          "files": {
            "type": "array",
            "items": {
              "properties": {
                "hash": {
                  "type": "string",
                  "example": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
                },
                "name": {
                  "type": "string",
                  "example": "photo.jpg"
                },
                "mimeType": {
                  "type": "string",
                  "example": "image/jpeg"
                },
                "createdAt": {
                  "type": "string",
                  "example": "2026-10-14 12:00:00"
                },
                "url": {
                  "type": "string",
                  "example": "https://storage.example.test/private-upload?signature=opaque"
                },
                "headers": {
                  "type": "object",
                  "example": {
                    "Content-Type": [
                      "image/jpeg"
                    ]
                  },
                  "additionalProperties": {
                    "type": "array",
                    "items": {
                      "type": "string"
                    }
                  }
                }
              },
              "type": "object"
            }
          }
        },
        "type": "object"
      },
      "ProfileOpenApiVisitsIndex": {
        "required": [
          "new",
          "working",
          "done",
          "cancel"
        ],
        "properties": {
          "new": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitGroup"
          },
          "working": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitGroup"
          },
          "done": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitGroup"
          },
          "cancel": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitGroup"
          },
          "completed": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitGroup",
            "description": "Only with showCompleted=true; completed visits are normalized into done"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiVisitGroup": {
        "required": [
          "status",
          "title",
          "count",
          "visits"
        ],
        "properties": {
          "status": {
            "type": "string",
            "enum": [
              "new",
              "working",
              "done",
              "cancel",
              "completed"
            ],
            "example": "new"
          },
          "title": {
            "type": "string",
            "example": "Новая"
          },
          "count": {
            "type": "integer",
            "example": 1
          },
          "visits": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProfileOpenApiVisit"
            }
          }
        },
        "type": "object"
      },
      "ProfileOpenApiVisit": {
        "description": "Actual CRM v3 index serializer. Index SELECT omits adminID, dateTo, externalProvider and confirmedAs; it preserves dateTime, notification and review.",
        "properties": {
          "visitID": {
            "type": "integer",
            "example": 100
          },
          "clientID": {
            "type": "integer",
            "example": 42
          },
          "profileID": {
            "type": "integer",
            "example": 3
          },
          "relation": {
            "type": "string",
            "example": "f564f057a747aa069617bccbf4d28546"
          },
          "status": {
            "type": "string",
            "enum": [
              "new",
              "working",
              "done",
              "cancel"
            ],
            "example": "new"
          },
          "date": {
            "type": "string",
            "format": "date",
            "example": "2030-04-10"
          },
          "dateTime": {
            "description": "Local SQL date-time of the earliest active bid; preserved CRM format without timezone suffix",
            "type": "string",
            "example": "2030-04-10 10:00:00"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "isOnline": {
            "type": "boolean",
            "example": false
          },
          "notification": {
            "properties": {
              "notificationReminder": {
                "$ref": "#/components/schemas/ProfileOpenApiVisitNotification"
              },
              "notificationRepeatReminder": {
                "$ref": "#/components/schemas/ProfileOpenApiVisitNotification"
              }
            },
            "type": "object"
          },
          "bids": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProfileOpenApiVisitBid"
            }
          },
          "client": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitClient",
            "description": "Preserved soft-deleted client. May be absent if no client relation exists."
          },
          "futureRepeatsCount": {
            "type": "integer",
            "example": 0
          },
          "repeat": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitRepeat"
          },
          "sale": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitSale"
          },
          "review": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitReview"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiVisitNotification": {
        "properties": {
          "id": {
            "type": "integer",
            "example": 10
          },
          "notification_id": {
            "type": "integer",
            "example": 100
          },
          "statusText": {
            "type": [
              "string",
              "null"
            ],
            "example": "sent"
          },
          "text": {
            "type": [
              "string",
              "null"
            ],
            "example": "Напоминание отправлено"
          },
          "createdAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time",
            "example": "2030-04-10T07:00:00.000000Z"
          }
        },
        "type": [
          "object",
          "null"
        ]
      },
      "ProfileOpenApiVisitBid": {
        "required": [
          "bidID",
          "visitID",
          "masterID",
          "serviceID",
          "timeFrom",
          "timeTo",
          "timeDuration",
          "price",
          "priceType",
          "priceTypeAlias",
          "currency"
        ],
        "properties": {
          "bidID": {
            "type": "integer",
            "example": 200
          },
          "visitID": {
            "type": "integer",
            "example": 100
          },
          "masterID": {
            "type": "integer",
            "example": 8
          },
          "serviceID": {
            "type": "integer",
            "example": 12
          },
          "timeFrom": {
            "type": [
              "string",
              "null"
            ],
            "example": "10:00"
          },
          "timeTo": {
            "type": [
              "string",
              "null"
            ],
            "example": "11:00"
          },
          "timeDuration": {
            "type": "integer",
            "example": 60
          },
          "price": {
            "description": "Index exposes price; show/store/update set price fields to null when owner lacks all-show-bids-price",
            "type": [
              "number",
              "null"
            ],
            "example": 25.5
          },
          "priceType": {
            "type": [
              "integer",
              "null"
            ],
            "example": 0
          },
          "priceTypeAlias": {
            "type": [
              "string",
              "null"
            ],
            "example": "fixed"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "avatar": {
            "type": [
              "string",
              "null"
            ],
            "example": "https://example.com/storage/avatar/120x120/master.jpg"
          },
          "masterAvatar": {
            "type": [
              "string",
              "null"
            ],
            "example": "https://example.com/storage/avatar/120x120/master.jpg"
          },
          "masterName": {
            "type": [
              "string",
              "null"
            ],
            "example": "Мастер"
          },
          "masterPosition": {
            "description": "Position from key profile",
            "type": [
              "string",
              "null"
            ],
            "example": "Стилист"
          },
          "serviceName": {
            "type": [
              "string",
              "null"
            ],
            "example": "Стрижка"
          },
          "date": {
            "type": "string",
            "format": "date-time",
            "example": "2030-04-10T07:00:00.000000Z"
          },
          "images": {
            "type": "array",
            "items": {
              "type": "string"
            },
            "maxItems": 0,
            "example": []
          },
          "serviceColor": {
            "type": [
              "string",
              "null"
            ],
            "example": "#e60059"
          },
          "serviceMemo": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "currency": {
            "type": "string",
            "example": "USD"
          },
          "hasNote": {
            "type": "boolean",
            "example": false
          },
          "hasMembership": {
            "type": "boolean",
            "example": false
          },
          "membershipName": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "membership": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitMembership"
          },
          "sale_bids": {
            "description": "Conditional raw CRM sale-item relation. Removed from the first bid when a non-canceled sale is formatted; other bids may retain it. No new wrapper is introduced.",
            "type": "array",
            "items": {
              "properties": {
                "id": {
                  "type": "integer",
                  "example": 30
                },
                "saleID": {
                  "type": "integer",
                  "example": 15
                },
                "itemable_id": {
                  "type": "integer",
                  "example": 200
                },
                "itemable_type": {
                  "type": "string",
                  "example": "Beauty\\Modules\\Common\\Models\\Bid"
                },
                "quantity": {
                  "type": "integer",
                  "example": 1
                },
                "price": {
                  "type": "number",
                  "example": 25.5
                },
                "priceWithoutDiscount": {
                  "type": "number",
                  "example": 25.5
                },
                "type": {
                  "type": "string",
                  "example": "plus"
                },
                "userID": {
                  "type": "integer",
                  "example": 8
                },
                "name": {
                  "type": "string",
                  "example": "Стрижка"
                },
                "stockID": {
                  "type": [
                    "integer",
                    "null"
                  ],
                  "example": null
                },
                "sale": {
                  "description": "Raw CRM sale relation, including its loaded items_trashed and payments when present",
                  "type": [
                    "object",
                    "null"
                  ],
                  "example": {
                    "id": 15,
                    "state": "unpaid",
                    "profileID": 3
                  },
                  "additionalProperties": true
                }
              },
              "type": "object",
              "additionalProperties": true
            }
          }
        },
        "type": "object"
      },
      "ProfileOpenApiVisitMembership": {
        "properties": {
          "id": {
            "type": "integer",
            "example": 6
          },
          "name": {
            "type": "string",
            "example": "Абонемент"
          },
          "status": {
            "type": "string",
            "example": "active"
          },
          "textStatus": {
            "type": "string",
            "example": "Активен"
          },
          "color": {
            "type": [
              "string",
              "null"
            ],
            "example": "#e60059"
          },
          "endAt": {
            "type": [
              "string",
              "null"
            ],
            "format": "date",
            "example": "2030-05-01"
          },
          "unlimitedSessions": {
            "type": "boolean",
            "example": false
          },
          "initialSessionsNumber": {
            "type": "integer",
            "example": 10
          },
          "availableSessions": {
            "type": "integer",
            "example": 7
          },
          "activationPeriod": {
            "type": "string",
            "example": "immediately"
          },
          "client": {
            "description": "Client relation as serialized by CRM membership formatter",
            "type": [
              "object",
              "null"
            ],
            "example": {
              "clientID": 42,
              "name": "Анна"
            },
            "additionalProperties": true
          }
        },
        "type": [
          "object",
          "null"
        ]
      },
      "ProfileOpenApiVisitClient": {
        "properties": {
          "clientID": {
            "type": "integer",
            "example": 42
          },
          "name": {
            "type": [
              "string",
              "null"
            ],
            "example": "Анна"
          },
          "phone": {
            "description": "Existing CRM owner permissions control phone visibility",
            "type": [
              "string",
              "null"
            ],
            "example": "+447700900123"
          },
          "avatar": {
            "type": [
              "string",
              "null"
            ],
            "example": "https://example.com/storage/avatar/200x200/anna.jpg"
          },
          "phoneDetails": {
            "properties": {
              "number": {
                "type": "string",
                "example": "7700900123"
              },
              "phoneCode": {
                "type": "string",
                "example": "44"
              },
              "countryCode": {
                "type": "string",
                "example": "GB"
              }
            },
            "type": [
              "object",
              "null"
            ]
          },
          "isDeleted": {
            "type": "boolean",
            "example": false
          }
        },
        "type": "object"
      },
      "ProfileOpenApiVisitRepeat": {
        "properties": {
          "frequency": {
            "type": "string",
            "example": "everyWeek"
          },
          "frequencyText": {
            "type": "string",
            "example": "Каждую неделю"
          },
          "futureCount": {
            "description": "Index preserves CRM value 0; future repeats are not counted",
            "type": "integer",
            "example": 0
          },
          "end": {
            "properties": {
              "type": {
                "type": "string",
                "enum": [
                  "times",
                  "date"
                ],
                "example": "times"
              },
              "times": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "afterFiveTimes"
              },
              "timesText": {
                "type": [
                  "string",
                  "null"
                ],
                "example": "После 5 раз"
              },
              "date": {
                "type": [
                  "string",
                  "null"
                ],
                "format": "date",
                "example": "2030-05-10"
              }
            },
            "type": "object"
          }
        },
        "type": [
          "object",
          "null"
        ]
      },
      "ProfileOpenApiVisitSale": {
        "description": "Non-canceled sale of the first bid; null if absent. CRM calculates price from all items including returns, and payments from sale payments.",
        "properties": {
          "id": {
            "type": "integer",
            "example": 15
          },
          "state": {
            "type": "string",
            "enum": [
              "replace",
              "completed",
              "partialPaid",
              "returned",
              "unpaid",
              "draft"
            ],
            "example": "unpaid"
          },
          "stateText": {
            "type": "string",
            "example": "Не оплачено"
          },
          "price": {
            "type": "number",
            "example": 25.5
          },
          "payments": {
            "type": "number",
            "example": 0
          },
          "createdAt": {
            "type": "string",
            "format": "date-time",
            "example": "2030-04-01T05:00:00.000000Z"
          },
          "currency": {
            "type": "string",
            "example": "USD"
          }
        },
        "type": [
          "object",
          "null"
        ]
      },
      "ProfileOpenApiVisitReview": {
        "properties": {
          "reviewID": {
            "type": "integer",
            "example": 5
          },
          "parentID": {
            "type": [
              "integer",
              "null"
            ],
            "example": null
          },
          "visitID": {
            "type": [
              "integer",
              "null"
            ],
            "example": 100
          },
          "masterID": {
            "type": [
              "integer",
              "null"
            ],
            "example": 8
          },
          "clientable_type": {
            "type": "string",
            "example": "Beauty\\Modules\\Common\\Models\\Client"
          },
          "clientable_id": {
            "type": [
              "string",
              "null"
            ],
            "example": "42"
          },
          "stars": {
            "type": [
              "integer",
              "null"
            ],
            "example": 5
          },
          "message": {
            "type": [
              "string",
              "null"
            ],
            "example": "Спасибо"
          },
          "active": {
            "type": "boolean",
            "example": true
          },
          "read": {
            "type": "boolean",
            "example": false
          }
        },
        "type": [
          "object",
          "null"
        ]
      },
      "ProfileOpenApiVisitsValidationError": {
        "required": [
          "errors"
        ],
        "properties": {
          "errors": {
            "type": "object",
            "example": {
              "date": [
                {
                  "code": "8",
                  "message": "Invalid date"
                }
              ]
            },
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/ProfileApiValidationMessage"
              }
            }
          }
        },
        "type": "object"
      },
      "ProfileOpenApiVisitCreate": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ProfileOpenApiVisitWrite"
          },
          {
            "properties": {
              "bids": {
                "type": "array",
                "items": {
                  "required": [
                    "price"
                  ],
                  "properties": {
                    "price": {
                      "type": "integer",
                      "maximum": 9999999999,
                      "example": 2000
                    }
                  },
                  "type": "object"
                }
              }
            },
            "type": "object"
          }
        ]
      },
      "ProfileOpenApiVisitWrite": {
        "required": [
          "clientID",
          "date",
          "bids"
        ],
        "properties": {
          "clientID": {
            "description": "Active client of the key profile",
            "type": "integer",
            "example": 11
          },
          "date": {
            "type": "string",
            "format": "date",
            "example": "2030-04-01"
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "maxLength": 3000,
            "example": "Visit"
          },
          "addressID": {
            "description": "Optional active address of the key profile used for price template validation",
            "type": [
              "integer",
              "null"
            ],
            "example": 5
          },
          "checkSum": {
            "description": "Optional POST reservation shared by tokens of one profile. Successful requests protected for 15 seconds. Error releases reservation but does not roll back partial v3 writes.",
            "type": [
              "string",
              "null"
            ],
            "example": "integration-request-42"
          },
          "updateType": {
            "description": "PUT only; required by the current CRM branch when asking how to change a series",
            "type": [
              "string",
              "null"
            ],
            "enum": [
              "single",
              "future"
            ],
            "example": "future"
          },
          "bids": {
            "type": "array",
            "items": {
              "required": [
                "masterID",
                "serviceID",
                "timeFrom",
                "timeDuration"
              ],
              "properties": {
                "masterID": {
                  "description": "Active employee of the profile or its owner; no automatic attachment",
                  "type": "integer",
                  "example": 8
                },
                "serviceID": {
                  "description": "Active service of the selected profile price template",
                  "type": "integer",
                  "example": 20
                },
                "timeFrom": {
                  "description": "An entry in the CRM TimeArray time dictionary",
                  "type": "string",
                  "example": "10:00"
                },
                "timeDuration": {
                  "description": "Minutes; calculated timeTo must exist in the same CRM dictionary",
                  "type": "integer",
                  "maximum": 9999999999,
                  "example": 60
                },
                "price": {
                  "description": "Required and non-null on POST; optional on PUT as in CRM v3",
                  "type": [
                    "integer",
                    "null"
                  ],
                  "maximum": 9999999999,
                  "example": 2000
                },
                "priceType": {
                  "description": "CRM PriceTypeObject alias or numeric ID",
                  "example": "fixed",
                  "oneOf": [
                    {
                      "type": "string"
                    },
                    {
                      "type": "integer"
                    },
                    {
                      "type": "null"
                    }
                  ]
                }
              },
              "type": "object"
            },
            "maxItems": 10,
            "minItems": 1
          },
          "repeat": {
            "properties": {
              "frequency": {
                "description": "Alias from GET visits/repeats; never creates one visit",
                "type": "string",
                "example": "everyDay"
              },
              "end": {
                "description": "Required unless frequency=never",
                "properties": {
                  "type": {
                    "type": "string",
                    "enum": [
                      "times",
                      "date"
                    ],
                    "example": "times"
                  },
                  "times": {
                    "description": "Count alias; untilDate is not accepted here",
                    "type": "string",
                    "example": "afterThreeTimes"
                  },
                  "date": {
                    "description": "Required for end.type=date; not before the visit date and at most one year after it",
                    "type": "string",
                    "format": "date",
                    "example": "2030-04-30"
                  }
                },
                "type": "object"
              }
            },
            "type": [
              "object",
              "null"
            ]
          }
        },
        "type": "object"
      },
      "ProfileOpenApiVisitShow": {
        "description": "CRM v3 show/store/update serializer. Owner price permission controls nullable price fields. Flutter headers preserve CRM date and status normalization.",
        "properties": {
          "visitID": {
            "type": "integer",
            "example": 100
          },
          "clientID": {
            "type": "integer",
            "example": 42
          },
          "profileID": {
            "type": "integer",
            "example": 3
          },
          "relation": {
            "type": "string",
            "example": "f564f057a747aa069617bccbf4d28546"
          },
          "status": {
            "type": "string",
            "enum": [
              "new",
              "working",
              "done",
              "cancel",
              "completed",
              "noShow"
            ],
            "example": "new"
          },
          "date": {
            "description": "Y-m-d or local Y-m-d H:i:s for Flutter version 38+",
            "type": "string",
            "example": "2030-04-10"
          },
          "dateTime": {
            "description": "Local SQL date-time of the earliest active bid; preserved CRM format without timezone suffix",
            "type": "string",
            "example": "2030-04-10 10:00:00"
          },
          "adminID": {
            "type": "integer",
            "example": 1
          },
          "dateTo": {
            "type": "string",
            "format": "date",
            "example": "2030-04-10"
          },
          "externalProvider": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "confirmedAs": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "comment": {
            "type": [
              "string",
              "null"
            ],
            "example": null
          },
          "isOnline": {
            "type": "boolean",
            "example": false
          },
          "notification": {
            "properties": {
              "notificationReminder": {
                "$ref": "#/components/schemas/ProfileOpenApiVisitNotification"
              },
              "notificationRepeatReminder": {
                "$ref": "#/components/schemas/ProfileOpenApiVisitNotification"
              }
            },
            "type": "object"
          },
          "bids": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ProfileOpenApiVisitBid"
            }
          },
          "client": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitClient",
            "description": "Preserved soft-deleted client. May be absent if no client relation exists."
          },
          "futureRepeatsCount": {
            "type": "integer",
            "example": 0
          },
          "repeat": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitRepeat"
          },
          "sale": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitSale"
          },
          "review": {
            "$ref": "#/components/schemas/ProfileOpenApiVisitReview"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiVisitRepeats": {
        "required": [
          "body"
        ],
        "properties": {
          "body": {
            "required": [
              "frequency",
              "end"
            ],
            "properties": {
              "frequency": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ProfileOpenApiRepeatItem"
                }
              },
              "end": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ProfileOpenApiRepeatItem"
                }
              }
            },
            "type": "object"
          }
        },
        "type": "object"
      },
      "ProfileOpenApiRepeatItem": {
        "required": [
          "value",
          "text",
          "alias"
        ],
        "properties": {
          "value": {
            "type": "integer",
            "example": 1
          },
          "text": {
            "type": "string",
            "example": "Каждый день"
          },
          "alias": {
            "type": "string",
            "example": "everyDay"
          }
        },
        "type": "object"
      }
    }
  },
  "tags": [
    {
      "name": "CRM Open API / clients",
      "description": "Profile-scoped integration API authenticated with X-Api-Key"
    },
    {
      "name": "CRM Open API / employees",
      "description": "Profile employee directory: employees.read; assigned services additionally require services.read. Independent of schedules and available windows"
    },
    {
      "name": "CRM Open API / free windows",
      "description": "Read employee availability with employees.free-windows.read; business contract matches CRM"
    },
    {
      "name": "CRM Open API / booking",
      "description": "Explicit ordered employee/service selection; booking.availability.read for available dates/time slots and visits.write for online booking creation"
    },
    {
      "name": "CRM Open API / visits",
      "description": "Profile visits and repeat options: visits.read for reads, visits.write for creation, updates and cancellation; CRM v3 contracts"
    },
    {
      "name": "CRM Open API / future profit",
      "description": "Read projected bid price totals with analytics.future-profit.read using the CRM calculation"
    },
    {
      "name": "CRM Open API / schedules",
      "description": "Profile schedules: schedules.read/write/delete"
    },
    {
      "name": "CRM Open API / services",
      "description": "Profile services and signed photos: services.read/write/delete"
    },
    {
      "name": "CRM Open API / service categories",
      "description": "Profile categories without photos: service-categories.read/write/delete; nested services list requires services.read"
    }
  ]
}
