Artificial Intelligence

Limited access

This feature is only available to root and super_admin_domain profiles. Ask your administrator for proper user role profiling.

What OpenGate AI does

OpenGate can train a machine-learning model on the data it already collects, package the trained model as an inference service and wire that service into the rules engine โ€” so that every new reading is scored the moment it arrives, and an anomaly becomes an alarm without anyone writing a line of Python.

You do not bring a model. You pick a training plan from the platform catalogue โ€” a ready-made recipe for a concrete problem, such as anomalous data sessions on a mobile APN or defective parts in an image โ€” point it at your data, and OpenGate takes it from there: it exports the data, runs the training, versions the result, builds an inference container and, if you ask it to, creates the rule that calls it.

flowchart LR
    D["Your data<br>time series or files"]:::ext --> T["<b>Trainer</b><br>runs a training plan"]
    T --> M["Model version"]
    M --> I["<b>Inferencer</b><br>deployed inference service"]
    R["<b>Rule</b><br>on new readings"] --> I
    I --> R
    R --> A["Datastreams<br>and alarms"]:::ext
    classDef ext fill:#e9edfa,stroke:#486ac9,color:#101010

Four things carry the whole feature:

Concept What it is Where it is documented
Training plan A recipe for one kind of problem: the algorithm, the data it needs, the configuration it accepts and the container image that runs it. Curated by the platform. Training plans
Trainer Your instruction to run a training plan on your data โ€” once, or on a retraining schedule. A trainer produces model versions. Trainers
Inferencer A trained model deployed as an HTTPS service inside the platform, with the versions it can run and the rules that call it. Inferencers
Rule The ordinary OpenGate rule that sends each new reading to the inferencer and turns the answer into datastreams and alarms. Created for you by the training plan. Rules

Two platform services do the plumbing and are worth knowing about because they are reachable on their own: the scheduler, which runs anything on a cron expression or an interval โ€” REST requests, container images, pipelines of both โ€” and is what actually launches a training; and the file connector, which gives each organization a file space where training files and images live.

Two ways to use it

From the web console. The Artificial Intelligence section of the OpenGate console walks you through choosing a plan, pointing it at a time series or an uploaded file, naming the model and deciding whether it retrains. When the training finishes, the same screen shows the inferencer, its versions and their metrics, and lets you switch versions on and off. See The web console.

From the REST API. Everything the console does is a call to one of five services under /ai, /scheduler and /fileConnector on the platform host. The pages of this section document each of them, with its OpenAPI specification at the bottom. Read How it works first: it follows one training from the POST that schedules it to the alarm that a rule raises, and names every moving part along the way.

The plans available today

Plan Detects Learns from
RADIUS session anomalies โ€” Isolation Forest Data sessions of SIM subscriptions on one APN whose traffic profile is unusual The RADIUS session records of that APN
RADIUS session anomalies โ€” Autoencoder The same problem, with a neural network that scores how badly a session can be reconstructed The RADIUS session records of that APN
Image anomaly detection Defective items in photographs, with a heat map of where the defect is Two folders of images: correct and incorrect

Every plan on the platform follows the same contract, so what you learn about scheduling, versions and rules for one applies to all of them. Building a new one is the platform team’s job; Building training plans explains what that involves.

Trainings and inferencers consume platform resources

A training is a container job that runs until it finishes or hits its timeout, and an active inferencer is a service that stays up and is called on every new reading its rule matches. Both are billed as platform usage. An inferencer can be left configured but inactive, and a trainer can be created without a retraining schedule; both are deliberate choices you make when you create them.

Index

Subsections of Artificial Intelligence

How it works

The whole loop in one picture

Everything in this section is a step of the loop below. The names in bold are the API resources you will meet on the following pages.

sequenceDiagram
    participant U as You
    participant TA as Trainers API
    participant SC as Scheduler
    participant J as Training job
    participant IA as Inferencers API
    participant RU as Rules engine

    U->>TA: POST trainer (plan, data source, schedule)
    TA->>SC: schedule image execution or pipeline
    SC-->>J: at the scheduled time: export data, run the plan's image
    J->>J: train, evaluate, register the model version
    J->>J: build the inference image and push it
    J->>IA: create inferencer, or add the new version to it
    J->>RU: create the rule (first time only)
    J-->>SC: callback: OK
    U->>IA: activate a version
    IA->>RU: deploy the service, activate the rule
    RU->>IA: on each matching reading: POST prediction request
    IA-->>RU: prediction
    RU->>RU: collect datastreams, open or close alarms

1. You create a trainer

A trainer is a request to run a training plan on a data source, optionally on a schedule (POST /ai/organization/{organizationId}/trainer, see Trainers). You give it:

  • the plan to run, by its identifier from the catalogue;
  • the model name โ€” lowercase letters, digits and hyphens. It becomes the name of the model, of the inferencer, of the rule and of the scheduler entry, so choose it with care: it cannot be changed later and only one trainer per model name can exist in an organization;
  • the plan’s configuration โ€” the fields the plan declares, such as the APN to learn;
  • a data source โ€” a file in your organization’s file space, or a time series to export;
  • a schedule, if the model has to be retrained periodically; and
  • an execution timeout for the training job.

The Trainers API checks the plan exists, stores a small Kubernetes secret with your API key and organization so the job can call the platform back on your behalf, and hands the work to the scheduler.

2. The scheduler runs it

The scheduler is a general-purpose service: it runs REST requests, container images and pipelines of both on a cron expression or an interval, and keeps a history of every execution. A trainer becomes one of two things there, both named after the model:

Data source What is scheduled Steps
A file (source.path) An image execution Run the plan’s container with dataSourcePath=/data/<your path>
A time series (source.timeserie) A pipeline 1. POST the time series Parquet export, writing <model>-<plan>-<timeseries>.parquet into your file space
2. Run the plan’s container with dataSourcePath pointing at that file

Inside the job your organization’s file space is mounted at /data, which is why every path the plan sees starts there. The job also receives the plan’s configuration as environment variables, the platform-wide AI settings from a shared secret, and a callbackUri it must call when it finishes. The training job is a Kubernetes Job with no retries: it either completes, fails, or is killed when the timeout expires.

3. The job trains, versions and publishes

Every plan image runs the same three commands, provided by the training template framework:

  1. Generate the run profile โ€” experiment <organization>-<model>, registered model <organization>-<model>-<algorithm>, data location from dataSourcePath.
  2. Run the recipe โ€” ingest, split, transform, train, evaluate, register. Each run is tracked in the platform’s MLflow, which is where model versions come from: the first successful training registers version 1, a retraining registers version 2, and so on. If the data does not meet the plan’s minimum, the run fails here and says so.
  3. Publish the inference โ€” download the latest run’s model and metrics, wrap them in the framework’s FastAPI inference server, build a container image named <organization>-<model>:v<version> and push it to the platform registry. Then, through the Inferencers API:
    • if an inferencer named after the model does not exist yet, create it with that image as its only version โ€” and, unless the trainer was created with createRule: false, first create the plan’s rule in default_channel and link it to the inferencer;
    • if it already exists, add the image as a new version.

Whatever happens, the job reports back to the scheduler with OK, ERROR or โ€” if it was killed by the timeout โ€” TIMEOUT, and a description. That report is what you see as the execution’s history.

4. You activate a version

A freshly created inferencer has one version and nothing deployed. Activation is your call (PUT .../inferencer/{inferencerId}/activation?image=<version>&active=true), because it is the moment the platform starts spending resources on your behalf:

  • the Inferencers API deploys the image as a service reachable inside the platform at https://<organization>-<model>:8443/api/predict โ€” lowercase, underscores turned into hyphens;
  • it waits for the container to be running;
  • it sets the linked rules to active: true.

You can activate a specific version, or latest: then every new version a retraining produces replaces the running one automatically, and the rule keeps calling the same address. Deactivating (active=false) undeploys the service and deactivates the rules; the versions stay, ready to be activated again.

5. The rule scores every reading

The rule a plan creates is an ordinary ADVANCED rule: it triggers on the datastream the plan cares about, builds the request the model expects, calls the inferencer with http.client, and translates the answer into your data model โ€” typically a boolean datastream saying whether the reading was anomalous, a score, an explanation, and an alarm that opens when the entity turns anomalous and closes when it returns to normal. The rule is created inactive and is switched on and off together with the inferencer, so a deactivated model never leaves a rule calling a service that is not there.

