Welcome to issue #04 of NowLink — a build-in-public newsletter of creating a local MCP server that connects Claude Desktop directly to ServiceNow.
In our last issue, we walked through how NowLink scales safely up to bulk updates by introducing a two-turn confirmation engine and session tokens.
This week, we try to step outside of basic data tables altogether and interface directly with platform orchestration.
Triggering an Automation⚡
The goal was clear: teach Claude to trigger Flow Designer automations on demand. But as with every iteration of this project, the platform put up a fight.
We wanted Claude to look at an active ServiceNow instance, identify published automation routines, pass them specialized input parameters, execute them, and monitor the results. Straightforward, decoupled enterprise automation. Except, out of the box, there is absolutely no REST endpoint designed to do it. So we built one.
That’s what we cover in this launch edition and more:
The Platform Wall: The confusing reality behind ServiceNow's "Flow API" documentation and why your standard REST tools will hit a dead end.
Building the Bridge: How we initialized the
nowlink.devprofile to automatically deploy our custom API gateway.The Automation Trio: Demystifying the operational boundaries between Flows, Subflows, and Actions from an external application's perspective.
Testing the Architecture: A simple subflow test as a proof of concept
The Weekly Gotcha: When each automation speaks a different language - and what we plan to do about it
The Platform Wall 🧱
If you look through the official ServiceNow developer documentation, you will find extensive references to a "Flow API". Community threads talk at length about starting flows programmatically. Naturally, we pointed our HTTP client at the logical endpoint:
GET /api/now/flow_api
→ 400 Bad Request: "Requested URI does not represent any resource"Here is the underlying issue: the "Flow API" in the documentation is sn_fd.FlowAPI—an isolated, server-side JavaScript object. It exists purely within ServiceNow's internal execution context. It is not an exposed REST namespace that you can hit via Python or curl.
The only way to get a native, REST-triggerable Flow Designer endpoint without writing server-side code is the Flow Designer REST Trigger. The catch? That feature requires an Integration Hub Enterprise subscription. It is an expensive add-on completely unavailable on a standard, out-of-the-box PDI.
So the dilemma stood: No direct REST endpoint, and no automated trigger engine without upgrading licenses.
Building the Bridge 🌉
To bypass the missing out-of-the-box API namespaces, NowLink bypasses manual configuration altogether by building its own infrastructure directly onto the platform. It uses our integration client to automatically deploy a custom Scripted REST API (sys_ws_definition).
Because a Scripted REST API executes native server-side JavaScript, it easily taps into the internal sn_fd.FlowAPI class namespaces. However, to allow our automation client to log in and write these new endpoint structures onto the instance, the dedicated integration profile requires specialized system authority.
To achieve this, we explicitly added the web_service_admin role to our dedicated integration user account, nowlink.dev

Adding new role to the integration user
Note: While web_service_admin is ideal for clean, automated endpoint deployment on a local PDI, enterprise production environments would obviously require tighter ACL scoping.
With this role active, running the initialization setup sequence via our local terminal deploys our custom routing layer across three distinct, endpoint operations:

Building the bridge
The bridge installs three endpoints — subflow, flow, and action — covering all three programmatically triggerable types in Flow Designer. All use the same FlowAPI pattern under the hood, just .subflow(), .flow(), or .action().

NowLink Flow Bridge resources
The resource itself is a simple script which triggers using FlowAPI.
(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {
var body = request.body.data;
var subflowName = body.subflow_name;
var inputs = body.inputs || {};
if (!subflowName) {
response.setStatus(400);
response.setBody({ error: 'subflow_name is required' });
return;
}
try {
var runner = sn_fd.FlowAPI.getRunner()
.subflow(subflowName)
.inBackground()
.withInputs(inputs)
.run();
var executionId = null;
if (runner && typeof runner.getExecutionId === 'function') {
executionId = runner.getExecutionId();
} else if (runner && typeof runner.getContextId === 'function') {
executionId = runner.getContextId();
}
response.setStatus(200);
response.setBody({
status: 'triggered',
subflow_name: subflowName,
execution_id: executionId,
});
} catch (e) {
response.setStatus(500);
response.setBody({ error: e.message || String(e) });
}
})(request, response);Flow vs Subflow vs Action — The Automation Trio 3⃣
To build a reliable integration path, we have to unpack the structural distinctions between ServiceNow’s three primary workflow components. They are not interchangeable, and they behave entirely differently when called from an external AI client:
Flows: Heavily bound to a specific platform trigger condition (such as an Incident table insert or a calendar schedule). They expect the platform's internal transactional engine to run them—you do not pass arbitrary outside variables directly to a Flow on a whim.
Subflows: Completely unbound from native trigger events. They are defined strictly by their input parameters and returned outputs. They function exactly like an independent, reusable software function, making them the ideal target for outside orchestration clients.
Actions: The most granular, atomic operations within Flow Designer (e.g., sending an email, patching a specific record field, or executing a PowerShell script). While typically bundled sequentially inside a larger workflow canvas, independent Actions can actually be pulled out and fired standalone by our scripting engine.

