Engee documentation

External software interface for accessing Engee

Engee provides a programming interface (HTTP API) to developers of third-party applications that allows a third-party application to log in to Engee and perform actions on behalf of a user on the Engee system.

Authorisation

Authorisation of a user of a third-party application in Engee is done through the standard OAuth mechanism.

The third-party application developer passes the following information to the Engee developer:

  • redirect_uri - the URL in the third-party application where the user will be returned to.

The Engee developer passes the following information to the third-party application developer:

  • client_id - the ID of the third-party application;

  • client_secret - the secret key of the third-party application;

  • scope - authorised APIs for this application.


How does it work?

  1. The third-party application directs the user to the Authorise URL (https://engee.com/account/authorize) with the following query parameters:

  2. Once the user consents to provide the data, the user will be redirected back to the third-party application at redirect_uri, with the following query parameters:

  3. After that it is necessary to get the token. This is done by sending a POST request to the Token URL (https://engee.com/account/api/oauth2/token), with the following body (application/x-www-form-urlencoded):

    • grant_type: "authorisation_code" - fixed value;

    • code - the code obtained in the second step;

    • client_id - ID of the third-party application;

    • client_secret - secret key of the third-party application;

    • scope - authorised APIs for this application;

    • redirect_uri - URL in the third-party application where the user will be returned to;

      Example request:

      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

      In response, an access token will be sent, which is used to authorise all subsequent requests to the HTTP API. The refresh token, which is necessary to update the access token, will also be sent.

    To refresh the token it is necessary to send a request to POST address https://engee.com/account/api/oauth2/token with parameters:

    • grant_type: "refresh_token" - fixed value;

    • refresh_token - refresh token;

    • client_id - ID of the third-party application;

    • client_secret - secret key of the third-party application;

    • scope - authorised APIs for this application;

    • redirect_uri - URL in the third-party application where the user will be returned to.

      Access token lifetime: 10 minutes, refresh token: 30 days.
  4. Further it is possible to get the user data using the received token, for this purpose it is necessary to send a GET request with a header (Header) to https://engee.com/account/api/oauth2/session.

    Example request:

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

    Example of a response:

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

Interaction with Engee

For all the APIs for interaction with Engee to work, the system must be started by the user (the Start Engee button in the personal account is pressed). If it is not possible to press the Start button, it is also possible to automate starting and stopping Engee (see description of Engee management methods below).

An authorisation header "Authorisation: Bearer $token" (token received at the authorisation stage) should be added to all requests.

API methods

Prefix for all Engee Authority APIs - https://engee.com.

Engee Management

GET /account/api/engee/info - get Engee status information. Response body:

{
"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 - start Engee (analogue of clicking the Start Engee button in personal account). Response body (URL of the server to be launched):

{
'server': string
}

DELETE /account/api/engee/stop - stopping Engee. Returns response code 204.

The prefix for all other APIs is of the form: https://engee.com/prod/user/$license-$nickname/. The prefix is different for each user, it should be taken from the Engee state response from the URL field.

Execution of arbitrary code

POST /external/command/eval.

  1. The simplest query. Query:

    {
      "command": "3 + 5"
    }

    Returned data:

    • Status code: 200.

      {
        "result": "8"
      }
  2. Error during source code execution. Request:

    {
      "command": "unexisting_variable"
    }

    Returned data:

    • Status code: 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"
        }
      }

      here:

      • error.full_description - full description of the error along with the stacktrace. The output is similar to the output of the error in the terminal;

      • error.short_description - an exception without a stacktrace;

      • error.type - error type.

  3. JSON return. Query:

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

    Returned data:

    • Status code: 200.

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

Uploading files

POST /external/file/upload.

The request is sent in multipart/form-data format.

The paths field contains JSON of the following format:

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

here:

  • Each element inside sources denotes the directory where the file will be uploaded;

  • The upload_files field contains the file to be uploaded;

  • The number of upload_files fields must correspond to the number of passed directories.

Example request:

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'
  • If the directory where the file is being downloaded does not exist, it will be created;

  • All downloaded files must be inside the /user directory, otherwise error 403 will be returned;

  • If the file already exists, a suffix will be added to its name;

  • The file will be written to the disc only after the download is complete. If the download is interrupted, the file will not appear in the file system.

Downloading files

POST /external/file/download.

Request:

{
  "sources": [
    "/user/some_file"
  ]
}
  • If the file is single, it is returned in multipart/form-data format;

  • If there are multiple files, they are ZIP-compressed and application/x-zip-compressed is already returned;

  • If at least one file does not exist, a 404 will be returned;

  • All downloaded files must be inside the /user directory, otherwise error 403 will be returned.