The rule is yours: you can read it, tune its thresholds or change what it collects in the rules editor like any other. Deleting the inferencer deletes its rules too, unless another inferencer still uses them.

Naming, in one table

Once you know the organization name and the model name, you can predict every other name the feature creates:

Thing Name Example for organization acme, model radius-anomalies
Scheduler entry (image execution or pipeline) <model> radius-anomalies
Exported time series file <model>-<plan id>-<time series id>.parquet radius-anomalies-โ€ฆ-โ€ฆ.parquet
MLflow experiment <organization>-<model> acme-radius-anomalies
Registered model <organization>-<model>-<algorithm> acme-radius-anomalies-isolation-forest
Inference image <organization>-<model>:v<version> acme-radius-anomalies:v3
Inferencer <model> radius-anomalies
Deployed service <organization>-<model> (lowercase, _ โ†’ -) acme-radius-anomalies
Inferencer endpoint https://<service>:8443/api/predict https://acme-radius-anomalies:8443/api/predict
Rule <model>, in default_channel radius-anomalies

Retraining

A trainer with a schedule.expression runs every time the expression fires, and each run adds a new version to the same inferencer. The scheduler understands standard five-field cron and the extended form with a leading seconds field and a trailing year field; the web console writes seven-field expressions such as 0 0 0 15 */3 ? * โ€” midnight on the 15th, every third month. If the expression pins a single instant (every field numeric, year included), the trainer is a one-off and the API reports hasRetraining: false.

A trainer created without a schedule runs once, about a minute after it is created.

An inferencer keeps a bounded number of versions (five by default). When a new one arrives over the limit, the oldest inactive version is dropped; the active one is never removed by a retraining.

Callbacks and history

Because trainings take minutes to hours, nothing in this loop blocks. The scheduler records every execution in its history (GET /scheduler/organization/{organizationId}/history), with a state โ€” IN_PROGRESS until the callback arrives, FINISHED when it does, FINISHED OUT OF TIME if it arrived after the wait expired โ€” and one entry per step with its result and description. For a time-series trainer that is two steps: the export and the training. The web console’s See history action is a view of exactly this.

What you need

  • A user with the root or super_admin_domain profile: the five AI services accept no other.
  • Authentication is the usual X-ApiKey header, or Authorization: Bearer <JWT>.
  • The AI services are exposed on the same host as the rest of the OpenGate API, under the prefixes /ai (training plans, trainers, inferencers), /scheduler and /fileConnector.

Training plans

Limited access

This feature is only available to root and super_admin_domain profiles. Ask your administrator for proper user role profiling.

What a training plan is

A training plan is a packaged answer to one question โ€” is this RADIUS session unusual for its APN?, is this part defective? โ€” built and validated by the platform team and published in a catalogue. It bundles:

  • the algorithm and the whole training recipe: how the data is cleaned, split, transformed, trained and evaluated;
  • the container image that runs that recipe;
  • the data it needs: which source types it accepts (a file, a time series, or either) and, for time series, the columns it expects to find;
  • the configuration you must supply when you create a trainer โ€” an APN, for example;
  • the minimum amount of data below which a training is refused rather than producing a meaningless model;
  • and, implicitly, the inference contract of the model it produces and the rule it creates to call it.

You do not modify a plan. You create a trainer that runs it, and the plan’s own pages below tell you what to feed it and what comes out.

Listing the catalogue

curl --request GET \
     --header "X-ApiKey: <your-api-key>" \
     https://api.opengate.es/ai/trainingPlans

The catalogue is platform-wide, not per organization, and read-only through the API. Each entry:

{
  "identifier": "6f1c2a4e-โ€ฆ",
  "name": "RADIUS anomalies per APN (Isolation Forest)",
  "description": "Detects anomalous data sessions of the subscriptions of one APN",
  "modelType": "anomaly",
  "modelFormat": "isolation-forest",
  "source": ["file", "timeserie"],
  "configFields": ["apn"],
  "columnData": [
    { "name": "APN", "type": "STRING", "description": "Access point name of the session" },
    { "name": "sbytes", "type": "LONG", "description": "Bytes sent by the device" }
  ],
  "minDataToTrain": { "value": 15000, "unit": "ITEMS" },
  "image": { "name": "trainingplan-if-radius-anomalies-per-apn", "tag": "1.0.0" }
}
Field Meaning How you use it
identifier The plan’s id imageExecution.trainingPlan.identifier when creating a trainer
name, description What the plan does, for people The console shows them in the plan picker
modelType The family of problem: anomaly, classificationโ€ฆ Groups plans in the console
modelFormat The algorithm: isolation-forest, autoencoder, pytorchโ€ฆ Informative; it also names the registered model
source The data source types the plan accepts: file, timeserie or both Decides whether source.path or source.timeserie is allowed in the trainer
configFields The configuration keys the plan needs Every one of them must appear in imageExecution.configuration
columnData The columns the plan expects in its input For a time-series source, map each of them to a column of your time series
minDataToTrain The minimum amount of data: a number of ITEMS (rows, images per class) or of DAYS Below it, the training fails with an explicit message instead of producing a bad model
image The container image and tag the scheduler runs Informative

The plans available

  • RADIUS session anomalies โ€” Isolation Forest

    Learns what a normal data session looks like on one APN and flags the sessions that do not fit: the data it needs, how it trains, the prediction request and answer, and the rule and alarm it creates.

  • RADIUS session anomalies โ€” Autoencoder

    The same RADIUS-per-APN problem solved with a neural network that learns to reconstruct normal sessions and flags the ones it reconstructs badly: data, training, prediction contract and the rule it creates.

  • Image anomaly detection

    Tells defective items from correct ones in photographs and draws a heat map of the defect: the folders of images it trains on, the two models it combines, the prediction request over the organization's file space, and the rule and alarm it creates.

The three plans share the same lifecycle, described in How it works: what differs between them is the data they take, the request their inferencer answers, and the rule they create.

RADIUS ยท Isolation Forest RADIUS ยท Autoencoder Image anomaly detection
Source types file, time series file, time series file (a folder)
Input RADIUS session records of one APN RADIUS session records of one APN Photos in correct/ and incorrect/ folders
Configuration apn apn โ€”
Inference request sbytes, dbytes, spkts, dpkts, dur sbytes, dbytes, spkts, dpkts, dur image_route, generate_heat_map
Inference answer prediction, anomaly_score, explanation prediction, anomaly_score, explanation predictions, anomaly_score, heatmap_path
Rule triggers on GPRS presence turning STOP GPRS presence turning STOP A new imagePathToCheck value
Alarm deviceWithAnomaly deviceWithAnomaly imageWithAnomaly

API specification

Subsections of Training plans

RADIUS session anomalies โ€” Isolation Forest

What it detects

Every data session a SIM subscription opens through a mobile operator is recorded by RADIUS accounting: how long it lasted, how many bytes and packets went each way. On a given APN those sessions have a shape โ€” an IoT fleet sending small periodic uploads looks nothing like a fleet streaming video. This plan learns that shape and scores each new session against it, so that a SIM that suddenly uploads gigabytes, or holds a session open for days, stands out.

The algorithm is an Isolation Forest: an ensemble of random trees where points that are easy to isolate โ€” few splits away from everything else โ€” are anomalies. It needs no labelled examples of bad sessions, only enough normal traffic to learn from.

Data it needs

The plan reads RADIUS session records and keeps the sessions of one APN, given as the apn configuration field. Each record needs these columns; with a time-series source you map them in the trainer, with a file source they must be present under these names in a Parquet file:

Column Type Meaning
APN string Access point name of the session
SessionState string State of the session record
IP_GGSN, IP_Device string Gateway and device IP addresses
sbytes, dbytes integer Bytes sent and received by the device
spkts, dpkts integer Packets sent and received
dur integer Duration of the session

Records with missing values are dropped, and so are TERMINATED records that carry no traffic counters at all. The plan refuses to train when the APN has fewer sessions than the catalogue’s minDataToTrain (15 000 when the plan does not say otherwise): the execution fails with Not enough data for APN rather than register a model nobody should trust.

How it trains

