Skip to content

Custom Page API (scadaink) ​

Last updated

Every custom page has a global scadaink object (also window.scadaink). It runs in the viewer's browser with the viewer's login: each call goes to the Scada.Ink REST API as that user, so the server applies that user's role. An operator's page can't do what an operator can't do. On a public page opened without logging in, calls that need a login fail, and live tags are limited to published ones (see Custom Page Security).

Unlike server scripts, most calls here return a Promise. Use .then() / .catch(), or await — top-level await works in the page's JavaScript. A script that uses it runs inside an async function, so its own functions are local: for an inline onclick="…" handler, assign the function to window (see Custom Pages). A failed call rejects with an Error whose message is the server's error, or HTTP 403 style text.

Device ids and names ​

Tag and history functions take a device's numeric id — the same id shown by Tag Snippet and used in tag:3/… shortcodes — or its name, such as 'CNC-001' (upper/lower case doesn't matter). A name is looked up once from the device list, which needs a signed-in viewer, so on a public page opened without logging in, use the numeric id. An unknown name logs scadaink.subscribe: Device not found: X to the browser console, and history calls reject with Device not found: X.

scadaink.read and scadaink.tags.readAll don't make a request, so with a name they only work once a subscribe call has looked it up.

Live tags ​

FunctionReturnsNotes
scadaink.subscribe(deviceId, tagPath, callback)nothingcallback(value, update): value is a string; update is { deviceId, tagPath, value, dataType, quality, timestamp }. The current value is sent immediately if the server has one, then on every change.
scadaink.read(deviceId, tagPath)string or undefinedLast value received by this page — undefined unless you've subscribed to that tag and a value has arrived. No request is made.
scadaink.tags.subscribe / scadaink.tags.readSame as above.
scadaink.tags.readAll(deviceId){ [tagPath]: string }Only the tags this page has received values for.
scadaink.write(tagPath, value)PromiseWrites a local tag. The tag must exist (Local tag not found). Roles: admin, editor, operator.
scadaink.tags.write(tagPath, value)PromiseSame as scadaink.write.
scadaink.tags.writeDevice(deviceId, tagPath, value)PromiseWrites a device tag. Roles: admin, editor, operator. The device must have writes enabled, and the tag's write security level applies.
javascript
scadaink.subscribe(3, 'Spindle/Speed', function (value, update) {
  document.getElementById('speed').textContent = Number(value).toFixed(0);
  document.getElementById('speed').classList.toggle('stale', update.quality !== 'good');
});

document.getElementById('reset').addEventListener('click', function () {
  scadaink.write('Line1/ResetRequest', 'true')
    .then(function () { showMessage('Reset sent'); })
    .catch(function (err) { showMessage('Reset failed: ' + err.message); });
});

(showMessage stands for your own function that puts text on the page — alert() is blocked.)

Values are written as strings. There is no unsubscribe function; subscriptions last until the page is closed or reloaded.

Devices ​

FunctionResolves to
scadaink.devices.list()The device list, as the Devices page gets it
scadaink.devices.find(name)Numeric id or null (name ignores case)
scadaink.devices.localId()Id of the built-in local device, or null

Alarms ​

FunctionResolves to
scadaink.alarms.getActive()Active alarms, as the Alarms page gets them
scadaink.alarms.acknowledge(alarmId)The API response; recorded as acknowledged by the viewer

History ​

FunctionResolves to
scadaink.historian.query(deviceId, tagPath, start, stop?)[{ timestamp, value }], oldest first, up to 1000 points
scadaink.historian.queryAggregated(deviceId, tagPath, start, stop, window, fn)[{ timestamp, value }], up to 2000 points; pass undefined for stop
scadaink.history(deviceId, tagPath, start, stop?)Same as historian.query

start/stop accept relative times ('-1h', '-7d') or ISO timestamps, in UTC (…Z) or with an offset such as +02:00. fn is mean, max, min, sum, count, last or first. If the historian is unavailable the call rejects with InfluxDB is not available.

