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?
-
The third-party application directs the user to the
Authorise URL
(https://engee.com/account/authorize) with the following query parameters:-
client_id
- ID of the third-party application; -
redirect_uri
- URL in the third-party application where the user will be returned to; -
response_type
- response type (must becode
); -
scope
- authorised APIs for this application; -
state
- data from the third-party application that will be returned in the same form when the user is redirected toredirect_uri
.
-
-
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:-
scope
- the authorised APIs for this application; -
code
- the code for authorisation; -
state
- thestate
parameters passed to Engee in the first step.Example of redirect page with query parameters: http://redirect_uri/?code=code&scope=user%3Aid%20user%3Ausername%20profile%3Aname%20profile%3Aprimary_email%20engee.
-
-
After that it is necessary to get the token. This is done by sending a
POST
request to theToken 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.
-
-
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
.
-
The simplest query. Query:
{ "command": "3 + 5" }
Returned data:
-
Status code: 200.
{ "result": "8" }
-
-
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.
-
-
-
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.