# Inferencers

{{% notice style="note" title="Limited access" icon="lock" %}}
{{% include file="include/restricted_access.md" hidefirstheading="true"  %}}
{{% /notice %}}

## 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

```bash
curl --request GET \
     --header "X-ApiKey: <your-api-key>" \
     "https://api.opengate.es/ai/organization/acme/inferencer?name=radius-orange"
```

```json
[
  {
    "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:

```bash
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:

```bash
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.

{{% notice style="info" title="Active means called on every reading" icon="circle-info" %}}
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.
{{% /notice %}}

## Versions

Trainers add versions; you can also add one yourself:

```bash
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:

```bash
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](../../management/organizations/channels/rules/):

```javascript
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

```bash
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:

```json
{
  "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](../building_training_plans/) is the practical route for a custom model rather than a
bare container.

## API specification

{{< openapi src="inferencers.yaml" >}}
