{
  "meta": {
    "title": "Laitap REST API — справочник для ИИ",
    "version": "1.0.0",
    "updated": "2026-06-19",
    "documentationUrl": "https://laitap.pro/documentation.html#api-intro",
    "baseUrl": "https://api.laitap.pro/api",
    "contentType": "application/json; charset=utf-8",
    "purpose": "Машиночитаемая документация REST API Laitap PRO для ассистентов, агентов и скриптов автоматизации"
  },
  "instructions_for_ai": "Ты работаешь с REST API Laitap — облачного хранилища HTTP-коллекций (аналог Postman). Базовый URL: https://api.laitap.pro/api. Все запросы к REST API требуют заголовок Authorization: Bearer ltp_<api_key> (ключ создаётся в приложении Laitap → Настройки → API-ключи; нужна подписка PRO). Сессионный JWT входа в desktop-приложение для REST API не используй. Формат тел — JSON UTF-8, Content-Type: application/json. Коллекция — дерево папок и HTTP-запросов. Личное облако (teamId=null): CRUD через /collections, тело в формате Postman data (info+item) или native (name+children). Командные коллекции: только через /teams/{teamId}/collections/..., только native-формат (children), не data. Папки и запросы не имеют отдельных эндпоинтов — всегда GET коллекцию, измени дерево, отправь PUT с полным содержимым. Ошибки: JSON { \"error\": \"описание\" }. Коды: 200/201 успех, 400 валидация, 401 ключ/PRO, 403 лимиты/права, 404 не найдено, 429 rate limit, 500 сервер. При генерации curl/скриптов подставляй переменные {{name}} как в Postman. UUID генерируй v4 для id узлов в native-формате.",
  "authentication": {
    "type": "bearer",
    "header": "Authorization",
    "format": "Bearer ltp_<api_key>",
    "requirements": [
      "Активная подписка PRO у владельца API-ключа",
      "Ключ создаётся в Laitap → Настройки → API-ключи",
      "JWT сессии desktop-приложения для REST API не предназначен"
    ],
    "example": "Authorization: Bearer ltp_a1b2c3d4e5f6..."
  },
  "workspaces": {
    "personal": {
      "description": "Личные коллекции пользователя (teamId = null)",
      "basePath": "/collections",
      "bodyFormats": ["postman_data", "native_children"],
      "limits": { "freeInApp": 10, "proInApp": 100, "restApiRequiresPro": true }
    },
    "team": {
      "description": "Коллекции командного workspace (teamId != null)",
      "basePath": "/teams/{teamId}/collections",
      "bodyFormats": ["native_children_only"],
      "notes": [
        "Через /collections/{id} командные коллекции недоступны",
        "Права зависят от роли: owner, admin, member",
        "Зашифрованные ZK-коллекции: encrypted=true, encryptedPayload вместо children"
      ]
    }
  },
  "dataFormats": {
    "postman_data": {
      "description": "Postman Collection v2.1 — поле data с info и item",
      "usedIn": ["personal collections POST/PUT/import"],
      "schema": {
        "data": {
          "info": { "name": "string optional", "description": "string optional" },
          "item": "array of folder or request nodes"
        }
      },
      "folderNode": { "name": "string", "description": "string optional", "item": "array" },
      "requestNode": {
        "name": "string",
        "request": {
          "method": "GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS",
          "url": "string",
          "header": [{ "key": "string", "value": "string", "disabled": "boolean optional" }],
          "body": { "mode": "none|raw|formdata|urlencoded|graphql", "raw": "string optional" }
        }
      }
    },
    "native_children": {
      "description": "Нативный формат Laitap",
      "usedIn": ["personal PUT alternative", "team collections all mutations"],
      "collectionFields": {
        "name": "string required 1-200 chars",
        "description": "string optional up to 50000",
        "auth": { "type": "string", "credentials": "object" },
        "variables": [{ "id": "string", "key": "string", "value": "string", "enabled": "boolean" }],
        "scripts": { "prerequest": "string", "test": "string" },
        "children": "array of tree nodes"
      },
      "folderNode": {
        "id": "uuid optional",
        "type": "folder",
        "name": "string",
        "children": "array",
        "auth": "object optional",
        "variables": "array optional",
        "scripts": "object optional"
      },
      "requestNode": {
        "id": "uuid optional",
        "type": "request",
        "name": "string",
        "request": "HttpRequest object — см. httpRequest"
      },
      "smartFolderNode": {
        "type": "smartFolder",
        "smartMode": "tags|rules",
        "tagConfig": "object optional",
        "rules": "array optional"
      }
    },
    "httpRequest": {
      "id": "uuid optional",
      "name": "string required 1-200",
      "method": "GET|POST|PUT|PATCH|DELETE|HEAD|OPTIONS",
      "url": "string required, supports {{variables}}",
      "params": [{ "id": "string", "key": "string", "value": "string", "enabled": "boolean" }],
      "headers": [{ "id": "string", "key": "string", "value": "string", "enabled": "boolean" }],
      "auth": { "type": "inherit|bearer|basic|apikey|noauth|...", "credentials": "object" },
      "body": {
        "mode": "none|raw|formdata|urlencoded|graphql",
        "raw": "string",
        "rawLanguage": "json|text|...",
        "formData": "array",
        "urlEncoded": "array",
        "graphqlQuery": "string",
        "graphqlVariables": "string"
      },
      "scripts": { "prerequest": "string", "test": "string" },
      "tags": ["string"],
      "description": "string optional"
    }
  },
  "teamRoles": {
    "member": ["read collections", "create collection", "edit collection"],
    "admin": ["all member", "delete collection", "publish docs", "enable team vault encryption"],
    "owner": ["all admin", "delete team", "manage members"]
  },
  "endpoints": [
    {
      "group": "personal_collections",
      "method": "GET",
      "path": "/collections",
      "summary": "Список личных коллекций (метаданные, пагинация)",
      "query": {
        "page": "integer default 1",
        "limit": "integer default 20, max 100",
        "search": "string optional, substring in name"
      },
      "response200": {
        "items": [{ "id": "uuid", "owner_id": "uuid", "name": "string", "description": "string|null", "created_at": "ISO8601", "updated_at": "ISO8601" }],
        "page": 1,
        "limit": 20,
        "total": 1
      }
    },
    {
      "group": "personal_collections",
      "method": "GET",
      "path": "/collections?full=1",
      "summary": "Все личные коллекции с полным содержимым (синхронизация)",
      "response200": {
        "collections": ["array of full collection objects with children or data"],
        "limits": { "maxCollections": 100, "count": 3 }
      }
    },
    {
      "group": "personal_collections",
      "method": "GET",
      "path": "/collections/{id}",
      "summary": "Одна личная коллекция",
      "pathParams": { "id": "uuid collection id" },
      "response200": { "collection": { "id": "uuid", "owner_id": "uuid", "name": "string", "data": "postman object" } }
    },
    {
      "group": "personal_collections",
      "method": "POST",
      "path": "/collections",
      "summary": "Создать личную коллекцию",
      "body": {
        "name": "string required 1-255",
        "description": "string optional up to 1000",
        "data": "postman object optional — default empty item[]"
      },
      "response201": { "collection": "created collection object" }
    },
    {
      "group": "personal_collections",
      "method": "POST",
      "path": "/collections/import",
      "summary": "Импорт JSON-коллекции (Postman/Laitap export)",
      "body": {
        "name": "string required 1-255",
        "description": "string optional",
        "data": "object required, max 5MB, nesting max 20 levels"
      }
    },
    {
      "group": "personal_collections",
      "method": "PUT",
      "path": "/collections/{id}",
      "summary": "Обновить личную коллекцию (частично или полное дерево)",
      "body": {
        "name": "string optional",
        "description": "string optional",
        "data": "postman object optional — replaces full tree",
        "children": "native array optional — alternative to data"
      },
      "notes": ["Обновляются только переданные поля", "Для изменения папок/запросов передайте полное дерево"]
    },
    {
      "group": "personal_collections",
      "method": "DELETE",
      "path": "/collections/{id}",
      "summary": "Удалить личную коллекцию (soft delete)",
      "response200": { "ok": true }
    },
    {
      "group": "share",
      "method": "GET",
      "path": "/collections/{id}/share",
      "summary": "Статус публикации docs на laitap.pro/docs/{slug}",
      "response200": {
        "share": {
          "collectionId": "uuid",
          "published": true,
          "visibility": "public|password|team|off",
          "slug": "string",
          "url": "https://laitap.pro/docs/slug",
          "hasPassword": false,
          "publishedAt": "ISO8601",
          "updatedAt": "ISO8601"
        }
      }
    },
    {
      "group": "share",
      "method": "POST",
      "path": "/collections/{id}/share",
      "summary": "Опубликовать или изменить публичную документацию",
      "body": {
        "visibility": "public|password|team|off required",
        "password": "string 4-128 required when visibility=password on first publish",
        "regenerateSlug": "boolean optional",
        "snapshot": "object optional for ZK collections",
        "publishedVariables": [{ "key": "string", "value": "string optional up to 50000" }]
      },
      "notes": ["team visibility only for team collections", "admin/owner role required for team collections"]
    },
    {
      "group": "teams",
      "method": "GET",
      "path": "/teams",
      "summary": "Список команд текущего пользователя",
      "response200": {
        "teams": [{
          "id": "uuid",
          "name": "string",
          "ownerId": "uuid",
          "createdAt": "ISO8601",
          "currentUserRole": "owner|admin|member",
          "members": [{ "userId": "uuid", "email": "string", "role": "string", "joinedAt": "ISO8601" }]
        }]
      }
    },
    {
      "group": "teams",
      "method": "POST",
      "path": "/teams",
      "summary": "Создать команду",
      "body": { "name": "string required" },
      "response201": { "team": "team object" }
    },
    {
      "group": "teams",
      "method": "GET",
      "path": "/teams/{teamId}",
      "summary": "Команда и участники",
      "pathParams": { "teamId": "uuid" }
    },
    {
      "group": "teams",
      "method": "GET",
      "path": "/teams/{teamId}/members",
      "summary": "Участники команды"
    },
    {
      "group": "teams",
      "method": "POST",
      "path": "/teams/{teamId}/invites",
      "summary": "Пригласить участника по email",
      "body": { "email": "string", "role": "member|admin optional default member" }
    },
    {
      "group": "teams",
      "method": "GET",
      "path": "/teams/invites/pending",
      "summary": "Входящие приглашения для текущего пользователя"
    },
    {
      "group": "teams",
      "method": "POST",
      "path": "/teams/invites/{inviteId}/accept",
      "summary": "Принять приглашение в команду"
    },
    {
      "group": "teams",
      "method": "POST",
      "path": "/teams/invites/{inviteId}/decline",
      "summary": "Отклонить приглашение"
    },
    {
      "group": "team_collections",
      "method": "GET",
      "path": "/teams/{teamId}/collections",
      "summary": "Все коллекции команды с содержимым",
      "response200": {
        "collections": [{ "id": "uuid", "name": "string", "encrypted": false, "children": [] }],
        "limits": { "maxCollections": 100, "count": 2 }
      },
      "notes": ["encrypted=true returns encryptedPayload instead of children"]
    },
    {
      "group": "team_collections",
      "method": "GET",
      "path": "/teams/{teamId}/collections/versions",
      "summary": "Версии коллекций для лёгкой синхронизации",
      "response200": { "versions": [{ "id": "uuid", "updatedAt": "ISO8601" }] }
    },
    {
      "group": "team_collections",
      "method": "POST",
      "path": "/teams/{teamId}/collections",
      "summary": "Создать коллекцию в команде (role: member+)",
      "body": "native collection object — name required, children optional"
    },
    {
      "group": "team_collections",
      "method": "PUT",
      "path": "/teams/{teamId}/collections/{collectionId}",
      "summary": "Обновить коллекцию команды — полная замена содержимого (role: member+)",
      "body": "native collection object with full children tree"
    },
    {
      "group": "team_collections",
      "method": "DELETE",
      "path": "/teams/{teamId}/collections/{collectionId}",
      "summary": "Удалить коллекцию команды (role: admin+)",
      "response200": { "ok": true }
    }
  ],
  "workflows": [
    {
      "name": "create_personal_collection_with_requests",
      "steps": [
        "POST /collections with name and data.item tree (folders have item[], requests have request object)",
        "Or POST empty collection then PUT /collections/{id} with full data.item"
      ]
    },
    {
      "name": "add_request_to_personal_folder",
      "steps": [
        "GET /collections/{id}",
        "Modify data.item tree — add request node inside target folder",
        "PUT /collections/{id} with complete data object"
      ]
    },
    {
      "name": "sync_team_collection",
      "steps": [
        "GET /teams — find teamId",
        "GET /teams/{teamId}/collections/versions — check updatedAt",
        "GET /teams/{teamId}/collections — full download if changed",
        "Modify children array locally",
        "PUT /teams/{teamId}/collections/{collectionId} with full native body"
      ]
    },
    {
      "name": "publish_api_docs",
      "steps": [
        "POST /collections/{id}/share with visibility public or password",
        "Share returned url field with consumers"
      ]
    },
    {
      "name": "import_postman_export",
      "steps": [
        "Parse Postman collection JSON export",
        "POST /collections/import with name, optional description, data = export body"
      ]
    }
  ],
  "errors": {
    "format": { "error": "human-readable message in Russian or English" },
    "statusCodes": {
      "200": "Successful GET/PUT/DELETE",
      "201": "Resource created",
      "400": "Invalid JSON or parameters",
      "401": "Missing/invalid API key, revoked key, or expired PRO subscription",
      "403": "Collection limit reached or insufficient team role",
      "404": "Collection/team not found or wrong workspace",
      "410": "Team invite expired",
      "429": "Rate limit exceeded",
      "500": "Server error"
    }
  },
  "limits": {
    "collectionDataMaxBytes": 5242880,
    "collectionNestingMaxDepth": 20,
    "rateLimit": {
      "perIpPerMinute": 500,
      "collectionsGetPerUserPerMinute": 100,
      "collectionsWritePerUserPerMinute": 30
    },
    "collectionCounts": {
      "personalFreeInApp": 10,
      "personalPro": 100,
      "teamsCreatedFree": 1,
      "teamsCreatedPro": 10,
      "teamMembersFree": 7,
      "teamMembersPro": 100
    }
  },
  "examples": {
    "authHeader": "Authorization: Bearer ltp_YOUR_KEY_HERE",
    "createPersonalCollection": {
      "method": "POST",
      "url": "https://api.laitap.pro/api/collections",
      "headers": { "Authorization": "Bearer ltp_...", "Content-Type": "application/json" },
      "body": {
        "name": "Shop API",
        "data": {
          "info": { "name": "Shop API", "description": "" },
          "item": [
            {
              "name": "Catalog",
              "item": [
                {
                  "name": "List products",
                  "request": {
                    "method": "GET",
                    "url": "https://api.shop.example/v1/products",
                    "header": [{ "key": "Accept", "value": "application/json", "disabled": false }],
                    "body": { "mode": "raw", "raw": "" }
                  }
                }
              ]
            }
          ]
        }
      }
    },
    "updateTeamCollectionNative": {
      "method": "PUT",
      "url": "https://api.laitap.pro/api/teams/{teamId}/collections/{collectionId}",
      "body": {
        "name": "Shop API",
        "children": [
          {
            "id": "550e8400-e29b-41d4-a716-446655440001",
            "type": "folder",
            "name": "Catalog",
            "children": [
              {
                "id": "550e8400-e29b-41d4-a716-446655440002",
                "type": "request",
                "name": "List products",
                "request": {
                  "id": "550e8400-e29b-41d4-a716-446655440002",
                  "name": "List products",
                  "method": "GET",
                  "url": "https://api.shop.example/v1/products",
                  "params": [],
                  "headers": [{ "id": "1", "key": "Accept", "value": "application/json", "enabled": true }],
                  "auth": { "type": "inherit", "credentials": {} },
                  "body": { "mode": "none", "raw": "", "rawLanguage": "json", "formData": [], "urlEncoded": [], "graphqlQuery": "", "graphqlVariables": "{}" },
                  "scripts": { "prerequest": "", "test": "" }
                }
              }
            ]
          }
        ]
      }
    },
    "publishDocs": {
      "method": "POST",
      "url": "https://api.laitap.pro/api/collections/{id}/share",
      "body": { "visibility": "public" }
    }
  },
  "importantRules": [
    "REST API requires PRO subscription and ltp_ API key — not desktop JWT",
    "Team collections cannot be accessed via /collections/{id} — use /teams/{teamId}/collections/{collectionId}",
    "Folders and requests have no separate CRUD endpoints — always PUT full collection tree",
    "Personal cloud accepts Postman data OR native children; team collections accept only native format",
    "PUT replaces entire tree — always send complete data.item or children, not partial diffs",
    "Variables use {{name}} syntax in urls, headers, body",
    "Soft delete on personal collections — DELETE /collections/{id} returns { ok: true }",
    "403 on team operations usually means wrong role or not a team member",
    "404 on team collection often means collectionId does not belong to specified teamId"
  ]
}
