Flow API and MCP
Flow API
Every saved screen flow is also a REST endpoint. Because a flow declares a typed In / Out pair and
a single execute method, a multi-screen conversation collapses into one stateless JSON call: the
caller never sees screens, AID keys or a session. This is how a converted program is consumed by other
systems, as opposed to the Player, which is for a human clicking through screens.
All routes are relative to the backend's /api base:
| Method and path | What it does |
|---|---|
POST /applications/{app}/operations/{op}/flows/{ClassName}/execute | Run the flow end to end and return its output |
GET /applications/{app}/operations/{op}/flows | List the flows saved for that operation |
GET / PUT /applications/{app}/operations/{op}/flows/{ClassName} | Read or save the flow script; a save is lint- and compile-checked and rejected on errors |
GET /openapi.yaml | OpenAPI 3.0.3 document describing every flow and RPC program |
Calling a flow
The request wraps the flow's inputs in an input object, keyed by the field names of the flow's In
class. Nested fields use dotted paths, such as address.line1.
curl -X POST \
http://localhost:8080/api/applications/demo/operations/login/flows/DemoFlow/execute \
-H 'Content-Type: application/json' \
-d '{ "input": { "userId": "USER001" } }'
The response carries the flow's Out fields under output; meta holds execution detail such as the
steps that ran and any screen mismatch, and error is set instead of output when the flow fails.
{
"output": { "errorMessage": "" },
"meta": { "steps": [ ... ] },
"error": null
}
Flow inputs and outputs are currently exchanged as strings, so every field in the generated schema is
type: string. Numeric and date fields are still returned faithfully; convert on the caller side where
it matters.
API Docs
Click API Docs in the sidebar for a browsable, try-it-out Swagger UI view of the same document
inside the console. Flows are grouped by application and operation, and RPC invoke paths appear
alongside them. Every Try it out call is sent as the signed-in user, so it works in the SaaS console
and the OpenLegacy Platform, where the API requires sign-in. Swagger UI ships inside the console, so no
internet access is needed.
The document is generated on demand and cached. It refreshes automatically when you save or delete a flow, change an operation, or regenerate code.
The backend also serves a bare Swagger page at its own /swagger. It loads Swagger UI from a public CDN
and sends no credentials, so it only works on a backend without sign-in and with internet access; use
API Docs everywhere else.
MCP
OL Next exposes a Model Context Protocol endpoint so an external agent such as Cursor, Claude Desktop or Claude Code can drive the conversion loop (create an application, import a catalog example, an AtomIQ operation or your own source, and generate), then play screens, record and generate flows, invoke RPC programs and bind live data, without opening the console. MCP covers that loop; it is not a full REST substitute.
The endpoint is POST /api/mcp (streamable HTTP, service identity ol-next). On connect the server sends
short instructions (the application, operation and program/screen/flow model, the typical journeys and
the main rules), which your client passes to the agent.
Authentication
| Deployment | How the client authenticates |
|---|---|
| On-premises, standalone | No token for loopback callers. If a key is configured in Global Settings → MCP access, every caller sends it as x-api-key |
| SaaS / Platform | A Bearer Hub JWT, the same token as other /api/* calls. Add the server by its MCP URL, the API host followed by /next/api/mcp, not the console address. Clients discover OAuth at /next/.well-known/oauth-authorization-server on the API host, complete the Hub login, then attach the token automatically |
A Hub account API key from Global Settings → API access, sent as x-api-key, also works and needs no
interactive login.
Tools
Six tools cover the conversion loop, driving screens and saved flows. Every tool addresses applications and operations by name; application and operation names are slugs: lowercase letters, digits and hyphens, starting with a letter.
| Tool | What it does |
|---|---|
catalog | Discover what OL Next holds: applications with their operations (kind, whether generated, saved flows). applicationName narrows to one application and adds its data source. show: "examples" lists the built-in samples, "generate-options" the conversion options, and "api" the callable REST endpoints (flows and RPC programs, with method, path, body key and input/output fields) plus the OpenAPI path. show: "datasources" lists the host connections, never credentials; with a CoreIQ connection it lists that endpoint's schemas, and with schema too its tables (at most 200, filtered by match). show: "atomiq" lists the connected AtomIQ's workspaces, and with workspace that workspace's operations and whether each is importable |
operation | Set up and convert in one call. Creates the application and operation if missing; source takes an example, your files, or an atomiq workspace operation or .atq export, and replaces the operation's sources (16 MB per call). generate: true or an options object converts them; for your own RPC program pass generate: {kind: "rpc"} on the first convert. delete: "operation" or "application" removes it permanently. dataSource binds the application to live data: {connection, schema} for SQL, {connection, files: {CICSFILE: "DATASET"}} for VSAM, or "sample"; a bind tests itself and, for CoreIQ, reports the writes the host allows. Programs then read and write the live host. Connections are created in the console |
files | list paths and sizes, read one file or a line range, write one source file. kind: source (default) or generated (read-only) |
program | Without programName: the operation's RPC programs with their commarea schemas. With it: invoke that program with request and return its response and log. action: "map" renames fields, sets direction or hides them, the same as the console's schema editor |
screen | Open a session on entryProgram, then fill fields and press aidKey (default ENTER); each returns the session id, the screen and whether it is done. close: true ends it. A field the map marks secret, such as a password, comes back with secret: true and no value. Close every session you open |
flow | record-start / record-stop on a session (save: true stores the flow in the same call), save, list, read, lint, run, delete. While recording, screen's paramFields become the flow's inputs and captureFields its outputs. generate lets AI draft a flow from the operation's screens and saves it when it lints clean, never replacing an existing flow; modify changes a saved flow from an instruction. Both can take a few minutes |
Typical journeys
// Convert cousr01 and make a look-up-user API
catalog { show: "examples" }
operation { applicationName: "demo", operationName: "cousr01", source: { example: "cousr01" }, generate: true }
screen { applicationName: "demo", operationName: "cousr01", entryProgram: "COUSR01D" }
flow { action: "record-start", sessionId }
screen { sessionId, fields: { USERID: "ADMIN01" }, paramFields: ["USERID"], captureFields: ["FNAME"] }
flow { action: "record-stop", sessionId, flowName: "LookupUserFlow", save: true }
flow { action: "run", applicationName: "demo", operationName: "cousr01", flowName: "LookupUserFlow", input: { USERID: "ADMIN01" } }
// or let AI draft the flow instead of recording it
flow { action: "generate", applicationName: "demo", operationName: "cousr01" }
catalog { show: "api", applicationName: "demo" }
// Pull an operation from AtomIQ and convert it
catalog { show: "atomiq" }
catalog { show: "atomiq", workspace: "CardDemo" }
operation { applicationName: "demo", operationName: "cotrn02c",
source: { atomiq: { workspace: "CardDemo", operation: "COTRN02C" } }, generate: true }
// Use live host data
catalog { show: "datasources" }
catalog { show: "datasources", connection: "CARDDEMO-DB2", schema: "CARDDEMO", match: "CUST" }
operation { applicationName: "carddemo", dataSource: { connection: "CARDDEMO-VSAM", files: { CUSTDAT: "<dataset name>" } } }
A failed program, screen step or flow comes back as a tool error whose text says what to call next. A screen session lives on the server instance that opened it and does not survive a redeploy; open it again afterwards.
Connecting a client
On-premises standalone, on the machine running the backend, no key is needed:
{ "mcpServers": { "ol-next": { "url": "http://localhost:8080/api/mcp" } } }
From anywhere else, generate a key in Global Settings → MCP access (it is shown once) and send it:
{ "mcpServers": { "ol-next": {
"url": "https://your-console.example.com/api/mcp",
"headers": { "x-api-key": "olmcp_..." } } } }
Until a key exists, /api/mcp answers local callers only; once one exists, every caller must send it.
For SaaS, use the API host followed by /next/api/mcp. The client follows OAuth discovery, opens the Hub
sign-in in a browser, then calls the endpoint with the Hub Bearer token:
{ "mcpServers": { "ol-next": { "url": "https://<your-api-host>/next/api/mcp" } } }
Claude Code looks for the OAuth metadata only at the API host's root, where it is not served, and fails
with Dynamic Client Registration rejected (HTTP 404). Point it at the metadata explicitly:
claude mcp add-json -s user ol-next '{
"type": "http",
"url": "https://<your-api-host>/next/api/mcp",
"oauth": {
"authServerMetadataUrl": "https://<your-api-host>/next/.well-known/oauth-authorization-server"
}
}'
Then run /mcp, choose ol-next and Authenticate. If the server was added before without that
setting, remove it first (claude mcp remove ol-next -s user).
Consume a converted program over REST, OpenAPI and API Docs, or drive the conversion loop from an AI agent over MCP.