Advanced Endpoints

Advanced Endpoints let you implement custom, server-side execute-function behavior: a Lua function published on a clean URL path that encapsulates project-specific logic. This page explains how they work, the contract your Lua function receives, and a worked example.

Overview

Advanced Endpoints are built on execfunction. They are not a separate runtime — the only difference from a classic call is how invocation metadata is supplied and what the request body is used for.

Advanced Endpoint path style Classic execfunction

Route

/api/v2/execfunction/<library>/<function>

/api/v2/execfunction

Library / function routing

Encoded in the URL path

Supplied in query parameters or a JSON envelope

Context

Passed through query parameters such as ctx

Part of the query/envelope metadata

Request body

Dedicated to the endpoint payload; uses the content type your endpoint expects

A JSON invocation document (for the JSON-envelope POST pattern)

Because the Advanced Endpoint style moves routing into the URL, the request body is free to carry your actual payload in whatever content type the endpoint implementation expects.

The first path segment after /api/v2/execfunction/ is the library name. Whether a second segment is required depends on how the library is written (see Choose a library shape):

  • Library returns a table of functions — the second segment names the function to call:

    /api/v2/execfunction/corporate/readMachineHistory

    Here corporate is the Lua library and readMachineHistory is the function that will be executed.

  • Library returns a single function — there is no function segment; the library name alone routes to that function:

    /api/v2/execfunction/corporate

Any additional path segments and query parameters are made available to the function (see req) so you can implement routing logic inside Lua.

Before You Start

Prefer the Script Model. Store Advanced Endpoint scripts in the Script Model rather than on objects in the hierarchy. The Script Model and script libraries stored in the object hierarchy are two different mechanisms.

Understand ctx. If a script is stored in the object hierarchy — for example on a GenFolder in the I/O model — the default context path configured on the WebServer object may not match the intended execution context. In that case, provide the ctx query parameter to select a different execution context.

  • If the relevant scripts are available under the default context path configured on the WebServer object, ctx is not required.

  • If scripts are distributed across different objects in the hierarchy, provide ctx in the URL to select the desired execution context.

Keep in mind that changing ctx does not mean the logic runs on the node that owns that object.

Name the script in its first line. Begin each script with a comment holding the script (library) name, for example -- api-hello. The WebStudio Config app property editor shows the first line of the script as the script property, so this header lets you confirm at a glance that the stored script is the one you designed — and quickly spot a script that ended up in the wrong place.

Authorize the request. Include authorization information in the request headers according to your Web API authentication setup.

Choose a library shape. A script library can be implemented in two ways, and the choice determines the URL:

  1. Return a Lua table containing one or more functions. The caller selects a function with a second path segment: /api/v2/execfunction/<library>/<function>.

  2. Return a single function directly. No function segment is needed — the library name alone routes to it: /api/v2/execfunction/<library>.

Function Signature

Functions invoked through an Advanced Endpoint receive three parameters — arg, req, and hlp:

function lib:myfunction(arg, req, hlp)

Declare the function with colon notation. With colon notation Lua passes self implicitly, so the full logical signature is self, arg, req, hlp. In Lua, lib:myfunction(arg) is the same as lib.myfunction(lib, arg).

arg

The content of arg depends on the HTTP request body.

  • Empty body: arg is an empty Lua table.

  • JSON object body: each property becomes a key in the Lua table.

  • Any other non-empty body: arg becomes a table with a single key, data, containing the raw request body.

JSON object body:

{
    "starttime": "2019-04-02T10:00:00.000Z",
    "limit": 35
}

Lua view:

{
    ["starttime"] = "2019-04-02T10:00:00.000Z",
    ["limit"] = 35
}

Text body:

Hello World

Lua view:

{
    data = "Hello World"
}

JSON array body:

[
    "Inmation", "OT", "Data", "Fabric"
]

Lua view:

{
    data = { "Inmation", "OT", "Data", "Fabric" }
}

req

req is a Lua table describing the HTTP request and execution context.

Field Description

access_token_payload

Optional payload of the provided JSON Web Token.

headers

HTTP request headers.

method

HTTP method, for example GET, POST, PUT, DELETE, or PATCH.

path

The path segment after the function name. If none is provided, this is an empty string.

query

Query parameters as a Lua table. If a parameter appears more than once, the value is a Lua string array.

remote_ip_address

The client IP address.

server_oid

Object id of the WebServer object that processes the request.

context_info

Context information including ctx, profile, topic, and execfunction metadata (lib and func).

url

Full URL information including href, protocol, hostname, port, pathname, and optional search and hash.

Use req.path and req.query to implement routing logic inside Lua. For example, given:

/api/v2/execfunction/corporate/readMachineData/lab/1234?mode=full

readMachineData is the executed function and req.path is /lab/1234. The values in req.path and req.query are unescaped.

hlp

hlp is a helper object that exposes convenience functions commonly used by Advanced Endpoints. Use hlp:createResponse when you need to control the returned body, error payload, HTTP status code, or response headers.

  • checkPermission

  • createResponse

  • getProfileNames

  • isAdmin

  • isAdminOrReviewer

  • isJsonNull

  • isMemberOfProfile

  • isReviewer

