Web Services
Web Services let you expose custom HTTP endpoints backed by a script you write in the admin interface — for webhooks, third-party integrations, and bespoke APIs that don't fit the standard REST surface. Each service is served under /web_service/<name>.
How a request is routed
The path after the service name is handed to your script as a sub-path:
/web_service/myapi/devices/42
name = "myapi"
path = "/devices/42"
segments = ["devices", "42"]
Your service is matched by name and HTTP method, so one name can have separate handlers for GET, POST, etc.
Writing a service
A service is a script that defines a handleRequest function. The script runs in a sandboxed context with a curated set of globals:
| Global | What it is |
|---|---|
gem | The GemServer instance — the full server API (gem.command, gem.getAttribute, gem.zones, …). |
fetch | Outbound HTTP, for calling external services. |
store | A per-service in-memory object that persists across requests (resets on reload). Use it for caches, tokens, counters. |
console, Buffer, URL, URLSearchParams, atob/btoa | Standard utilities. |
JSON, Math, Date, timers, and core built-ins | Standard JS. |
// A minimal web service script
handleRequest = async (req, res) => {
if (req.segments[0] === 'devices') {
const id = Number(req.segments[1]);
const device = gem.devices[id];
return res.json({ id, online: device?.online ?? null });
}
res.status(404).json({ error: 'not found' });
};
Authentication
Each service sets an auth type:
- none — open endpoint.
- basic — HTTP Basic auth against the service's configured username/password.
Manage services, scripts, and auth from the admin interface.
Open Web Services
:::note gem.* is a frozen surface
Like the macro-script surface, gem.* inside a web service is an API contract — methods are added, never repurposed. Build against the documented helpers and prefer them over raw database access. See Macro Scripts.
:::