From the five counters the plan derives eight more features โ€” totals, ratios between directions, rates per second of duration โ€” and scales them robustly to the [0, 1] range. The data is split 75 / 12.5 / 12.5 into training, validation and test. After fitting, the forest scores the training set and the plan sets the decision threshold at the 95th percentile of those scores: anything scoring above it at inference time is an anomaly. Two numbers are recorded with the model version and shown as its metrics:

Metric Meaning
calculated_threshold The score above which a session is called anomalous
training_max_score The highest score seen in training; inference scores are divided by it so anomaly_score reads as a fraction

The prediction request

The inferencer answers POST /api/predict with the five counters of one session:

{ "sbytes": 18234, "dbytes": 1203991, "spkts": 210, "dpkts": 980, "dur": 3600 }
{
  "prediction": 1,
  "anomaly_score": 1.18,
  "explanation": [
    { "dbytes": 0.071, "received_bytes_rate": 0.044, "total_bytes": 0.031 }
  ]
}
Field Meaning
prediction 1 anomalous, 0 normal
anomaly_score The isolation score divided by the training maximum. Above roughly 1 means more extreme than anything seen in training
explanation Only when prediction is 1: the features that pushed the score up, with their contribution, computed with SHAP on the forest. Empty otherwise

The rule it creates

Unless the trainer says createRule: false, the first successful training creates an ADVANCED rule named after the model in default_channel, inactive until the inferencer is activated. What it does:

  1. Triggers on device.communicationModules[].subscription.mobile.presence.gprs, and acts only when the value is STOP โ€” the session has just closed โ€” and the subscription’s session record belongs to the configured apn.

  2. Sends that session’s sentBytes, receivedBytes, sentPackets, receivedPackets and duration to the inferencer.

  3. Collects three datastreams on the entity, dated at the session’s timestamp:

    Datastream Value
    withAnomaly true or false
    score The anomaly_score
    explanation The explanation as text, when the rule parameter shouldShowAnomalyReason is true
  4. Opens the alarm deviceWithAnomaly (severity URGENT, priority MEDIUM) when the entity becomes anomalous and was not before, carrying the explanation as extra information; closes it when a later session comes back normal.

The rule’s parameters โ€” inferenceServiceURL, apn, shouldShowAnomalyReason โ€” are editable in the rules editor, as is the whole script. The datastreams it collects must exist in the entity’s data model.

Choosing between this plan and the Autoencoder

Both plans take the same data and answer the same request, so a rule written for one works for the other. The Isolation Forest trains in seconds, needs no tuning and explains its decisions feature by feature; it is the one to start with. The Autoencoder is worth a try when the forest flags too much or too little and you have plenty of data: it learns a smoother notion of normal at the price of a longer training.

RADIUS session anomalies โ€” Autoencoder

What it detects

The same thing as the Isolation Forest plan: data sessions of the SIM subscriptions of one APN whose traffic profile does not match the APN’s usual behaviour. What changes is how usual is learned.

An autoencoder is a neural network trained to compress each session into a few numbers and rebuild it from them. It only sees normal traffic while training, so it becomes good at rebuilding normal sessions โ€” and bad at rebuilding anything else. The reconstruction error of a new session is its anomaly score.

Data it needs

Identical to the Isolation Forest plan: RADIUS session records with APN, SessionState, IP_GGSN, IP_Device, sbytes, dbytes, spkts, dpkts and dur, filtered to the configured apn, with the same cleaning and the same minDataToTrain check. A time series that feeds one plan feeds the other unchanged.

How it trains

The same thirteen features and scaling as the forest, then a symmetric network of five hidden layers (60 ยท 30 ยท 25 ยท 30 ยท 60 neurons) trained with the Adam optimiser on mean squared error, with early stopping and L2 regularisation. The decision threshold is again the 95th percentile of the training reconstruction errors, and the same two metrics are recorded:

Metric Meaning
calculated_threshold The reconstruction error above which a session is called anomalous
training_max_score The highest error seen in training, used to normalise anomaly_score

Training a network takes longer than growing a forest. Give the trainer a generous execution timeout.

The prediction request

Same request, same answer as the forest:

{ "sbytes": 18234, "dbytes": 1203991, "spkts": 210, "dpkts": 980, "dur": 3600 }
{
  "prediction": 1,
  "anomaly_score": 1.42,
  "explanation": [
    { "dbytes": 0.213, "received_bytes_rate": 0.187, "total_bytes": 0.171 }
  ]
}

anomaly_score is the reconstruction error divided by the training maximum. explanation, present only for anomalies, lists the features whose individual reconstruction error is in the top quarter โ€” the parts of the session the network could not make sense of.

The rule it creates

The same rule as the Isolation Forest plan: triggered by the GPRS presence turning STOP on a subscription of the configured APN, collecting withAnomaly, score and explanation, and opening and closing the deviceWithAnomaly alarm. Because both plans share the request and the answer, an organization can train both on the same data and compare them side by side, each with its own model name.

Image anomaly detection

What it detects

Given a photograph of an item โ€” a part on a line, a meter, a connector โ€” the model says whether it looks like the correct examples it was trained on or like the incorrect ones, and when it finds a defect it produces a heat map: the same image with the suspicious region painted over, saved next to the original.

Data it needs

This plan takes a file source only: a folder in your organization’s file space with two sub-folders of .jpg, .jpeg or .png images:

<your folder>/
  correct/      photographs of items that are fine
  incorrect/    photographs of items with the defect

Upload the images โ€” a .zip or .tar.gz is extracted on arrival โ€” and give the trainer the folder as source.path. Both classes need at least minDataToTrain images each; the training fails with a message naming the class that falls short. The more varied the correct set, the fewer false alarms.

How it trains

Two models are trained and used together:

  • A ResNet-18 classifier, pre-trained on ImageNet and fine-tuned on your two folders to output the probability that an image is defective. Images are resized to 224 ร— 224 and lightly jittered in brightness and contrast so the model does not learn the lighting of your photo booth.
  • A PaDiM anomaly model on the activations of one of the network’s inner layers, which estimates how far each region of a new image is from the distribution of correct images โ€” this is what the heat map comes from, and it catches defects the classifier has never seen.

The plan’s metric is the classifier’s F1 score on the test split; it is recorded with the version.

The prediction request

The inferencer answers POST /api/predict with the path of an image relative to the organization’s file space โ€” the same space the file connector manages, mounted for the inferencer at /data:

{ "image_route": "/line-3/2026-09-03/part-0412.jpg", "generate_heat_map": true }
{
  "predictions": 1,
  "anomaly_score": 0.87,
  "heatmap_path": "/line-3/2026-09-03/part-0412_heatmap.jpg"
}
Field Meaning
predictions 1 defective, 0 correct
anomaly_score Between 0 and 1. The classifier’s probability when it fires; otherwise PaDiM’s distance mapped onto the same range
heatmap_path When the item is defective and generate_heat_map was not false: the heat map written next to the original as <name>_heatmap.<ext>. null otherwise

An image_route that does not exist returns 422.

The flow is: the classifier decides first; if it sees a defect the answer is its probability and a Grad-CAM heat map of what it looked at. If it sees nothing, PaDiM gets a second look and can still call the item defective when its distance exceeds the plan’s minimum, with its own heat map.

The rule it creates

The rule named after the model, in default_channel, inactive until the inferencer is activated:

  1. Triggers on the datastream imagePathToCheck โ€” collect the path of a new photograph into it, and the rule runs.
  2. Sends that path to the inferencer, with generate_heat_map taken from the rule parameter requestHeatMap.
  3. Collects imageWithAnomaly (true / false) and, when there is one, imageWithHeatMapPath, dated at the photograph’s timestamp.
  4. Opens the alarm imageWithAnomaly (severity URGENT, priority MEDIUM) naming the image when the entity becomes anomalous, and closes it when a later image is correct.

So a camera integration only has to do two things: drop the photograph into the organization’s file space and collect its path into imagePathToCheck. The rest is the loop described in How it works.

Trainers

Limited access

This feature is only available to root and super_admin_domain profiles. Ask your administrator for proper user role profiling.

What a trainer is

A trainer is the instruction run this training plan, on this data, with this configuration, now and maybe again later. Creating one is the only step a person takes to obtain a model: everything after the 201 โ€” the export, the training job, the model version, the inference image, the inferencer and its rule โ€” happens on its own and is described in How it works.