checkPermission

Use checkPermission(pathspec, sec_attr, verbose) to validate whether the current user has the required permissions.

Parameter Description

pathspec

Object path, object reference, or numeric object/property id.

sec_attr

Bitwise OR combination of security attributes.

verbose

Optional flag controlling error detail.

createResponse

Use createResponse(data, error, status_code, headers) to build a custom HTTP response.

Parameter Description

data

Value or body returned by the endpoint.

error

Custom error payload.

status_code

HTTP status code.

headers

Response headers.

If Content-Type is set in headers, the data value drives the response body. If no content type is specified, an error payload takes precedence when provided.

Example

This is a simple example of an Advanced Endpoint.

Store this Lua snippet as api-hello on a Script Library Item in the Script Model.

-- api-hello

local lib = {}

function lib:say_hello(arg)
    local name = arg.name or 'unknown'
    local msg = string.format("Hello %s", name)
    return msg
end

return lib

The function is declared with colon notation, so it receives self implicitly along with arg.

Advanced Endpoint path style — library and function routing live in the URL path:

  • Method: POST

  • Request URL: /api/v2/execfunction/api-hello/say_hello

Request body:

{
    "name": "Ben"
}

Response — Content-Type text/plain:

"Hello Ben"

Example Bank

Read Actual Value

Store this Lua snippet as api-hello on a Script Library Item in the Script Model. If already created, overwrite it with the script below.

-- api-hello

local lib = {}

function lib:say_hello(arg, req, hlp)
    local name = arg.name or 'unknown'
    local msg = string.format("Hello %s", name)
    return msg
end

function lib:getvalue(arg, req, hlp)
    local path = arg.path
    local value = nil
    if path then
        value = syslib.getvalue(path, {
            prp_val_nil_as_null = true,
            error_strategy = 'nil'
        })
    end
    return {
        msg = self:say_hello(arg, req, hlp),
        value = value
    }
end

return lib

The function returns a plain Lua table, so the endpoint serializes it to JSON automatically and responds with Content-Type: application/json — you do not need hlp:createResponse for the common case. Reach for createResponse only when you need a different content type, status code, or response headers (see the CSV Response example).

  • Method: POST

  • Request URL: /api/v2/execfunction/api-hello/getvalue

Request body:

{
    "name": "Ben",
    "path": "/IO-Home/Examples/Demo Data/Process Data/DC0815"
}

Response — Content-Type application/json:

{
    "msg": "Hello Ben",
    "value": 14.958015345935872
}

CSV Response

Advanced Endpoints are not limited to JSON. This example exports aligned historical data for several items as a CSV download. It shows three techniques worth reusing:

  • Compose server-side with performaction. The endpoint builds a consolidate action pipeline that reads historical data for each item and aligns the values on a shared timestamp, then runs it in a single call — no client-side stitching of multiple reads.

  • Transform the result in Lua. The aligned payload is turned into CSV rows with esi-mapreduce (map/reduce): one header line followed by one line per interval, with the timestamp converted to ISO 8601 via syslib.gettime.

  • Control the response content type with hlp:createResponse. Returning the CSV string with a Content-Type: text/csv header makes the caller receive a CSV document rather than a JSON-wrapped value. This is the key difference from the previous example, which returned a plain Lua table and was serialized as JSON.

Store this Lua snippet as api-csv-exporter on a Script Library Item in the Script Model.

-- api-csv-exporter

local inAPI = require('syslib.api')
local MR = require('esi-mapreduce')
local map = MR.map
local reduce = MR.reduce

local itemDefList = {
    {
        name = "DC0815",
        path = "/IO-Home/Examples/Demo Data/Process Data/DC0815"
    },
    {
        name = "FC0815",
        path = "/IO-Home/Examples/Demo Data/Process Data/FC0815"
    },
    {
        name = "LC0815",
        path = "/IO-Home/Examples/Demo Data/Process Data/LC0815"
    }
}

local function getActionPipeline(starttime, endtime)
    return {
        ["type"] = "consolidate",
        ["starttime"] = starttime,
        ["endtime"] = endtime,
        ["mode"] = "align",
        ["items"] = map(itemDefList, function(itemDef)
            return {
                ["name"] = itemDef.name,
                ["outerJoin"] = true,
                ["action"] = {
                    ["type"] = "read-historical-data",
                    ["objspec"] = itemDef.path,
                    ["numberOfIntervals"] = 10
                }
            }
        end)
    }
end

local lib = {}