Flow Designer Engine
The three MCP tools⚒
In NowLink v0.4 we focus on subflows only (but we have prepared the resources for flows and actions as well). For subflows we implemented three new MCP tools:
list_subflows - queries
sys_hub_flowfor all active, published subflows on the instance. Returns name, description, andtrigger_name— the value to pass directly totrigger_subflow. Ordered by most recently updated, so your subflowstrigger_subflow - calls the bridge with a
scope.internal_nameand an inputs dict. Returns an execution ID immediately — subflows run in the background. The internal name is the one Flow Designer uses, not the display label.get_flow_status - queries
sys_flow_contextby execution ID. Returns stateComplete,Running,Error), fault description if it failed, and output variables if it completed.
Similar tools will be created for flows and actions in NowLink v0.5.
Testing the Architecture 🧪
To put this new orchestration architecture to the test, we built a simple diagnostic validation script inside Flow Designer called the NowLink Test Subflow. This routine declares an explicit input string parameter, logs it directly to the system log view, and passes back a confirmation object.

Simple test subflow
When this configuration is loaded, NowLink exposes a set of clean MCP tools allowing Claude Desktop to parse, invoke, and track these tasks end-to-end.

Triggering the subflow in Claude
Let’s confirm that it was actually executed.
asking Claude

Claude confirmation
looking into ServiceNow logs

ServiceNow log
Well, it worked! 🤘
The Weekly Gotcha 🪵
After proving the bridge worked, I wanted to test it against a real subflow — something more meaningful than our test subflow that just logs a message.
global.send_email looked promising. Clear name, obvious purpose. So I triggered it with what I assumed were the right inputs:
{
"subflow_name": "global.send_email",
"inputs": {
"to": "[email protected]",
"subject": "NowLink test",
"body": "Hello from NowLink v0.4"
}
}NowLink returned 200. Execution ID came back. Looked like a success.
{
"status": "triggered",
"subflow_name": "global.send_email",
"execution_id": "0c23030edbd9c750233758a87ef5c806"
}Then I checked the execution status.
state: Error
error_message: Email validation failed: Email has no recipients.The subflow ran. It didn't error on our inputs — it simply ignored them entirely. Our field names (to, subject, body) weren't what the subflow expected. Whatever the correct variable names are, we didn't pass them. The subflow saw an empty recipient list and failed trying to send to nobody.
No warning at trigger time. No "unknown input ignored." Just a 200 and an execution ID — and a silent failure on the other side.
This is the honest limitation of v0.4. NowLink can trigger any subflow. It cannot tell you what inputs that subflow actually expects. Get the variable names wrong and the subflow fires anyway — with empty values where your data should have been.
The fix is input variable discovery — querying the subflow definition before triggering to find the declared input names, types, and which ones are mandatory. That's v0.5.
For now: before triggering any subflow, open it in Flow Designer and check the Inputs tab. The variable names there are exactly what you pass. Not the display labels — the internal names.
What's Next: Smart Flows (v0.5) 🚀
Next time we will talk about:
input variable discovery
validation before trigger
full
trigger_flowandtrigger_actiontools exposed properly
What subflow would you want Claude to trigger first? Reply to this email and let me know—I read every response!
Until next week, happy coding.
— Tomáš Dolejšek, creator of NowLink