To Call
Schedule a training POST /ai/organization/{organizationId}/trainer
List the organization’s trainers GET /ai/organization/{organizationId}/trainer
Remove a trainer and its schedule DELETE /ai/organization/{organizationId}/trainer/{trainerId}

Creating a trainer

curl --request POST \
     --header "X-ApiKey: <your-api-key>" \
     --header "Content-Type: application/json" \
     --data @trainer.json \
     https://api.opengate.es/ai/organization/acme/trainer

A trainer that learns the sessions of one APN from a time series and retrains every quarter:

{
  "name": "radius-orange-trainer",
  "description": "Anomalous sessions on the corporate APN",
  "imageExecution": {
    "model": { "name": "radius-orange" },
    "trainingPlan": { "identifier": "6f1c2a4e-โ€ฆ" },
    "configuration": { "apn": "corporate.example.apn" },
    "timeout": 1800
  },
  "source": {
    "timeserie": {
      "id": "683fee1f2b447a7318586445",
      "filter": { "gte": { "at": "2026-06-01T00:00:00Z" } },
      "columns": [
        { "column": "apn",           "output": { "name": "APN" } },
        { "column": "state",         "output": { "name": "SessionState" } },
        { "column": "ggsnIp",        "output": { "name": "IP_GGSN" } },
        { "column": "deviceIp",      "output": { "name": "IP_Device" } },
        { "column": "sentBytes",     "output": { "name": "sbytes", "parquet": { "type": "LONG" } } },
        { "column": "receivedBytes", "output": { "name": "dbytes", "parquet": { "type": "LONG" } } },
        { "column": "sentPackets",   "output": { "name": "spkts",  "parquet": { "type": "LONG" } } },
        { "column": "receivedPackets", "output": { "name": "dpkts", "parquet": { "type": "LONG" } } },
        { "column": "duration",      "output": { "name": "dur",    "parquet": { "type": "LONG" } } }
      ],
      "timeout": 6000
    }
  },
  "schedule": {
    "expression": "0 0 0 15 */3 ? *",
    "isImmediateExecution": true
  },
  "createRule": true
}

The response is 201 Created with a Location header holding the trainer’s identifier.

Field by field

Field Required Meaning
name yes The trainer’s name, unique in the organization. Names the Kubernetes secret that carries your credentials to the job
description no Free text
imageExecution.model.name yes The model name: ^[a-z0-9][a-z0-9-]*$. Only one trainer per model name per organization. It becomes the name of the inferencer, the rule and the scheduler entry โ€” see the naming table
imageExecution.trainingPlan.identifier yes The plan to run, from the catalogue. Must exist
imageExecution.configuration per plan Keyโ€“value pairs, one per entry in the plan’s configFields. Handed to the job as environment variables
imageExecution.timeout yes Seconds the training job may run before it is killed. The autoencoder and the image plan need far more than the forest
source yes Exactly one of path or timeserie, below
schedule no When and how often to run. Absent: once, about a minute from now
createRule no Whether the first training should create the plan’s rule. Default true

Read-only fields come back on GET: identifier, orgName, hasRetraining and schedule.schedulerId โ€” the identifier of the entry the scheduler created, which is also the model name.

Data sources

A file in your file space

"source": { "path": "radius/sessions-2026-q2.parquet" }

path is relative to the root of your organization’s file space, where you upload it beforehand. Inside the job the file space is mounted at /data, so the plan reads /data/radius/sessions-2026-q2.parquet. A path may also be a folder, which is what the image plan expects.

A file source schedules a single image execution in the scheduler.

A time series

"source": {
  "timeserie": {
    "id": "<time series identifier>",
    "filter": { "โ€ฆ": "โ€ฆ" },
    "columns": [ { "column": "<yours>", "output": { "name": "<the plan's>", "parquet": { "type": "LONG" } } } ],
    "timeout": 6000
  }
}
Field Meaning
id The time series to export, from the organization’s time series
filter Optional. A time series filter restricting the rows, for example to a date range
columns Which of your columns feed the plan, and under what name. output.name must be one of the names the plan lists in columnData; output.parquet.type fixes the Parquet type when the default is not right
timeout Seconds to wait for the export. The scheduler waits five seconds more than this for the export’s callback

A time-series source schedules a pipeline: first the platform’s own Parquet export of that time series, writing <model>-<plan>-<time series>.parquet into your file space, then the training image with dataSourcePath pointing at it. Each retraining exports again, so the model always learns from current data.

Schedules

"schedule": { "expression": "0 0 0 15 */3 ? *", "isImmediateExecution": true }
  • expression โ€” a cron expression. Standard five fields work; the scheduler also accepts a leading seconds field and a trailing year field, and ? in the day fields. 0 0 0 15 */3 ? * is 00:00:00 on the 15th of every third month. Time zone is UTC.
  • isImmediateExecution โ€” also run now, without waiting for the first tick. The console sets it, so a new trainer always produces a first version straight away.

No schedule at all means a single execution about one minute after creation, and hasRetraining: false. An expression that pins one instant โ€” every field numeric, including the year โ€” is treated the same way.

What the platform does with your request

Knowing this helps when something does not appear where you expect it.

  1. Rejects the request if another trainer in the organization has the same name or the same model name, or if the plan is not in the catalogue.
  2. Creates a Kubernetes secret named <organization>-<trainer name>-env-secret holding your API key and the organization name. The training job uses it to register the inferencer, create the rule and report back โ€” everything the job does, it does as you.
  3. Asks the scheduler for an image execution (file source) or a pipeline (time-series source) named after the model, running the plan’s image with these environment variables: your configuration entries, modelName, createRule, minDataToTrain from the plan, and dataSourcePath; plus the secret above and the platform’s shared AI settings. The scheduler waits for the job’s callback for timeout + 5 seconds.
  4. Stores the trainer and answers 201.

The trainer record is a schedule, not a status: to know whether a training ran and how it went, read the scheduler’s history for schedulerId = model name, or open See history in the console.

Listing trainers

curl --request GET \
     --header "X-ApiKey: <your-api-key>" \
     https://api.opengate.es/ai/organization/acme/trainer

Returns every trainer of the organization, with the read-only fields filled in. An organization with no trainers gets an empty list, not an error.

Removing a trainer

curl --request DELETE \
     --header "X-ApiKey: <your-api-key>" \
     https://api.opengate.es/ai/organization/acme/trainer/<trainerId>

Removes the schedule from the scheduler, the credentials secret and the trainer record, and answers 204. A training that is running is not interrupted โ€” cancel it through the scheduler if you need to. The inferencer, its model versions and its rule are not touched: they are separate resources, removed through the Inferencers API. Deleting the trainer only means no further version will be trained.

Errors

Errors follow the platform’s usual shape, a list of code, message and context:

Situation Status
A trainer with that name, or that model name, already exists in the organization 400
The training plan identifier is not in the catalogue 400
The body fails the specification โ€” a model name with uppercase letters, both path and timeserie, a missing timeout 400
The organization does not exist, or the trainer to delete does not 404

API specification

Inferencers

Limited access

This feature is only available to root and super_admin_domain profiles. Ask your administrator for proper user role profiling.

What an inferencer is

An inferencer is a trained model as a running service. It has a name, a list of images โ€” one per model version, each a container the platform can deploy โ€” at most one active image, an endpoint where rules call it, a request schema describing what to send, and the rules that call it.

Trainers create inferencers: the first successful training of a model creates one named after the model, later trainings add versions to it. You will rarely create one by hand, but you will use this API for what the trainer does not decide for you โ€” which version runs, and whether it runs at all.

To Call
List the organization’s inferencers GET /ai/organization/{organizationId}/inferencer
Read one GET /ai/organization/{organizationId}/inferencer/{inferencerId}
Create one POST /ai/organization/{organizationId}/inferencer
Change its rules, request path or request schema PUT /ai/organization/{organizationId}/inferencer/{inferencerId}
Delete it DELETE /ai/organization/{organizationId}/inferencer/{inferencerId}
Add a version POST /ai/organization/{organizationId}/inferencer/{inferencerId}/images
Remove a version DELETE /ai/organization/{organizationId}/inferencer/{inferencerId}/images/{imageId}
Deploy or undeploy a version PUT /ai/organization/{organizationId}/inferencer/{inferencerId}/activation?image=โ€ฆ&active=โ€ฆ

