Документация Engee

Внешний программный интерфейс доступа к Engee

Страница в процессе разработки.

Engee предоставляет разработчикам сторонних приложений программный интерфейс (HTTP API), позволяющий стороннему приложению авторизоваться в Engee и совершать действия от имени пользователя в системе Engee.

Авторизация

Авторизация пользователя стороннего приложения в Engee осуществляется через стандартный механизм OAuth.

Разработчик стороннего приложения передает следующую информацию разработчику Engee:

  • redirect_uri — URL в стороннем приложении, куда будет возвращен пользователь.

Разработчик Engee передает разработчику стороннего приложения следующую информацию:

  • client_id — ID стороннего приложения;

  • client_secret — секретный ключ стороннего приложения;

  • scope — авторизованные API для данного приложения.


Как это работает?

  1. Стороннее приложение направляет пользователя на Authorize URL (https://engee.com/account/authorize) со следующими query параметрами:

  2. После того как пользователь даст согласие на предоставление данных, его перенаправит обратно в стороннее приложение на redirect_uri, со следующими query параметрами:

  3. После этого необходимо получить токен. Для этого нужно послать POST запрос на Token URL (https://engee.com/account/api/oauth2/token), со следующим телом (application/x-www-form-urlencoded):

    • grant_type: "authorization_code" — фиксированное значение;

    • code — полученный на втором шаге код;

    • client_id — ID стороннего приложения;

    • client_secret — секретный ключ стороннего приложения;

    • scope — авторизованные API для данного приложения;

    • redirect_uri — URL в стороннем приложении, куда будет возвращен пользователь;

      Пример запроса:

      curl -d grant_type=authorization_code -d code=$code -d client_id=$client_id -d client_secret=$client_secret -d "scope=user:id user:username profile:name profile:primary_email engee" -d redirect_uri=$redirect_uri https://engee.com/account/api/oauth2/token

      В ответ придет access token, которым авторизуются все последующие запросы к HTTP API. Также придет refresh token, который необходим для обновления access token.

    Для обновления токена необходимо послать запрос на адрес POST https://engee.com/account/api/oauth2/token c параметрами:

    • grant_type: "refresh_token" — фиксированное значение;

    • refresh_token — токен обновления;

    • client_id — ID стороннего приложения;

    • client_secret — секретный ключ стороннего приложения;

    • scope — авторизованные API для данного приложения;

    • redirect_uri — URL в стороннем приложении, куда будет возвращен пользователь.

      Время жизни access token: 10 минут, refresh token: 30 дней.
  4. Далее возможно получить данные пользователя с помощью полученного токена, для этого необходимо отправить запрос GET с заголовком (Header) на https://engee.com/account/api/oauth2/session.

    Пример запроса:

    curl -H "Authorization: Bearer $token" https://engee.com/account/api/oauth2/session

    Пример ответа:

    {"sub":"1d592c69-9664-4eb5-9166-447c421a02df","nickname":"username","email":"username@email.tld"}

Взаимодействие с Engee

Для работы всех API по взаимодействию с Engee нужно, чтобы система была запущена у пользователя (нажата кнопка Старт Engee в личном кабинете). Если нет возможности нажимать кнопку запуска, то можно также автоматизировать запуск и остановку Engee (см. описание методов управления Engee далее).

Во все запросы должен быть добавлен заголовок авторизации "Authorization: Bearer $token" (токен получен на этапе авторизации).

Методы API

Префикс для всех API Управления Engeehttps://engee.com.

Управление Engee

GET /account/api/engee/info — получение информации о состоянии Engee. Тело ответа:

{
"username": "string",
"serverName": "string",
"serverId": "string",
"url": "string",
"serverStatus": "starting"|"running"|"stopping"|"stopped",
"startingAt": "2025-02-10T10:24:31.328Z",
"startedAt": "2025-02-10T10:24:31.328Z",
"stoppingAt": "2025-02-10T10:24:31.328Z",
"stoppedAt": "2025-02-10T10:24:31.328Z",
"lastActivityAt": "2025-02-10T10:24:31.328Z",
"inactivityTimeout": 0,
"stopSessionAt": "2025-02-10T10:24:31.328Z",
"clusterNamespace": {
"minimalMode": true
}

POST /account/api/engee/start — запуск Engee (аналог нажатия кнопки Старт Engee в личном кабинете). Тело ответа (URL запускаемого сервера):

{
'server': string
}

DELETE /account/api/engee/stop — остановка Engee. Возвращает код ответа 204.

Префикс для всех остальных API имеет вид: https://engee.com/prod/user/$license-$nickname/. Префикс у каждого пользователя свой, его нужно брать из ответа состояния Engee из поля URL.

Исполнение произвольного кода

POST /external/command/eval

  1. Простейший запрос. Запрос:

    {
      "command": "3 + 5"
    }

    Возвращаемые данные:

    • Статус-код: 200.

      {
        "result": "8"
      }
  2. Ошибка при исполнении исходного кода. Запрос:

    {
      "command": "unexisting_variable"
    }

    Возвращаемые данные:

    • Статус-код: 400.

      {
        "result_code": "command_error",
        "error": {
          "full_description": "UndefVarError: `unexisting_variable` not defined\n\nStacktrace:\n  [1] top-level scope\n    @ none:1\n  [2] eval(m::Module, e::Any)\n    @ Core ./boot.jl:370\n  [3] top-level scope\n    @ none:4\n  [4] eval\n    @ ./boot.jl:370 [inlined]\n  [5] eval_mask_code(code::String)\n    @ Main.Masks /app/IJulia/preload_cells/masks.jl:206\n  [6] eval_mask_codes(mask_codes::Vector{Main.Masks.MaskCode})\n    @ Main.Masks /app/IJulia/preload_cells/masks.jl:197\n  [7] |>\n    @ ./operators.jl:907 [inlined]\n  [8] evaluate_mask_codes(mask_codes::Vector{Dict{String, String}})\n    @ Main.Masks /app/IJulia/preload_cells/masks.jl:272\n  [9] |>\n    @ ./operators.jl:907 [inlined]\n [10] (::Workspaces.Servers.var\"#eval_mask_codes#157\"{typeof(Main.Masks.evaluate_mask_codes), Workspaces.Servers.var\"#convert_mask_result#156\"})(blocks::Vector{Dict{String, String}})\n    @ Workspaces.Servers /usr/local/julia-1.9.3/packages/Workspaces/2XYbD/src/servers/handlers.jl:539\n [11] request_pipeline(req::HTTP.Messages.Request, log_func::var\"#13#14\"{String}, req_params_func::Function, log_response_func::Workspaces.Servers.var\"#mask_log_and_response#159\"{Workspaces.Servers.var\"#result_to_response#158\"}, pipeline_stages::Workspaces.Servers.PipelineStages)\n    @ Workspaces.Servers /usr/local/julia-1.9.3/packages/Workspaces/2XYbD/src/servers/handlers_common.jl:282\n [12] required_body_request_pipeline\n    @ /usr/local/julia-1.9.3/packages/Workspaces/2XYbD/src/servers/handlers_common.jl:289 [inlined]\n [13] eval_mask_code_handler\n    @ /usr/local/julia-1.9.3/packages/Workspaces/2XYbD/src/servers/handlers.jl:556 [inlined]\n [14] (::Workspaces.Servers.var\"#eval_mask_code#186\"{var\"#13#14\"{String}, typeof(Main.Masks.evaluate_mask_codes)})(req::HTTP.Messages.Request)\n    @ Workspaces.Servers /usr/local/julia-1.9.3/packages/Workspaces/2XYbD/src/servers/Servers.jl:68\n [15] (::HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing})(req::HTTP.Messages.Request)\n    @ HTTP.Handlers /usr/local/julia-1.9.3/packages/HTTP/sJD5V/src/Handlers.jl:439\n [16] (::HTTP.Handlers.var\"#1#2\"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}})(stream::HTTP.Streams.Stream{HTTP.Messages.Request, HTTP.Connections.Connection{Sockets.TCPSocket}})\n    @ HTTP.Handlers /usr/local/julia-1.9.3/packages/HTTP/sJD5V/src/Handlers.jl:58\n [17] #invokelatest#2\n    @ ./essentials.jl:819 [inlined]\n [18] invokelatest\n    @ ./essentials.jl:816 [inlined]\n [19] handle_connection(f::Function, c::HTTP.Connections.Connection{Sockets.TCPSocket}, listener::HTTP.Servers.Listener{Nothing, Sockets.TCPServer}, readtimeout::Int64, access_log::Nothing)\n    @ HTTP.Servers /usr/local/julia-1.9.3/packages/HTTP/sJD5V/src/Servers.jl:469\n [20] macro expansion\n    @ /usr/local/julia-1.9.3/packages/HTTP/sJD5V/src/Servers.jl:401 [inlined]\n [21] (::HTTP.Servers.var\"#16#17\"{HTTP.Handlers.var\"#1#2\"{HTTP.Handlers.Router{typeof(HTTP.Handlers.default404), typeof(HTTP.Handlers.default405), Nothing}}, HTTP.Servers.Listener{Nothing, Sockets.TCPServer}, Set{HTTP.Connections.Connection}, Int64, Nothing, ReentrantLock, Base.Semaphore, HTTP.Connections.Connection{Sockets.TCPSocket}})()\n    @ HTTP.Servers ./task.jl:514",
          "short_description": "UndefVarError(:unexisting_variable)",
          "type": "UndefVarError"
        }
      }

      здесь:

      • error.full_description — полное описание ошибки вместе со стектрейсом. Вывод аналогичен выводу ошибки в терминал;

      • error.short_description — исключение без стектрейса;

      • error.type — тип ошибки.

  3. Возврат JSON-а. Запрос:

    {
      "command": "using JSON\nJSON.json(\"a\"=>1)"
    }

    Возвращаемые данные:

    • Статус-код: 200.

      {
        "result": "{\"a\":1}"
      }

Загрузка файлов

POST /external/file/upload

Запрос отправляется в формате multipart/form-data.

Поле paths содержит JSON следующего формата:

{
  "sources": [
    "/user/some_dir"
  ]
}

здесь:

  • Каждый элемент внутри sources означает директорию, куда будет загружен файл;

  • Поле upload_files содержит файл, который необходимо загрузить;

  • Количество полей upload_files должно соответствовать количеству переданных директорий.

Пример запроса:

curl -X 'POST' \
  'https://engee.com/prod/user/$license-$user/external/file/upload' \
  -H 'Authorization: Bearer $access_token' \
  -H 'Content-Type: multipart/form-data' \
  -F 'paths={
  "sources": [
    "/user/some_directory"
  ]
}' \
  -F 'upload_files=@2.png;type=image/png'
  • Если директория, куда загружается файл, не существует, то она будет создана;

  • Все скачиваемые файлы должны находиться внутри директории /user, иначе вернется ошибка 403;

  • Если файл уже существует, то его к его имени добавится суффикс;

  • Файл будет записан на диск только после полной загрузки. Если загрузка будет прервана, то файл не появится в файловой системе.

Скачивание файлов

POST /external/file/download

Запрос:

{
  "sources": [
    "/user/some_file"
  ]
}
  • Если файл один, то он возвращается в формате multipart/form-data;

  • Если файлов несколько, то они сжимаются в ZIP и уже возвращается application/x-zip-compressed;

  • Если хотя бы один файл не существует, то в ответ вернется 404;

  • Все скачиваемые файлы должны находиться внутри директории /user, иначе вернется ошибка 403.