---Exports historical data for specified items in CSV format.
---@param arg { starttime: string, endtime: string }
---@param req SysLib.API.Req
---@param hlp SysLib.API.Hlp
function lib:export(arg, req, hlp)
    local starttime = arg.starttime
    local endtime = arg.endtime
    local actionPipeline = getActionPipeline(starttime, endtime)
    local output = inAPI:performaction({
        action = actionPipeline
    })
    -- Result is a table with the following structure:
    --[=[
    {
        "payload": [
            {
                "DC0815": 7.392681985576574,
                "FC0815": 38.947148370234174,
                "LC0815": 7827.927777973474,
                "t": 1780308000000
            },
            ...
        ]
    }
    ]=]

    -- Let's convert the payload to CSV format
    -- First the header
    local header = map(itemDefList, function(itemDef) return '"' .. itemDef.name .. '"' end)
    table.insert(header, 1, '"Timestamp"') -- Add the timestamp column at the beginning

    local csvLines = reduce(output.payload, function(acc, row)
        local csvLine = map(itemDefList, function(itemDef)
            return row[itemDef.name] or ""
        end)

        local isoTS = syslib.gettime(row.t)           -- Convert timestamp to ISO 8601 format
        table.insert(csvLine, 1, '"' .. isoTS .. '"') -- Add the timestamp at the beginning of the line
        table.insert(acc, table.concat(csvLine, ",")) -- Add the line as a CSV line to the accumulator
        return acc
    end, {
        table.concat(header, ",") -- Add the header line to csvLines
    })

    local csvResult = table.concat(csvLines, "\n") -- Join all lines with newline characters
    local respHeader = {
        ["Content-Type"] = "text/csv"
    }
    return hlp:createResponse(csvResult, nil, 200, respHeader)
end

return lib
  • Method: POST

  • Request URL: /api/v2/execfunction/api-csv-exporter/export

Request body:

{
    "starttime": "2026-06-01T10:00:00.000Z",
    "endtime": "2026-06-01T10:10:00.000Z"
}

Response — Content-Type text/csv:

"Timestamp","DC0815","FC0815","LC0815"
"2026-06-01T10:00:00.000Z",7.3926819855765737,38.947148370234174,7827.9277779734739
"2026-06-01T10:01:00.000Z",7.3926819855765737,38.947148370234174,7827.9277779734739
"2026-06-01T10:02:00.000Z",7.3581843217877791,38.282693521966472,7925.4648197289243
"2026-06-01T10:03:00.000Z",7.4169059273742795,37.968567011845543,7928.2033615618138
"2026-06-01T10:04:00.000Z",7.4169059273742795,37.968567011845543,7928.2033615618138
"2026-06-01T10:05:00.000Z",7.5675904860405367,37.904043781454952,8073.1551419058515
"2026-06-01T10:06:00.000Z",7.6175959135489917,37.274615032346,8270.1216216287266
"2026-06-01T10:07:00.000Z",7.7954935853757732,37.87439894189508,8417.9627637799149
"2026-06-01T10:08:00.000Z",7.7954935853757732,37.87439894189508,8417.9627637799149
"2026-06-01T10:09:00.000Z",7.6780406972507134,37.606963538303027,8359.2323332829274

Echo

The echo endpoint is a diagnostic tool: it returns whatever arg and req it received, so you can inspect exactly what an Advanced Endpoint function is handed for any request you send. Use it to see how a request body maps to arg, how extra path segments and query parameters land in req.path and req.query, and what your token payload and headers look like in req.

It also demonstrates the single-function library shape: the script returns one function directly, so the URL has no function segment — echo is the library name and the trailing /info/hello%20to%20you becomes req.path.

Store this Lua snippet as echo on a Script Library Item in the Script Model.

-- echo

return function(arg, req)
    return {
        arg = arg,
        req = req
    }
end
  • Method: POST

  • Request URL: /api/v2/execfunction/echo/info/hello%20to%20you?name=inmation&path=%2FHome

Request body:

{
    "msg": "Hello World"
}

Response — Content-Type application/json. The endpoint echoes the arg it derived from the request body and the full req context:

{
    "arg": {
        "msg": "Hello World"
    },
    "req": {
        "access_token_payload": {
            "exp": 1783001253,
            "iat": 1783000953,
            "in_prf": [
                "<USERNAME / PROFILE>"
            ],
            "in_usr": "019ef5d8-3759-72a1-8062-53f1a844709a",
            "nbf": 1783000953,
            "sub": "<USERNAME>"
        },
        "context_info": {
            "ctx": "/Home/WebServer",
            "execfunction": {
                "lib": "echo"
            },
            "topic": "execfunction"
        },
        "headers": {
            "Content-Length": "28",
            "Host": "<HOSTNAME>",
            "accept": "*/*",
            "accept-encoding": "gzip, deflate, br",
            "content-type": "application/json"
        },
        "method": "POST",
        "path": "/info/hello to you",
        "query": {
            "name": "inmation",
            "path": "/Home"
        },
        "remote_ip_address": "<IP-ADDRESS>",
        "server_oid": "019ef5d8-386e-708f-8036-d6c96fb60a72",
        "url": {
            "hostname": "<HOSTNAME>",
            "href": "<BASEURL>/api/v2/execfunction/echo/info/hello%20to%20you?name=inmation&path=%2FHome",
            "pathname": "/api/v2/execfunction/echo/info/hello to you",
            "port": 8008,
            "protocol": "http:",
            "search": "?name=inmation&path=%2FHome"
        }
    }
}