Reading an inferencer

curl --request GET \
     --header "X-ApiKey: <your-api-key>" \
     "https://api.opengate.es/ai/organization/acme/inferencer?name=radius-orange"
[
  {
    "identifier": "b1c4โ€ฆ",
    "orgName": "acme",
    "name": "radius-orange",
    "images": [
      {
        "id": "9e2aโ€ฆ",
        "url": "<registry>/acme-radius-orange:v1",
        "creationDate": "2026-07-02T03:00:41.120Z",
        "metrics": { "calculated_threshold": 0.575, "training_max_score": 0.722 }
      },
      {
        "id": "4c7fโ€ฆ",
        "url": "<registry>/acme-radius-orange:v2",
        "creationDate": "2026-08-15T00:01:12.004Z",
        "metrics": { "calculated_threshold": 0.581, "training_max_score": 0.731 }
      }
    ],
    "active": "latest",
    "activeName": "4c7fโ€ฆ",
    "port": 8443,
    "endpoint": "https://acme-radius-orange:8443/api/predict",
    "requestSchema": { "โ€ฆ": "โ€ฆ" },
    "rules": [ { "id": "a0d3โ€ฆ", "channel": "default_channel" } ]
  }
]
Field Meaning
images The versions, oldest first. Each has an id, the image url the trainer pushed, when it arrived and the metrics the training recorded โ€” the numbers to compare versions by
active The image id that is deployed, latest if the inferencer follows its newest version, or absent when nothing is deployed
activeName With active: latest, the id of the version actually running
endpoint Where rules call it: https://<organization>-<name>:8443/<resourcePath>. This is an address inside the platform, reachable from the rules engine, not from the internet
requestSchema The JSON schema of a prediction request, as the training plan declared it
rules The rules switched on and off with the inferencer

The list accepts two filters: ?name=<inferencer name> and ?image=<image url>.

Activating a version

A new inferencer is not deployed. Nothing runs, and its rules stay inactive, until you activate a version:

curl --request PUT \
     --header "X-ApiKey: <your-api-key>" \
     "https://api.opengate.es/ai/organization/acme/inferencer/b1c4โ€ฆ/activation?image=latest&active=true"
image Effect
An image id Deploy exactly that version. Retrainings add versions but leave this one running
latest Deploy the newest version, and replace it automatically each time a training adds a newer one. The rule keeps calling the same endpoint throughout

What activation does, in order: deploys the image as a service named <organization>-<name> listening on 8443 with TLS, waits until its container is running โ€” an image that cannot be pulled fails here with 404 โ€” and then sets every linked rule to active: true. It answers 204.

Only one version can be active. Activating a second one while another is running is refused with 400; deactivate first:

curl --request PUT \
     --header "X-ApiKey: <your-api-key>" \
     "https://api.opengate.es/ai/organization/acme/inferencer/b1c4โ€ฆ/activation?active=false"

Deactivation reverses the steps: rules to active: false, then the service is undeployed. The versions remain.

Active means called on every reading

An active inferencer is invoked by its rule for each reading the rule matches. It is a running service that consumes platform resources for as long as it is active. Leave a model deactivated while you evaluate its metrics, and activate it when you are ready to act on its answers.

Versions

Trainers add versions; you can also add one yourself:

curl --request POST \
     --header "X-ApiKey: <your-api-key>" \
     --header "Content-Type: application/json" \
     --data '{ "url": "<registry>/acme-radius-orange:v3", "metrics": { "calculated_threshold": 0.59 } }' \
     https://api.opengate.es/ai/organization/acme/inferencer/b1c4โ€ฆ/images

Rules that keep the version list honest:

  • An image url ending in :latest is refused โ€” a version must be a fixed tag, or active: latest would mean nothing.
  • The same url cannot be added twice.
  • An inferencer keeps a bounded number of versions, five by default. Adding one beyond the limit drops the oldest inactive version; the active one is never dropped. With a limit of one and the only version active, the addition is refused.
  • With active: latest, adding a version redeploys the service on the new image straight away. If the new image fails to start, it is removed again and the previous version is put back.
  • A version cannot be removed while it is active, and the last remaining version cannot be removed at all. With active: latest, removing the newest version redeploys the one before it.

Rules

rules lists the rules that the inferencer switches on and off. A trainer fills it with the rule the plan creates; you can point it at your own instead:

curl --request PUT \
     --header "X-ApiKey: <your-api-key>" \
     --header "Content-Type: application/json" \
     --data '{ "rules": [ { "id": "<rule id>", "channel": "default_channel" } ] }' \
     https://api.opengate.es/ai/organization/acme/inferencer/b1c4โ€ฆ

Every rule is checked to exist; an unknown id is a 400. The same PUT changes resourcePath โ€” which also rewrites endpoint โ€” and requestSchema. At least one of the three must be present.

Calling an inferencer from a rule

The rules a plan creates are the model to follow. The essential part, in the ADVANCED rules JavaScript API:

http.client.uri = parameterObject['inferenceServiceURL'];   // https://acme-radius-orange:8443/api/predict
http.client.trustedAll = true;                              // the service uses the platform's internal certificate
http.client.headers = { 'content-type': 'application/json', 'accept': 'application/json' };
http.client.body = {
    sbytes: session.sentBytes, dbytes: session.receivedBytes,
    spkts: session.sentPackets, dpkts: session.receivedPackets,
    dur: session.duration
};
var response = http.client.post();
if (response.body.prediction === 1) {
    alarm.open({ alarmName: 'deviceWithAnomaly', ruleName: ruleName, severity: 'URGENT', priority: 'MEDIUM',
                 description: 'Detected anomaly with inferencer service' });
}

Keep the endpoint in a rule parameter rather than in the script, as the generated rules do: the address is stable for the life of the inferencer, but a parameter is what you would change if you ever pointed the rule at a different model. Besides /api/predict, every inferencer built by the platform framework also serves GET /api/metrics, returning the metrics of the running version, and GET /health.

Deleting an inferencer

curl --request DELETE \
     --header "X-ApiKey: <your-api-key>" \
     https://api.opengate.es/ai/organization/acme/inferencer/b1c4โ€ฆ

If a version is active it is deactivated first โ€” rules off, service undeployed. Then the inferencer is removed, and so are its rules, except any rule another inferencer of the organization still lists. The model versions in MLflow and the images in the registry are not deleted. The trainer that created the inferencer is not affected: its next scheduled run creates the inferencer again, rule included.

Creating an inferencer by hand

Trainers do this for you. If you need to register a model that was not trained on the platform:

{
  "name": "my-model",
  "resourcePath": "/api/predict",
  "requestSchema": { "type": "object", "properties": { "x": { "type": "number" } }, "required": ["x"] },
  "images": [ { "url": "<registry>/acme-my-model:v1", "metrics": { "f1": 0.93 } } ],
  "rules": [ { "id": "<rule id>", "channel": "default_channel" } ]
}

Exactly one image at creation, not tagged latest; rules is optional. The image must be pullable by the platform and serve HTTPS on the port the platform expects โ€” the framework’s inference server does, which is why Building training plans is the practical route for a custom model rather than a bare container.

API specification

Scheduler

Limited access

This feature is only available to root and super_admin_domain profiles. Ask your administrator for proper user role profiling.

What the scheduler does

The scheduler runs things later, and again. Three kinds of things:

Type What runs Typical use
REST request An HTTP call to any URL, with headers and body Start a time series export every night
Image execution A container image from the platform registry, as a Kubernetes Job Run a training plan, an aggregation, a report
Pipeline Two or more of the above, in sequence Export data, then train on it โ€” which is exactly what a time-series trainer is

Every schedulation belongs to an organization, has an identifier you choose, a schedule and a history of executions. Trainers are its main customer today, but the console’s Schedulers screen and the dashboard scheduler widgets let you use it directly.

To Call
Create POST /scheduler/organization/{organizationId}/restRequest ยท โ€ฆ/imageExecution ยท โ€ฆ/pipeline
List GET on the same three paths
Delete DELETE โ€ฆ/restRequest/{requestId} ยท โ€ฆ/imageExecution/{imageExecutionId} ยท โ€ฆ/pipeline/{pipelineId}
Report completion (callback) POST โ€ฆ/{type}/{id}/execution/{executionId} โ€” for a pipeline, โ€ฆ/{executionId}/{stepId}
Cancel a running execution DELETE โ€ฆ/imageExecution/{id}/execution/{executionId} ยท โ€ฆ/pipeline/{id}/execution/{executionId}/{stepId}
Read the history GET /scheduler/organization/{organizationId}/history

