openapi: 3.0.3 info: title: iams-v2 API version: "1.0.0" description: | REST API сервиса **iams** (`platform/iams-v2`) — управление пользователями, ресурсами/проектами, оргструктурой (должности, подразделения, группы) и правами доступа (IAM). Сервис написан на Go (**Fiber**). Приложение собирается в `internal/controller/http/server.go` (`NewServer`). Роутинг состоит из двух контуров: - **внутренний** — группы `/api/v0`, `/api/admin/v0`, `/api/v1`, `/api/v2`; аутентификации на уровне приложения нет, доступ ограничивается сетевым слоем (через ingress не публикуется); - **внешний** — группы `/external/api/v0`, `/external/api/admin/v0`, `/external/api/v1`, `/external/api/v2`; регистрируется только при `AUTH_ENABLED=true`, публикует подмножество внутренних маршрутов (часть — только на чтение). Health-check доступен по `GET /api/health`. ### Аутентификация Внешние эндпоинты требуют заголовок `Authorization: Bearer ` (`internal/controller/http/jwt/parser.go`). **Подпись токена не проверяется** — доверие делегируется вышестоящему gateway/virtual service. Схема разбора выбирается по заголовку `Identity`: 1. заголовок `Identity` отсутствует/пуст → legacy-формат (Django SimpleJWT); 2. заголовок `Identity` задан → Zitadel (клейм `urn:zitadel:iam:user:metadata`, значения полей — base64). Внутренние эндпоинты (`/api/*`) аутентификации на уровне приложения не требуют. ### Пагинация Списочные ответы используют `limit`/`offset` (по умолчанию `limit=1000`) и оборачиваются в `{ count, results }`. ### Обработка ошибок Ошибки возвращаются как JSON `{ error, id }` (`httpx.Response`). Доменный `Kind` маппится на HTTP-статус (`httpx/error_resolve.go`): `InvalidArgument`/`OutOfRange` → `400`, `Unauthenticated` → `401`, `PermissionDenied` → `403`, `NotFound` → `404`, `Aborted`/`AlreadyExists` → `409`, `PreconditionFailed` → `412`, `ResourceExhausted` → `429`, `Unavailable` → `503`, `Internal`/`Unknown`/`DataLoss` → `500`. servers: - url: /api description: Внутренний контур (без auth) - url: /external/api description: Внешний контур (JWT, только при AUTH_ENABLED=true) tags: - name: health - name: users - name: permission-check - name: groups - name: positions - name: departments - name: django-permissions - name: resource-types - name: resources-v0 - name: projects-v0 - name: widgets-v0 - name: resources - name: widgets - name: resource-permissions - name: company-resource-permissions - name: service-accounts paths: /health: get: tags: [health] summary: Health-check description: Проверка БД и (при включении) Kafka. Обслуживается как `/api/health`. security: [] responses: "200": description: Сервис доступен content: application/json: schema: type: object properties: status: { type: string, example: ok } "503": description: Недоступна зависимость (БД/Kafka) content: application/json: schema: type: object properties: status: { type: string, example: unhealthy } error: { type: string, example: database_unavailable } /v0/users: get: tags: [users] summary: Список пользователей parameters: - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/Offset" - { name: username, in: query, schema: { type: string } } - { name: search, in: query, schema: { type: string } } - { name: is_active, in: query, schema: { type: boolean } } - { name: is_staff, in: query, schema: { type: boolean } } - { name: is_superuser, in: query, schema: { type: boolean } } - { name: service_account_id, in: query, schema: { type: string, format: uuid } } - { name: id, in: query, description: "CSV из id", schema: { type: string } } - { name: company_id, in: query, schema: { type: integer } } - { name: departments, in: query, description: "CSV", schema: { type: string } } - { name: positions, in: query, description: "CSV", schema: { type: string } } - { name: groups, in: query, description: "CSV", schema: { type: string } } responses: "200": description: OK content: application/json: schema: type: object properties: count: { type: integer } results: type: array items: { $ref: "#/components/schemas/User" } default: { $ref: "#/components/responses/Error" } post: tags: [users] summary: Создать пользователя requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UserCreateInput" } responses: "201": description: Создан content: application/json: schema: { $ref: "#/components/schemas/User" } default: { $ref: "#/components/responses/Error" } patch: tags: [users] summary: Массовое обновление пользователей (legacy bulk_update) requestBody: required: true content: application/json: schema: type: array items: { $ref: "#/components/schemas/UserBulkPatchItem" } responses: "200": description: OK default: { $ref: "#/components/responses/Error" } /v0/users/{id}: parameters: - { name: id, in: path, required: true, schema: { type: integer, format: int64 } } get: tags: [users] summary: Пользователь по id responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/User" } default: { $ref: "#/components/responses/Error" } put: tags: [users] summary: Полное обновление пользователя requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UserUpdateInput" } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } patch: tags: [users] summary: Частичное обновление пользователя requestBody: required: true content: application/json: schema: { type: object } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } delete: tags: [users] summary: Удалить пользователя responses: "204": { description: Удалён } default: { $ref: "#/components/responses/Error" } /admin/v0/users/activation: post: tags: [users] summary: Активация/деактивация пользователей компании requestBody: required: true content: application/json: schema: type: array items: { $ref: "#/components/schemas/CompanyUserActivationItem" } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } /v1/users-with-resources: post: tags: [users] summary: Пользователи с их ресурсами requestBody: required: true content: application/json: schema: type: object required: [tenant_id, id] properties: tenant_id: { type: integer, format: int64 } id: type: array items: { type: integer, format: int64 } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } /v1/users-grouped-by-resource/: get: tags: [users] summary: Пользователи, сгруппированные по ресурсу responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } /v0/permissions-check: post: tags: [permission-check] summary: Проверка прав пользователя requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/UserPermissionCheckInput" } responses: "200": description: OK content: application/json: schema: { $ref: "#/components/schemas/UserPermissionCheckOutput" } default: { $ref: "#/components/responses/Error" } /v0/groups: get: tags: [groups] summary: Список групп parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ] responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } post: tags: [groups] summary: Создать группу requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/GroupCreateInput" } responses: "201": { description: Создана } default: { $ref: "#/components/responses/Error" } /v0/groups/search: post: tags: [groups] summary: Поиск групп requestBody: required: true content: application/json: schema: { type: object } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } /v0/groups/{id}: parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ] get: tags: [groups] summary: Группа по id responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } put: tags: [groups] summary: Обновить группу requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/GroupUpdateInput" } } } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } patch: tags: [groups] summary: Частично обновить группу requestBody: required: true content: { application/json: { schema: { type: object } } } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } delete: tags: [groups] summary: Удалить группу responses: "204": { description: Удалена } default: { $ref: "#/components/responses/Error" } /v0/positions: get: tags: [positions] summary: Список должностей parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ] responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } post: tags: [positions] summary: Создать должность requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/PositionCreateInput" } } } responses: "201": { description: Создана } default: { $ref: "#/components/responses/Error" } /v0/positions/{id}: parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ] get: tags: [positions] summary: Должность по id responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } put: tags: [positions] summary: Обновить должность requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/PositionUpdateInput" } } } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } patch: tags: [positions] summary: Частично обновить должность requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } delete: tags: [positions] summary: Удалить должность responses: "204": { description: Удалена } default: { $ref: "#/components/responses/Error" } /v0/departments: get: tags: [departments] summary: Список подразделений parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ] responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } post: tags: [departments] summary: Создать подразделение requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/DepartmentCreateInput" } } } responses: "201": { description: Создано } default: { $ref: "#/components/responses/Error" } /v0/departments/{id}: parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ] get: tags: [departments] summary: Подразделение по id responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } put: tags: [departments] summary: Обновить подразделение requestBody: required: true content: { application/json: { schema: { $ref: "#/components/schemas/DepartmentUpdateInput" } } } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } patch: tags: [departments] summary: Частично обновить подразделение requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } delete: tags: [departments] summary: Удалить подразделение responses: "204": { description: Удалено } default: { $ref: "#/components/responses/Error" } /v0/permissions: get: tags: [django-permissions] summary: Список permission'ов parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ] responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } post: tags: [django-permissions] summary: Создать permission requestBody: required: true content: application/json: schema: type: object required: [name, codename] properties: name: { type: string } codename: { type: string } responses: "201": { description: Создан } default: { $ref: "#/components/responses/Error" } /v0/permissions/search: post: tags: [django-permissions] summary: Поиск permission'ов requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } /v0/permissions/{id}: parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ] get: tags: [django-permissions] summary: Permission по id responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } delete: tags: [django-permissions] summary: Удалить permission responses: "204": { description: Удалён } default: { $ref: "#/components/responses/Error" } /v0/resource-types: get: tags: [resource-types] summary: Список типов ресурсов responses: "200": { description: OK } default: { $ref: "#/components/responses/Error" } post: tags: [resource-types] summary: Создать тип requestBody: required: true content: application/json: schema: type: object required: [name] properties: name: { type: string } parent_id: { type: integer, format: int64, nullable: true } responses: "201": { description: Создан } default: { $ref: "#/components/responses/Error" } /v0/resource-types/{id}: parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ] get: tags: [resource-types] summary: Тип по id responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } put: tags: [resource-types] summary: Обновить тип requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } patch: tags: [resource-types] summary: Частично обновить тип requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } delete: tags: [resource-types] summary: Удалить тип responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } } /v0/resources: get: tags: [resources-v0] summary: Список ресурсов (v0) parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ] responses: "200": description: OK content: application/json: schema: type: object properties: count: { type: integer } results: type: array items: { $ref: "#/components/schemas/ResourceV0Output" } default: { $ref: "#/components/responses/Error" } post: tags: [resources-v0] summary: Создать ресурс requestBody: required: true content: application/json: schema: type: object required: [name, tenant_id] properties: name: { type: string } parent_id: { type: string, nullable: true } type_id: { type: integer, format: int64 } tenant_id: { type: integer, format: int64, minimum: 1 } created_by: { type: integer, format: int64 } responses: { "201": { description: Создан }, default: { $ref: "#/components/responses/Error" } } /v0/resources/{id}: parameters: [ { name: id, in: path, required: true, schema: { type: string } } ] get: tags: [resources-v0] summary: Ресурс по id responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } put: tags: [resources-v0] summary: Обновить ресурс requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } patch: tags: [resources-v0] summary: Частично обновить ресурс requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } delete: tags: [resources-v0] summary: Удалить ресурс responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } } /v0/projects: get: tags: [projects-v0] summary: Список проектов responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } post: tags: [projects-v0] summary: Создать проект requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "201": { description: Создан }, default: { $ref: "#/components/responses/Error" } } /v0/projects/{publicID}: parameters: [ { name: publicID, in: path, required: true, schema: { type: string, format: uuid } } ] get: tags: [projects-v0] summary: Проект по public id responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } put: tags: [projects-v0] summary: Обновить проект requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } patch: tags: [projects-v0] summary: Частично обновить проект requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } delete: tags: [projects-v0] summary: Удалить проект responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } } /v0/widgets: get: tags: [widgets-v0] summary: Список виджетов responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } post: tags: [widgets-v0] summary: Создать виджет requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "201": { description: Создан }, default: { $ref: "#/components/responses/Error" } } /v0/widgets/{publicID}: parameters: [ { name: publicID, in: path, required: true, schema: { type: string, format: uuid } } ] get: tags: [widgets-v0] summary: Виджет по public id responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } put: tags: [widgets-v0] summary: Обновить виджет requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } patch: tags: [widgets-v0] summary: Частично обновить виджет requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } delete: tags: [widgets-v0] summary: Удалить виджет responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } } /v1/resource: get: tags: [resources] summary: Список ресурсов (v1) parameters: - $ref: "#/components/parameters/Limit" - $ref: "#/components/parameters/Offset" - { name: parent_id, in: query, schema: { type: string, format: uuid } } - { name: type, in: query, schema: { type: integer } } - { name: tenant_id, in: query, description: "CSV", schema: { type: string } } - { name: name, in: query, schema: { type: string } } - { name: code, in: query, schema: { type: string } } - { name: public_id, in: query, schema: { type: string, format: uuid } } - { name: target_id, in: query, schema: { type: integer } } - { name: service_accounts, in: query, description: "CSV из uuid", schema: { type: string } } responses: "200": description: OK content: application/json: schema: type: object properties: count: { type: integer } results: type: array items: { $ref: "#/components/schemas/Resource" } default: { $ref: "#/components/responses/Error" } post: tags: [resources] summary: Создать ресурс requestBody: required: true content: application/json: schema: { $ref: "#/components/schemas/ResourceCreateInput" } responses: { "201": { description: Создан }, default: { $ref: "#/components/responses/Error" } } /v1/resource/{publicID}: parameters: [ { name: publicID, in: path, required: true, schema: { type: string, format: uuid } } ] get: tags: [resources] summary: Ресурс по public id responses: "200": description: OK content: { application/json: { schema: { $ref: "#/components/schemas/Resource" } } } default: { $ref: "#/components/responses/Error" } put: tags: [resources] summary: Обновить ресурс requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/ResourceUpdateInput" } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } patch: tags: [resources] summary: Частично обновить ресурс requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } delete: tags: [resources] summary: Удалить ресурс responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } } /v1/targets/{target_id}/resource: parameters: [ { name: target_id, in: path, required: true, schema: { type: integer, format: int64 } } ] get: tags: [resources] summary: Ресурс по target id (только внутренний контур) responses: "200": description: OK content: { application/json: { schema: { $ref: "#/components/schemas/Resource" } } } default: { $ref: "#/components/responses/Error" } /v1/resources-grouped-by-sa: get: tags: [resources] summary: Ресурсы, сгруппированные по сервисному аккаунту description: Регистрируется, только если включён соответствующий use case. responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } /v1/service-accounts: get: tags: [service-accounts] summary: Список сервисных аккаунтов responses: "200": description: OK content: application/json: schema: type: object properties: count: { type: integer } results: type: array items: { type: string, format: uuid } default: { $ref: "#/components/responses/Error" } /v2/resource: get: tags: [widgets] summary: Список виджетов (v2) parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ] responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } post: tags: [widgets] summary: Создать виджет requestBody: { required: true, content: { application/json: { schema: { $ref: "#/components/schemas/WidgetCreateInput" } } } } responses: { "201": { description: Создан }, default: { $ref: "#/components/responses/Error" } } /v2/resource/{publicID}: parameters: [ { name: publicID, in: path, required: true, schema: { type: string, format: uuid } } ] get: tags: [widgets] summary: Виджет по public id responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } put: tags: [widgets] summary: Обновить виджет requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } patch: tags: [widgets] summary: Частично обновить виджет requestBody: { required: true, content: { application/json: { schema: { type: object } } } } responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } delete: tags: [widgets] summary: Удалить виджет responses: { "204": { description: Удалён }, default: { $ref: "#/components/responses/Error" } } /v1/resource_permission: get: tags: [resource-permissions] summary: Список прав на ресурсы parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ] responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } post: tags: [resource-permissions] summary: Создать права (пакет) requestBody: required: true content: application/json: schema: type: object required: [items] properties: items: type: array items: { $ref: "#/components/schemas/ResourcePermissionCreateInput" } responses: { "201": { description: Создано }, default: { $ref: "#/components/responses/Error" } } /v1/resource_permission/{id}: parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ] get: tags: [resource-permissions] summary: Право по id responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } delete: tags: [resource-permissions] summary: Удалить право responses: { "204": { description: Удалено }, default: { $ref: "#/components/responses/Error" } } /v1/permissions-bulk: patch: tags: [resource-permissions] summary: Массовое изменение прав requestBody: required: true content: application/json: schema: type: object required: [tenant_id, permissions] properties: tenant_id: { type: integer, format: int64, minimum: 1 } permissions: type: object description: "map[resource_uuid][]permission_uuid" additionalProperties: type: array items: { type: string, format: uuid } unrestricted_permissions: type: object additionalProperties: true responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } /v1/company_resource_permission: get: tags: [company-resource-permissions] summary: Список прав компании parameters: [ { $ref: "#/components/parameters/Limit" }, { $ref: "#/components/parameters/Offset" } ] responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } post: tags: [company-resource-permissions] summary: Создать права компании (пакет) requestBody: required: true content: application/json: schema: type: object required: [items] properties: items: type: array items: { $ref: "#/components/schemas/CompanyResourcePermissionCreateInput" } responses: { "201": { description: Создано }, default: { $ref: "#/components/responses/Error" } } /v1/company_resource_permission/{id}: parameters: [ { name: id, in: path, required: true, schema: { type: integer, format: int64 } } ] get: tags: [company-resource-permissions] summary: Право компании по id responses: { "200": { description: OK }, default: { $ref: "#/components/responses/Error" } } delete: tags: [company-resource-permissions] summary: Удалить право компании responses: { "204": { description: Удалено }, default: { $ref: "#/components/responses/Error" } } components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT description: | `Authorization: Bearer `. Требуется во внешнем контуре (`/external/api/*`). Дополнительный заголовок `Identity` переключает разбор в режим Zitadel. Подпись не проверяется приложением. parameters: Limit: name: limit in: query description: Размер страницы (по умолчанию 1000) schema: { type: integer, default: 1000, minimum: 1 } Offset: name: offset in: query schema: { type: integer, default: 0, minimum: 0 } responses: Error: description: Ошибка content: application/json: schema: { $ref: "#/components/schemas/ApiError" } schemas: ApiError: type: object properties: error: { type: string, description: Человекочитаемое сообщение } id: { type: string, description: Стабильный slug доменной ошибки } User: type: object properties: id: { type: integer, format: int64 } username: { type: string } email: { type: string, format: email } first_name: { type: string } last_name: { type: string } is_staff: { type: boolean } is_active: { type: boolean } is_superuser: { type: boolean } job_title: { type: string } companies: type: array items: { type: integer, format: int64 } UserCreateInput: type: object required: [username, email, first_name, last_name] properties: username: { type: string } email: { type: string, format: email } first_name: { type: string } last_name: { type: string } is_staff: { type: boolean } is_active: { type: boolean } is_superuser: { type: boolean } job_title: { type: string } companies: type: array items: { type: integer, format: int64 } departments: type: array items: { type: integer, format: int64 } groups: type: array items: { type: integer, format: int64 } enable_notifications: { type: boolean } UserUpdateInput: type: object required: [username, email] properties: username: { type: string } email: { type: string, format: email } first_name: { type: string } last_name: { type: string } is_staff: { type: boolean } is_active: { type: boolean } is_superuser: { type: boolean } job_title: { type: string } companies: type: array items: { type: integer, format: int64 } UserBulkPatchItem: type: object required: [id] properties: id: { type: integer, format: int64 } username: { type: string } email: { type: string, format: email } first_name: { type: string } last_name: { type: string } is_staff: { type: boolean } is_active: { type: boolean } is_superuser: { type: boolean } job_title: { type: string } companies: { type: array, items: { type: integer, format: int64 } } departments: { type: array, items: { type: integer, format: int64 } } groups: { type: array, items: { type: integer, format: int64 } } enable_notifications: { type: boolean } CompanyUserActivationItem: type: object required: [user_id, company_id] properties: user_id: { type: integer, format: int64, minimum: 1 } company_id: { type: integer, format: int64, minimum: 1 } is_active: { type: boolean } UserPermissionCheckInput: type: object required: [user_id, checks] properties: user_id: { type: integer, format: int64, minimum: 1 } tenant_id: { type: integer, format: int64 } checks: type: array minItems: 1 maxItems: 100 items: type: object required: [permission] properties: permission: { type: string } public_resource_id: { type: string, format: uuid, nullable: true } UserPermissionCheckOutput: type: object properties: results: type: array items: type: object properties: permission: { type: string } public_resource_id: { type: string, format: uuid, nullable: true } allowed: { type: boolean } reason: { type: string } GroupCreateInput: type: object required: [name] properties: name: { type: string } description: { type: string } is_public: { type: boolean } company_id: { type: integer, format: int64, nullable: true } permission_ids: { type: array, items: { type: integer, format: int64 } } GroupUpdateInput: allOf: - $ref: "#/components/schemas/GroupCreateInput" - type: object required: [id] properties: id: { type: integer, format: int64 } PositionCreateInput: type: object required: [name, company_id] properties: name: { type: string } description: { type: string } company_id: { type: integer, format: int64, minimum: 1 } users: { type: array, items: { type: integer, format: int64 } } groups: { type: array, items: { type: integer, format: int64 } } PositionUpdateInput: allOf: - $ref: "#/components/schemas/PositionCreateInput" - type: object required: [id] properties: id: { type: integer, format: int64 } DepartmentCreateInput: type: object required: [name, company_id] properties: name: { type: string } description: { type: string } company_id: { type: integer, format: int64, minimum: 1 } legal_entity: { type: string } contractor: { type: object, nullable: true } users: { type: array, items: { type: integer, format: int64 } } groups: { type: array, items: { type: integer, format: int64 } } DepartmentUpdateInput: allOf: - $ref: "#/components/schemas/DepartmentCreateInput" - type: object required: [id] properties: id: { type: integer, format: int64 } Resource: type: object properties: public_id: { type: string, format: uuid } parent_id: { type: string, format: uuid, nullable: true } type_id: { type: integer, format: int64 } tenant_id: { type: integer, format: int64 } name: { type: string } description: { type: string } code: { type: string } target_id: { type: integer, format: int64 } show_in_overview: { type: boolean } location_verbose: { type: string } latitude: { type: number, format: double } longitude: { type: number, format: double } widgets: { type: object, additionalProperties: true } attributes: { type: object, additionalProperties: true } meta: { type: object, additionalProperties: true } ResourceCreateInput: type: object required: [name, tenant_id] properties: parent_id: { type: string, format: uuid, nullable: true, description: "public_id родителя" } name: { type: string } type_id: { type: integer, format: int64, description: "0/опущено — тип «Проект»" } tenant_id: { type: integer, format: int64, minimum: 1 } created_by: { type: integer, format: int64 } target_id: { type: integer, format: int64 } code: { type: string } ResourceUpdateInput: type: object required: [name, tenant_id] properties: parent_id: { type: string, format: uuid, nullable: true } type_id: { type: integer, format: int64 } tenant_id: { type: integer, format: int64, minimum: 1 } name: { type: string } description: { type: string } code: { type: string } target_id: { type: integer, format: int64 } planning_widget_id: { type: integer, format: int64, nullable: true } work_schedule_project_id: { type: integer, format: int64, nullable: true } show_in_overview: { type: boolean } location_verbose: { type: string } latitude: { type: number, format: double, minimum: -90, maximum: 90 } longitude: { type: number, format: double, minimum: -180, maximum: 180 } widgets: { type: object, additionalProperties: true } attributes: { type: object, additionalProperties: true } meta: { type: object, additionalProperties: true } ResourceV0Output: type: object properties: id: { type: string } public_id: { type: string } parent_id: { type: string, nullable: true } path: { type: string } type_id: { type: integer, format: int64 } tenant_id: { type: integer, format: int64 } name: { type: string } created_by: { type: integer, format: int64 } created_at: { type: string } updated_at: { type: string } WidgetCreateInput: type: object required: [name, tenant_id] properties: parent_id: { type: string, format: uuid, nullable: true } type_id: { type: integer, format: int64 } tenant_id: { type: integer, format: int64, minimum: 1 } name: { type: string } description: { type: string } code: { type: string } target_id: { type: integer, format: int64 } show_in_overview: { type: boolean } latitude: { type: number, format: double, minimum: -90, maximum: 90 } longitude: { type: number, format: double, minimum: -180, maximum: 180 } coordinate_system: { type: integer, format: int64 } widgets: { type: object, additionalProperties: true } attributes: { type: object, additionalProperties: true } meta: { type: object, additionalProperties: true } ResourcePermissionCreateInput: type: object required: [public_resource_id, service_account] properties: public_resource_id: { type: string, format: uuid } type: { type: integer, format: int64, minimum: 0 } service_account: { type: string, format: uuid } created_by: { type: integer, format: int64 } CompanyResourcePermissionCreateInput: type: object required: [tenant_id, service_account] properties: tenant_id: { type: integer, format: int64, minimum: 1 } service_account: { type: string, format: uuid } created_by: { type: integer, format: int64 } security: - bearerAuth: []