javascript
scadaink.historian.queryAggregated(3, 'Coolant/Temp', '-24h', undefined, '1h', 'max')
  .then(function (points) {
    points.forEach(function (p) { console.log(p.timestamp, Number(p.value)); });
  });

Data Tables ​

FunctionResolves toRoles
scadaink.db.tables()Array of local Data Table names. Remote tables are left out, because pages can't query them.any signed-in user
scadaink.db.query(table, { limit, offset, search, sortBy, sortDir })Array of rowsany signed-in user
scadaink.db.insert(table, row)The inserted rowadmin, editor
scadaink.db.update(table, rowId, changes)The updated rowadmin, editor
scadaink.db.remove(table, rowId) / scadaink.db.delete(table, rowId)The API responseadmin, editor
  • table is the Data Table's display name. Unknown: Table not found.
  • limit defaults to 100 and is capped at 1000. sortDir is 'asc' or 'desc' (default 'desc'); sortBy must be a column, id, created_at or updated_at — otherwise rows come newest first. search matches any column containing the text.
  • There is no where filter here (unlike the server-side db.query); use search, or a named query through scadaink.api.post.

Files ​

scadaink.fileUrl('images/logo.png') returns /api/files/serve/images/logo.png — the address of a file uploaded in the File Manager, with folder names as path segments (each segment URL-encoded). Use it for <img> sources, CSS backgrounds set from JavaScript, or fetch. The path is case-sensitive and must match the folder and file names exactly. File URLs don't need a login, so they also work on public pages; in HTML you can write the address directly: <img src="/api/files/serve/images/logo.png">. See File Manager.

scadaink.pageFile('logo.png') returns /api/page-files/serve/<id>/logo.png — the address of a file uploaded in this page's Page files panel, by its name, for the page the code is running on. It works on private pages for a signed-in viewer, and on public pages for anyone (see Custom Pages).

javascript
document.getElementById('logo').src = scadaink.pageFile('logo.png');

Users (admin viewers only) ​

For any other role the server answers 403 and the Promise rejects.

FunctionDoes
scadaink.users.list()Resolves to the user list
scadaink.users.add({ email, password, firstName, lastName, role })Creates a user
scadaink.users.update(id, changes)Edits a user
scadaink.users.setPassword(id, newPassword)Sets a password
scadaink.users.disable(id) / enable(id)Blocks / allows sign-in
scadaink.users.remove(id)Deletes the user

Any other endpoint ​

scadaink.api.get(path), .post(path, body), .put(path, body) and .del(path) call /api + path with the viewer's token, and resolve to the whole JSON response ({ success, data, … }). They reject when the HTTP status isn't OK or success is false.

javascript
scadaink.api.post('/named-queries/downtime_by_reason/execute', { params: { line: 2, start: '2026-09-14T06:00:00Z' } })
  .then(function (res) { renderRows(res.data.rows); });

Login helpers (for a custom sign-in page) ​

FunctionDoes
scadaink.auth.login(email, password)Resolves to { user, tokens, passwordChangeRecommended }; rejects with the server's message (e.g. invalid credentials)
scadaink.auth.applySession(session, redirectTo?)Stores the session in this browser so the Scada.Ink app is signed in, then goes to redirectTo
scadaink.auth.logout(redirectTo?)Clears the stored session and goes to redirectTo (default /login)
scadaink.auth.currentUser()The stored user object, or null
javascript
async function signIn() {
  var email = document.getElementById('email').value;
  var password = document.getElementById('password').value;
  try {
    var session = await scadaink.auth.login(email, password);
    scadaink.auth.applySession(session, '/');
  } catch (err) {
    document.getElementById('error').textContent = err.message;
  }
}
document.getElementById('signin').addEventListener('click', signIn);

Note: Inside the app or at /view/page/<id> the page runs in a sandboxed frame that isn't allowed to navigate the whole window, so the redirect loads the app inside the frame. For a sign-in screen, mark the page Public and open its raw address /api/custom-pages/<id>/render directly (bookmark it or set it as the kiosk start page) — there the redirect replaces the whole window. Use a button with a click handler rather than a <form> submit.

See also ​

Scada.Ink Help Center