The schedule

Every schedulation carries the same schedule object, in one of two forms:

"schedule": {
  "cron": { "expression": "0 0 2 * * * *", "timeZone": "Europe/Madrid" },
  "executeNow": false,
  "from": "2026-09-01T00:00:00Z",
  "to": "2026-12-31T23:59:59Z"
}
"schedule": {
  "interval": { "minutes": 15 },
  "executeNow": true
}
Field Meaning
cron.expression Standard five fields (minute hour day month weekday), optionally with a leading seconds field and a trailing year field. 0 0 2 * * * * is every day at 02:00:00. ? is accepted in the day fields
cron.timeZone Where the expression is evaluated. Default UTC
interval.minutes Run every n minutes instead, counted from from or from creation
executeNow Also run immediately on creation
from Cron: no execution before this instant. Interval: the first execution
to No execution after this instant

If an execution is still running when the next one is due, the next one is skipped and logged, not queued. An invalid time zone or expression is rejected on creation with 400 and the offending field named in the error.

REST requests

{
  "identifier": "export-sessions-nightly",
  "schedule": { "cron": { "expression": "0 0 1 * * * *" } },
  "restRequest": {
    "url": "https://api.opengate.es/north/v80/timeseries/provision/organizations/acme/683feeโ€ฆ/export",
    "method": "POST",
    "header": { "X-ApiKey": "<your-api-key>", "Content-Type": "application/json" },
    "body": { "outputFile": { "name": "sessions.parquet" }, "select": [ "โ€ฆ" ] }
  },
  "response": { "async": { "maxTimeToWaitCallback": 600 } }
}

response says how the scheduler knows the request is done:

Form Behaviour
"sync": { "timeout": 5 } The request is complete when the HTTP response arrives. timeout is the seconds to wait for it. A 4xx/5xx marks the execution as an error, with the platform error message when the body carries one
"async": { "maxTimeToWaitCallback": 600 } The scheduler adds a callback header to the outgoing request holding the URL the target must POST to when its work is done, and waits up to this many seconds for it. The platform’s own asynchronous endpoints, such as the time series export, honour that header

Image executions

{
  "identifier": "nightly-aggregation",
  "schedule": { "cron": { "expression": "0 0 3 * * * *", "timeZone": "UTC" } },
  "imageExecution": {
    "name": "acme-aggregator",
    "tag": "1.4.0",
    "env": { "organizationId": "acme", "window": "24h" },
    "envFrom": [ { "secret": "acme-aggregator-secrets" } ],
    "timeout": 900
  },
  "maxTimeToWaitCallback": 1200
}
Field Meaning
imageExecution.name, tag The image, resolved in the platform’s registry
env Environment variables for the container
envFrom Kubernetes secrets and config maps to expose as environment, optionally with a key prefix
timeout Seconds the job may run before Kubernetes kills it
maxTimeToWaitCallback Seconds to wait for the container to report completion, normally a little more than timeout

The container runs as a Kubernetes Job with no retries, with the organization’s file space mounted at /data, and with one extra environment variable the scheduler adds itself: callbackUri, the URL the container must POST to when it finishes. An image that cannot be pulled, or a container that exits with an error before reporting, fails the execution with that reason in the history.

Pipelines

{
  "identifier": "export-then-train",
  "schedule": { "interval": { "minutes": 1440 }, "executeNow": true },
  "pipeline": [
    {
      "identifier": "timeserieSource",
      "restRequest": { "url": "โ€ฆ/export", "method": "POST", "header": { "โ€ฆ": "โ€ฆ" }, "body": { "โ€ฆ": "โ€ฆ" } },
      "response": { "async": { "maxTimeToWaitCallback": 6005 } }
    },
    {
      "identifier": "launchTrainer",
      "imageExecution": { "name": "trainingplan-if-radius-anomalies-per-apn", "tag": "1.0.0", "env": { "โ€ฆ": "โ€ฆ" }, "timeout": 1800 },
      "maxTimeToWaitCallback": 1805
    }
  ]
}

A pipeline is a list of at least two steps, each a REST request or an image execution with a step identifier unique in the pipeline. Steps run in order: a synchronous REST step hands over as soon as its response arrives; an asynchronous REST step and an image step hand over when their callback arrives at โ€ฆ/pipeline/{pipelineId}/execution/{executionId}/{stepId}. A step that fails stops the pipeline; the history records the failing step and its description, and the remaining steps are not run.

The example above is, field for field, what the Trainers API creates for a time-series trainer.

Callbacks

Asynchronous work reports back with a POST to the callback URL โ€” the one in the callback header for a REST request, in callbackUri for a container โ€” carrying:

{ "result": "OK", "description": "Model version 3 published", "startedDate": "โ€ฆ", "finishedDate": "โ€ฆ" }

result is free text by contract; the platform’s own jobs use OK, ERROR and TIMEOUT. The callback is authenticated like any other call to the scheduler. It answers 204.

A callback that arrives after maxTimeToWaitCallback is not lost: the execution, already marked as finished without a callback, moves to FINISHED OUT OF TIME and records the late result.

Execution history

curl --request GET \
     --header "X-ApiKey: <your-api-key>" \
     "https://api.opengate.es/scheduler/organization/acme/history?schedulerType=PIPELINE&schedulerId=radius-orange&limit=20"
[
  {
    "id": "6d6081ee-โ€ฆ",
    "schedulerId": "radius-orange",
    "organization": "acme",
    "type": "PIPELINE",
    "state": "FINISHED",
    "startedDate": "2026-08-15T00:00:01.120Z",
    "finishedDate": "2026-08-15T00:41:37.004Z",
    "steps": [
      { "stepId": "timeserieSource", "result": "OK", "description": "Timeserie exported.",
        "startedDate": "2026-08-15T00:00:01.120Z", "finishedDate": "2026-08-15T00:03:12.271Z" },
      { "stepId": "launchTrainer", "result": "OK",
        "startedDate": "2026-08-15T00:03:12.300Z", "finishedDate": "2026-08-15T00:41:37.004Z" }
    ],
    "content": { "โ€ฆ": "the schedulation as it was executed" }
  }
]
Filter Meaning
schedulerType REST_REQUEST, IMAGE_EXECUTION or PIPELINE
schedulerId The schedulation identifier โ€” for a trainer, the model name
limit Maximum number of entries

state is IN_PROGRESS while a callback is awaited, FINISHED when the execution completed โ€” look at each step’s result to know how โ€” and FINISHED OUT OF TIME when the callback arrived after the wait had expired. An execution stopped by hand shows a step with result CANCELLED.

Cancelling a running execution

curl --request DELETE \
     --header "X-ApiKey: <your-api-key>" \
     https://api.opengate.es/scheduler/organization/acme/pipeline/radius-orange/execution/6d6081ee-โ€ฆ/launchTrainer

Deletes the Kubernetes Job behind an image execution โ€” or the image step of a pipeline โ€” waits until it is gone, and records the step as CANCELLED in the history. The schedulation itself is untouched and fires again at the next tick; to stop that, DELETE the schedulation.

In the web console

Everything on this page has a dashboard widget: the Image Execution, Rest Request and Pipeline scheduler browsers, their wizards, and the Schedulers History list. The Artificial Intelligence section of the console has its own, simpler Schedulers screen โ€” see The web console.

API specification

File connector

Limited access

This feature is only available to root and super_admin_domain profiles. Ask your administrator for proper user role profiling.

What it is

Every organization has a file space on the platform: a directory tree of its own that the file connector manages over HTTP, and that the AI services see as a mounted volume. It is where a trainer’s input file lives, where the time series export writes its Parquet file, where the image plan reads its photographs and writes its heat maps. Nothing outside the organization can reach it.

To Call
Upload a file or an archive POST /fileConnector/organizations/{organizationId}/upload
List a directory GET /fileConnector/organizations/{organizationId}/list?path=โ€ฆ
Download a file GET /fileConnector/organizations/{organizationId}/download?path=โ€ฆ
Delete a file or a directory POST /fileConnector/organizations/{organizationId}/delete

All paths are relative to the organization’s root. Wildcards are not supported. Authentication is the X-ApiKey header.

Uploading

curl --request POST \
     --header "X-ApiKey: <your-api-key>" \
     --form 'meta={"destinyPath": "radius", "overwriteFiles": true};type=application/json' \
     --form "file=@sessions-2026-q2.parquet" \
     https://api.opengate.es/fileConnector/organizations/acme/upload

A multipart request with two parts:

Part Content
meta JSON with destinyPath, the directory to write into (created if missing), and overwriteFiles, whether existing files may be replaced. Default true
file The file itself

Archives are unpacked, not stored. A .zip, .tar, .tar.gz or .tar.bz2 โ€” recognised by its MIME type โ€” is extracted into destinyPath, which is how the image plan’s correct/ and incorrect/ folders are uploaded in one request. Any other file is stored as it is.

204 means everything was written. 200 with an error list means a partial upload: overwriteFiles was false and some files already existed; the list names them.

Listing

curl --request GET \
     --header "X-ApiKey: <your-api-key>" \
     "https://api.opengate.es/fileConnector/organizations/acme/list?path=radius"
[
  { "name": ".", "isDir": true, "size": 4096, "modificationTime": "2026-09-01T10:12:00Z", "mode": "drwxr-xr-x" },
  { "name": "sessions-2026-q2.parquet", "isDir": false, "size": 48219833, "modificationTime": "2026-09-01T10:12:00Z", "mode": "-rw-r--r--" }
]

For a directory, the first entry named . is the directory itself, followed by its children. For a file, just that file. A path that matches nothing is a 400.

Downloading

curl --request GET \
     --header "X-ApiKey: <your-api-key>" \
     --output part-0412_heatmap.jpg \
     "https://api.opengate.es/fileConnector/organizations/acme/download?path=line-3/2026-09-03/part-0412_heatmap.jpg"

Returns the file’s bytes. This is how you retrieve a heat map the image plan wrote next to a photograph.

Deleting

curl --request POST \
     --header "X-ApiKey: <your-api-key>" \
     --header "Content-Type: application/json" \
     --data '{ "destinyPath": "radius", "fileName": "sessions-2026-q2.parquet" }' \
     https://api.opengate.es/fileConnector/organizations/acme/delete

With fileName, deletes that file inside destinyPath. With fileName omitted or null, deletes the whole destinyPath directory. 204 on success, 404 if there was nothing to delete.

How the AI services see it

Service Sees the file space as
A training job /data. A trainer’s source.path of radius/sessions.parquet becomes dataSourcePath=/data/radius/sessions.parquet
A time-series trainer The export writes <model>-<plan>-<time series>.parquet at the root, and the job reads it from /data/
An inferencer /data. The image plan’s image_route is resolved against it, and the heat map is written beside the image

So the console’s file browser in the trainer wizard, the list endpoint and the paths a rule sends to an inferencer all name the same files.

API specification

The web console

Limited access

This feature is only available to root and super_admin_domain profiles. Ask your administrator for proper user role profiling.

Where it is

The Artificial Intelligence section of the web console is the front end of everything in this chapter. It signs its requests with your console session, so you see the organization you are logged into and nothing else, and every action it offers is one of the API calls documented on the previous pages. Its menu has two screens: AI capabilities and Schedulers.

AI capabilities

The screen lists the trainers of your organization, one row per model:

Column Shows
Trainer name The trainer’s name
Model name The model it trains โ€” also the name of its inferencer and rule
Status Whether the model has been trained and whether its inferencer is running
Retraining Whether the trainer has a schedule, and when it fires next
Inferencer The inferencer’s state and active version, or that none has been created yet

Each row has three actions:

  • Configure inferencer โ€” opens the inferencer’s versions. Pick a version from the list to see the metrics its training recorded, and use the Enabled switch to deploy it or take it down. Saving with a different version selected deactivates the running one and activates the new one. This is the console’s face of the activation call. Greyed out until the first training has created the inferencer.
  • See history โ€” the executions of this trainer from the scheduler’s history, one line per step with result, description, start and end. A time-series trainer shows two steps per run: the export and the training. A failed training says why here โ€” Not enough data for APN, a timeout, an image that could not be built.
  • Delete โ€” removes the trainer and its schedule. As with the API, the inferencer and its versions stay; remove them from Configure inferencer or through the Inferencers API.

Reload data refreshes the table; Create new AI trainer opens the wizard.

Creating a trainer

The wizard has four steps and ends with the POST described in Trainers.

1. Selection of training plan

The catalogue, grouped by type โ€” anomaly holds the two RADIUS plans, classification the image plan. Each plan shows its description; read Training plans before choosing, because a plan is specific about the data it wants.

2. Configure training data

  • The plan’s configuration fields, one input per entry in its configFields โ€” the APN for the RADIUS plans.
  • The source type: File or Timeserie. Only the types the plan accepts are enabled; the image plan is file-only.
  • With File: a browser of your organization’s file space. Navigate folders, upload a file or an archive into the current one, download or delete files, and select the file โ€” or stay in a folder to select the folder itself, which is what the image plan needs.
  • With Timeserie: pick one of the organization’s time series, set the export timeout in seconds (the default is generous; a large series can take a while to export), and map every column the plan expects onto a column of the time series. The identifier column of the series is offered first.

3. Trainer configuration

Field Notes
Model’s name Lowercase letters, digits and hyphens; no spaces. It cannot be changed afterwards and names the inferencer and the rule
Model description Free text
Trainer’s name The trainer is a separate object from the model it produces; this name identifies the training task
Maximum execution time Seconds before a training run is killed. Default 1800; raise it for the autoencoder and image plans
Automatic re-training Off, every 30, 60 or 90 days, or a custom cron expression. A period runs at midnight on the day of the month the trainer was created plus three days, every one, two or three months

Whatever you choose, the wizard asks for an immediate first execution, so a first version is trained as soon as the trainer is created.

4. Summary

Everything you selected, then Create. The trainer appears in the table at once; the first version appears when the training finishes, and See history follows its progress.

A trainer with too little data is not an error yet

If the time series does not yet hold the minimum the plan requires, the trainer is still created. Its first execution fails with an explicit message in the history and, if it has a retraining schedule, it tries again at the next tick โ€” when enough history has accumulated, the model gets trained without anyone touching the trainer.

Schedulers

A compact view of the organization’s schedulations โ€” REST requests, image executions and pipelines together โ€” with identifier, type, cron pattern, last and next execution, and a Delete action. New scheduler opens a wizard for a REST request or an image execution. Trainers appear here too, as the pipeline or image execution named after their model: deleting one from this screen stops the trainer’s retraining as surely as deleting the trainer, but leaves the trainer record behind โ€” prefer the Delete action on the AI capabilities screen.

For the full-featured scheduler widgets โ€” cloning, per-schedulation history, pipeline editing โ€” use the dashboard scheduler browsers.

Building training plans

This page is for plan authors

Using the AI features needs nothing on this page. It documents how the platform team writes and packages a new training plan, and what the existing plans look like inside โ€” useful when reading their metrics, their rules or their failure messages.

The framework

Every training plan is a Python project built on the training template framework, the platform’s fork of MLflow Recipes. The framework provides:

  • the recipe engine โ€” the ingest โ†’ split โ†’ transform โ†’ train โ†’ evaluate โ†’ register pipeline, with anomaly detection recipes (anomaly/v1@isolation_forest, anomaly/v1@autoencoder) and a classification recipe (classification/v1) on top of MLflow’s regression and classification ones;
  • a generic inference server โ€” a FastAPI application that loads whatever model the plan produced and serves POST /api/predict, GET /api/metrics and GET /health over TLS;
  • the training-template CLI that runs the recipe, publishes the inference image and registers the result with the platform;
  • the conventions that let the Trainers API, the scheduler and the Inferencers API treat every plan alike.

A plan is therefore mostly declarative: a recipe.yaml, a handful of Python functions, and templates.

Layout of a plan repository

trainingplan-<name>/
โ”œโ”€โ”€ recipe.yaml                      # the recipe: algorithm, steps, thresholds, metrics
โ”œโ”€โ”€ model_schema.txt                 # JSON schema of a prediction request โ†’ inferencer.requestSchema
โ”œโ”€โ”€ steps/
โ”‚   โ”œโ”€โ”€ ingest.py                    # load_file_as_cleaned_dataframe(path): read and clean the data
โ”‚   โ”œโ”€โ”€ split.py                     # create_dataset_filter(df): rows to keep after the split
โ”‚   โ”œโ”€โ”€ transform.py                 # transformer_fn(): the scikit-learn transformer to fit
โ”‚   โ”œโ”€โ”€ train.py                     # estimator_fn(params): the unfitted estimator
โ”‚   โ””โ”€โ”€ custom_metrics.py            # metrics referenced from recipe.yaml
โ”œโ”€โ”€ configurations/
โ”‚   โ”œโ”€โ”€ entrypoint.sh                # the three CLI commands the container runs
โ”‚   โ”œโ”€โ”€ config.json                  # how the inference server loads the model
โ”‚   โ”œโ”€โ”€ predict_service.py           # the prediction service class
โ”‚   โ”œโ”€โ”€ schemas.py                   # pydantic RequestBody / ResponseBody
โ”‚   โ”œโ”€โ”€ mapper.py                    # map_to_response_body(dict) โ†’ ResponseBody
โ”‚   โ”œโ”€โ”€ requirements.txt             # runtime dependencies of the inference image
โ”‚   โ””โ”€โ”€ cli_config/rule_generator.py # generate_rule_creation_body() โ†’ the rule to create
โ””โ”€โ”€ cli_templates/
    โ”œโ”€โ”€ inference-docker-template.txt          # Dockerfile of the inference image
    โ”œโ”€โ”€ rule_create_body.txt                   # template the rule generator fills in
    โ””โ”€โ”€ additional_trainer_dependencies.txt    # extra RUN lines for the trainer image

The recipe

recipe: "anomaly/v1@isolation_forest"
threshold: 0.95                         # percentile of training scores that becomes the decision threshold
steps:
  ingest: {{INGEST_CONFIG}}             # filled in at run time from dataSourcePath
  split:
    split_ratios: [0.75, 0.125, 0.125]
    post_split_filter_method: create_dataset_filter
  transform:
    using: custom
    transformer_method: transformer_fn
  train:
    using: custom
    estimator_method: estimator_fn
    model_type: isolation-forest
  register:
    allow_non_validated_model: True

recipe selects the engine: anomaly/v1@isolation_forest, anomaly/v1@autoencoder or classification/v1. Each step names the function in steps/ that customises it; estimator_params under train is passed to estimator_fn. evaluate.validation_criteria and primary_metric decide whether a model is validated; register.allow_non_validated_model: True registers it regardless, which is what the current plans do. Custom metrics are declared under custom_metrics and implemented in steps/custom_metrics.py.

The {{INGEST_CONFIG}} placeholder is rendered when the container starts: the framework writes a run profile pointing the ingest step at steps/ingest.py::load_file_as_cleaned_dataframe with the location in dataSourcePath. That function is where a plan cleans its data and enforces minDataToTrain, failing the run with a clear message when there is not enough.

The inference service

After training, the framework copies its FastAPI server, the model artefacts and the plan’s configurations/ into a dist/ folder and builds an image from cli_templates/inference-docker-template.txt. At start-up the server reads configurations/config.json:

{
  "service_class_name": "AnomalyPredictionService",
  "service_file_name": "predict_service.py",
  "model_path": "model/model.pkl",
  "transformers_path": "transformer.pkl"
}

and loads the class from predict_service.py. The framework adds two keys after training โ€” threshold, the calculated decision threshold, and training_max_score โ€” so the running service knows the numbers its version was trained with. The class contract:

class AnomalyPredictionService:
    def __init__(self, model, threshold, transformer=None, max_score=None): ...
    @staticmethod
    def load_model(model_path: str): ...          # joblib, Keras, torch โ€” the plan decides
    def predict(self, data: dict) -> dict: ...    # one request in, one result out

schemas.py declares the request and response as pydantic models โ€” the request is what model_schema.txt describes in JSON schema, and what the inferencer publishes as requestSchema โ€” and mapper.py turns the service’s result into the response. Requests that fail the schema get 422.

The rule template

configurations/cli_config/rule_generator.py must expose generate_rule_creation_body() returning the JSON of a rule creation request. The existing plans render cli_templates/rule_create_body.txt with the model name, the inferencer’s service name and port, and plan configuration such as the APN. The rule is created inactive, in default_channel, named after the model, and only when no rule of that name exists there already. Keep the inferencer endpoint in a rule parameter, as the templates do.

What a trainer job looks like from inside

The container’s entrypoint.sh runs three commands and forwards SIGTERM to whichever is running, so a job killed by its timeout still reports TIMEOUT:

training-template generate-local-yaml --model-type isolation-forest
training-template run --profile local
training-template publish-inference isolation-forest
Command Does
generate-local-yaml Writes profiles/local.yaml: MLflow experiment <organizationId>-<modelName>, registered model <organizationId>-<modelName>-<model type>, tracking URI, artefact location and dataSourcePath
run --profile local Executes the recipe, logging parameters, metrics and the model to MLflow. A failure sends an ERROR callback and stops
publish-inference Downloads the latest run’s artefacts into dist/, renders the inference Dockerfile, builds and pushes the image with a Kaniko job as <imageRepoUrl>/<organizationId>-<modelName>:v<model version>, then creates the inferencer โ€” with the rule, if createRule is true and the inferencer is new โ€” or adds the image to the existing one, and sends the OK callback

The environment the job receives:

Variable From Meaning
organizationId, inferencersAPICredential The per-trainer secret Who the job acts as: the organization and the API key of the user who created the trainer
modelName, createRule, minDataToTrain, dataSourcePath The Trainers API The model to produce, whether to create the rule, the plan’s minimum, where the data is under /data
The plan’s configFields (apnโ€ฆ) The trainer’s configuration Plan-specific settings
callbackUri The scheduler Where to POST the final OK / ERROR / TIMEOUT
mlflowApiURL, mlflowArtifactsLocation, pvcName, imageRepoUrl, imageRepoUser, imageRepoPassword, inferencersAPI, inferencerServicePort, rulesAPI, serviceTlsCert, serviceTlsKey, kanikoTimeout, kanikoMode, kanikoRetries, trainerLogLevel, inferenceLogLevel, shouldShowAnomalyReason The platform’s shared AI secret Where MLflow, the registry, the Inferencers and Rules APIs live; the TLS material the inference server uses; build settings

Packaging a plan

  1. Build the base trainer image โ€” the framework plus the plan’s extra dependencies from cli_templates/additional_trainer_dependencies.txt (TensorFlow for the autoencoder, PyTorch for the image plan):

    training-template build-base-trainer --image-name <registry>/base-trainer-<type> --tag <tag>
  2. Generate the trainer Dockerfile, from the plan’s directory, and build it from the parent directory:

    training-template generate-trainer-dockerfile --image-name <registry>/base-trainer-<type> --tag <tag>
    cd .. && docker build -t <registry>/trainingplan-<name>:<version> -f trainingplan-<name>/generated/Dockerfile .
    docker push <registry>/trainingplan-<name>:<version>

    The generated Dockerfile copies steps/, configurations/, recipe.yaml, model_schema.txt and cli_templates/ and sets configurations/entrypoint.sh as the entry point.

  3. Register the plan in the catalogue with its image.name and image.tag, configFields, columnData, minDataToTrain, and the source types it supports. The catalogue is the platform’s own collection; the Training Plans API reads it, it does not write it.

To try a plan without the platform, set the variables of the table above in a shell, run the three entrypoint commands by hand with a local MLflow, and start the built inference server with training-template run-sbox-inference. A generate-mock-trainer command produces an image that skips the training and only exercises the callbacks, the Inferencers API and the rule creation โ€” the way the end-to-end tests check the loop without waiting for a real training.

The AI platform itself

The services this chapter documents โ€” the Training Plans, Trainers and Inferencers APIs, the AI console, the MLflow tracking server and the shared AI secret โ€” are versioned and deployed together by the platform team, alongside the scheduler and the file connector that belong to the core platform. Each API generates its server from the OpenAPI specification shown at the bottom of its page, so the specification is the contract, not a description of it.