Skip to main content
Version: 1.4 (Current)

Service topology

Overview​

A service topology is a graph of one service node, its environment nodes, and the resource nodes under them. The graph is a DAG of nodes and edges: a resource can hang from an environment or from another resource of the same environment, and it can have several parents (a database used by two applications). Every topology is bound to exactly one service, and a service has at most one topology.

This API creates and edits topologies, maps the service's active alerts onto the nodes (assignments), proposes nodes from alert data (suggest), and manages the weight policies that set an alert weight. The graph editor in the web UI uses the same endpoints.

Saving a topology also writes its resource-to-resource edges into the resource graph as relations with source topology, so policies and cause-and-effect rules can use the hierarchy you draw.

Authentication and permissions​

All endpoints take an API key:

Authorization: Key YOUR_API_KEY_HERE
ActionScope
List or read topologies, assignments, suggestions, weight policiesread:service_topologies
Create, update, or delete topologies and weight policies (including bulk)write:service_topologies
Export a topology or a bundleread:service_topologies
Import topologieswrite:service_topologies

See API keys for scopes. Creating a topology also requires the Service Topology feature in your license and a free slot in its topology limit (403 otherwise).

Topology object​

{
"id": "3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01",
"name": "Orders",
"description": "Orders service topology",
"service": "orders",
"serviceKpi": null,
"orientation": "TB",
"nodes": [
{
"id": "0d1e2f3a-4b5c-4d6e-8f90-a1b2c3d4e5f6",
"kind": "service",
"label": "orders",
"environment": null,
"resource": null,
"icon": "fa:server",
"params": {},
"hierarchyConfig": {},
"position": { "x": 100, "y": 100 }
},
{
"id": "1e2f3a4b-5c6d-4e7f-9a01-b2c3d4e5f6a7",
"kind": "environment",
"label": "Production",
"environment": "Production",
"resource": null,
"icon": "fa:server",
"params": {},
"hierarchyConfig": {},
"position": { "x": 100, "y": 250 }
},
{
"id": "2f3a4b5c-6d7e-4f80-a112-c3d4e5f6a7b8",
"kind": "resource",
"label": "orders-app",
"environment": "Production",
"resource": "orders-app",
"icon": "fa:server",
"params": {},
"hierarchyConfig": { "disabledHereditary": false },
"position": { "x": 100, "y": 400 }
},
{
"id": "3a4b5c6d-7e8f-4091-b223-d4e5f6a7b8c9",
"kind": "resource",
"label": "orders-db",
"environment": "Production",
"resource": "orders-db",
"icon": "fa:database",
"params": {},
"hierarchyConfig": {},
"position": { "x": 100, "y": 550 }
}
],
"edges": [
{ "id": "e1", "source": "0d1e2f3a-4b5c-4d6e-8f90-a1b2c3d4e5f6", "target": "1e2f3a4b-5c6d-4e7f-9a01-b2c3d4e5f6a7" },
{ "id": "e2", "source": "1e2f3a4b-5c6d-4e7f-9a01-b2c3d4e5f6a7", "target": "2f3a4b5c-6d7e-4f80-a112-c3d4e5f6a7b8" },
{ "id": "e3", "source": "2f3a4b5c-6d7e-4f80-a112-c3d4e5f6a7b8", "target": "3a4b5c6d-7e8f-4091-b223-d4e5f6a7b8c9" }
],
"schemaVersion": 2,
"createTime": "2026-10-09T08:15:00.000Z",
"updateTime": "2026-10-09T09:30:00.000Z"
}
FieldMeaning
idTopology ID
name / descriptionShown in the topology list and the editor header. Both are required when creating
serviceThe service this topology describes. Unique across topologies and immutable after creation
serviceKpiOptional ID of the Service KPI linked to the topology (service_kpi is also returned for older clients)
orientationLayout of the graph: TB (top-bottom), LR (left-right), or CI (circular). Default TB
nodes / edgesThe graph — see below
schemaVersion2 for topologies stored as nodes and edges; 1 for an old tree-based topology that has not been migrated yet and is converted on the fly (see Migration of older topologies)
createTime / updateTimeWhen the record was created and last saved

Node​

FieldMeaning
idStable node ID (any non-empty string; the editor uses UUIDs). Unique within the topology and across topologies
kindservice, environment, or resource
labelNode name, at most 255 characters. For an environment node it is the environment name; for a resource node it is the resource name
environmentDerived by the server on save: the environment the node belongs to (null for the service node)
resourceDerived by the server on save: the resource name for resource nodes, null otherwise
iconIcon identifier (for example fa:database), "" when none
paramsFree object for extra data (the editor stores the node Description here)
hierarchyConfigAlert hierarchy settings of the node. Allowed keys: hereditary, disabledHereditary, disabledSLA, disabledPerformance, severityLevelsSLA, severityLevelsPerformance, severityLevelsSLAAvailability, severityLevelsSLAPerformance. Any other key is rejected
position{ "x": number, "y": number } on the canvas

Values you send in environment and resource are ignored: the server recomputes them from the graph (the environment is the environment node reachable upwards from the resource).

Edge​

FieldMeaning
idEdge ID. Generated when omitted
sourceID of the parent node
targetID of the child node

Edges point downwards: service → environment, environment → resource, resource → resource. A repeated (source, target) pair is silently ignored.

Validation rules​

POST and PUT validate the whole graph before anything is stored. When it is invalid the response is 400 with message: "invalid topology graph" and an errors list, one entry per problem:

{
"status": "error",
"message": "invalid topology graph",
"code": 400,
"errors": [
{ "code": "cycle", "path": "edges", "message": "cycle: a → b → a" },
{ "code": "resourceWithoutEnvironment", "path": "nodes[3a4b5c6d-…]", "message": "resource \"orders-db\" is not reachable from an environment" }
],
"requestId": "…"
}

path points at the offending node or edge (nodes[<index>] or nodes[<index>].label for malformed input, nodes[<id>] for structural rules, edges[<index>] or edges for edge rules).

CodeRule
serviceNodeCountExactly one node of kind service is required
cycleThe edges form a loop. The message lists the node IDs of the cycle
serviceHasParentThe service node cannot be the target of an edge
environmentNotUnderServiceAn environment node must have exactly one parent: the service node
duplicateEnvironmentTwo environment nodes share the same label
resourceUnderServiceA resource node cannot hang directly from the service node
resourceWithoutEnvironmentA resource node is not reachable from any environment node
resourceInMultipleEnvironmentsFollowing its parents, a resource node reaches two different environments. A resource that exists in two environments needs one node per environment
duplicateResourceTwo resource nodes have the same label inside the same environment. Give a shared resource one node with several parents instead
duplicateIdTwo nodes share an id
idCollisionA node id is already used by another topology (PUT only; the message names the ID)
selfLoopAn edge whose source equals its target
edgeEndpointMissingAn edge without source / target, or pointing at a node that is not in nodes
missingId, missingLabel, invalidKind, invalidNode, invalidEdgeA node or edge is not an object or lacks a required field; kind is not service / environment / resource
valueTooLongA label longer than 255 characters
invalidPosition, invalidParams, invalidHierarchyConfigposition.x / position.y are not numbers; params or hierarchyConfig is not an object, or hierarchyConfig has an unknown key
invalidNodes, invalidEdges, tooLargenodes / edges is not a list, or more than 5,000 nodes

Malformed-input errors are reported first; the structural rules (cycle, environments, resources) only run once every node and edge is well-formed and there is one service node.


List topologies​

GET /api/service_topology
Authorization: Key YOUR_API_KEY

Returns a paginated list of topology objects, graph included.

Query parameters​

ParameterDescriptionExample
id, name, description, service, service_kpi, orientation, schemaVersionFilter by column?service=orders
createTime, updateTimeFilter by date?updateTime=2026-10-01
sort-bySort column; prefix with - for descending (name, service, createTime, updateTime, …)?sort-by=-updateTime
page / page-sizePagination?page=2&page-size=50

Example​

Most recently saved topologies first

curl -X GET "http://your-tobealert.com:9000/api/service_topology?sort-by=-updateTime&page-size=20" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Response​

{
"status": "ok",
"topologies": [ /* array of topology objects */ ],
"total": 3,
"page": 1,
"pageSize": 20,
"pages": 1,
"more": false
}

Get a topology​

GET /api/service_topology/<topology_id>
Authorization: Key YOUR_API_KEY
ParameterDescription
columnid (default) or service_name to look the topology up by its service instead of its ID

Read the topology of the service 'orders'

curl -X GET "http://your-tobealert.com:9000/api/service_topology/orders?column=service_name" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Response​

{
"status": "ok",
"total": 1,
"topology": { /* topology object */ }
}
CodeReason
400column other than id / service_name
404No topology with that ID or service

Create a topology​

POST /api/service_topology
Content-Type: application/json
Authorization: Key YOUR_API_KEY

Creates the topology record and its graph. Requires write:service_topologies.

Request body​

FieldTypeRequiredDescription
nameStringYesDisplay name
descriptionStringYesDescription
serviceStringYesService ID. Must not already have a topology
serviceKpiStringNoLinked Service KPI ID
orientationStringNoTB, LR, or CI
nodesArrayNoGraph nodes. When omitted, the topology starts with a single service node and no edges
edgesArrayNoGraph edges ([] when omitted). Validated together with nodes

Example​

Create an empty topology (service node only) for the service 'orders'

curl -X POST "http://your-tobealert.com:9000/api/service_topology" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"name": "Orders",
"description": "Orders service topology",
"service": "orders",
"orientation": "TB"
}'

Response​

201 Created:

{
"status": "ok",
"id": "3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01",
"topology": { /* topology object */ }
}

Errors​

CodeReason
400name, description, or service missing; invalid orientation; invalid graph (with the errors list)
403Feature not enabled or topology limit reached for your license
409The service already has a topology

Update a topology​

PUT /api/service_topology/<topology_id>
Content-Type: application/json
Authorization: Key YOUR_API_KEY

Updates the record fields and, when nodes and edges are sent, replaces the whole graph. Requires write:service_topologies. This is what Save changes in the graph editor calls.

Request body​

Any subset of name, description, serviceKpi, orientation, nodes, edges. Rules:

  • nodes and edges must be sent together; the graph you send replaces the stored one entirely (nodes you leave out are deleted).
  • service cannot change (400 if a different value is sent). id, schemaVersion, createTime, and updateTime are ignored; any other field is rejected (400 unknown fields).
  • Node IDs must not be in use by another topology (idCollision).
  • The graph is validated with the validation rules before it is stored; an invalid graph changes nothing.

Example​

Replace the graph: orders-db hangs from two parents in Production

curl -X PUT "http://your-tobealert.com:9000/api/service_topology/3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"nodes": [
{
"id": "svc",
"kind": "service",
"label": "orders",
"icon": "fa:server",
"position": {
"x": 100,
"y": 100
}
},
{
"id": "env-prod",
"kind": "environment",
"label": "Production",
"position": {
"x": 100,
"y": 250
}
},
{
"id": "orders-app",
"kind": "resource",
"label": "orders-app",
"position": {
"x": 0,
"y": 400
}
},
{
"id": "billing-app",
"kind": "resource",
"label": "billing-app",
"position": {
"x": 200,
"y": 400
}
},
{
"id": "orders-db",
"kind": "resource",
"label": "orders-db",
"icon": "fa:database",
"position": {
"x": 100,
"y": 550
}
}
],
"edges": [
{
"source": "svc",
"target": "env-prod"
},
{
"source": "env-prod",
"target": "orders-app"
},
{
"source": "env-prod",
"target": "billing-app"
},
{
"source": "orders-app",
"target": "orders-db"
},
{
"source": "billing-app",
"target": "orders-db"
}
]
}'

Response​

{
"status": "ok",
"topology": { /* topology object with the stored graph */ }
}

Errors​

CodeReason
400Body not an object, unknown field, service changed, only one of nodes / edges, empty name, invalid orientation, invalid graph (errors list), or idCollision
404Topology not found

Resource graph synchronization​

After every successful save (and on create), each resource → resource edge whose two nodes are in the same environment is stored in the resource graph as a relation {environment, resource: <child>, parent: <parent>, source: "topology", sourceRef: <topology id>}. Relations of this topology that no longer match an edge are removed. Service → environment and environment → resource edges are not written (they are not resource relations).

Relations follow the usual source ranking (manual > event > cmdb > topology > policy): a topology relation never overwrites one declared manually, by an alert, or by a CMDB, but it does take precedence over relations written by policies. You can list what a topology wrote with GET /api/resources/relations?source=topology&sourceRef=<topology id>.


Delete a topology​

DELETE /api/service_topology/<topology_id>
Authorization: Key YOUR_API_KEY

Deletes the topology, its graph, and the resource graph relations it wrote (source: topology, sourceRef = this ID). Requires write:service_topologies.

Delete a topology

curl -X DELETE "http://your-tobealert.com:9000/api/service_topology/3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Response​

{
"status": "ok",
"topology": "3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01",
"message": "topology deleted sucessfully"
}
CodeReason
404Topology not found

Bulk-delete topologies​

POST /api/service_topologies/bulk/delete
Content-Type: application/json
Authorization: Key YOUR_API_KEY

Deletes several topologies at once, including their resource graph relations. Requires write:service_topologies. This is what Delete topology on a multi-row selection in the web UI calls.

FieldTypeRequiredDescription
service_topologiesArrayYesTopology IDs, as strings or as { "id": "…" } objects

Delete two topologies

curl -X POST "http://your-tobealert.com:9000/api/service_topologies/bulk/delete" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"service_topologies": [
"3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01",
"7b2d9e4f-1c3a-4f5e-8d60-9a1b2c3d4e5f"
]
}'

Response​

{
"status": "ok",
"deleted": 2
}
CodeReason
400service_topologies empty or without valid IDs

Alert assignments​

GET /api/service_topology/<topology_id>/assignments
Authorization: Key YOUR_API_KEY

Maps the service's active alerts onto the nodes of the topology. The web UI uses it for Show alerts (node colors, badges, the per-node alerts table). Requires read:service_topologies.

The alerts are the same ones returned for the service (GET /api/alerts/by_service/<service>): alerts whose service contains the topology's service, filtered by the impact rules of the service catalog entry unless includeExcluded is set.

Query parameters​

ParameterDescription
severityOnly alerts with these severities (repeatable: ?severity=critical&severity=major)
includeExcludedtrue to ignore the service's impact filters

How an alert is assigned​

Environment and resource names are trimmed and compared exactly (an empty environment is a value of its own). For each alert:

  1. Exact match — a resource node with the alert's environment and resource gets the alert.
  2. Roll-up through the resource graph — when the alert's resource is not a node, the server walks the resource's ancestors in the resource graph (the alert's own resourceParent counts as the first ancestor even if the graph has no relation for it yet, up to 10 levels) and assigns the alert to the first ancestor that is a node of the same environment. These alerts are also reported in rollups.
  3. Environment node — otherwise, the node of the alert's environment.
  4. Unassigned — otherwise, the alert is listed in unassigned.

Aggregating counts upwards (an environment showing the alerts of its resources) and coloring is done by the client.

Example​

Critical alerts of the service, assigned to the topology nodes

curl -X GET "http://your-tobealert.com:9000/api/service_topology/3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01/assignments?severity=critical" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Response​

{
"status": "ok",
"total": 3,
"alerts": [ /* array of alert objects, as in the alert API */ ],
"assignments": {
"orders-db": ["a1f3…", "b7c2…"],
"env-prod": ["c9d4…"]
},
"unassigned": [],
"rollups": {
"b7c2…": { "nodeId": "orders-db", "via": "orders-db" }
}
}
FieldMeaning
alertsThe alerts considered, full objects
assignmentsNode ID → list of alert IDs assigned to that node
unassignedAlert IDs that matched neither a resource nor an environment node
rollupsAlert ID → { nodeId, via } for alerts assigned through the resource graph: via is the ancestor resource that is a node
CodeReason
404Topology not found

Suggest nodes from alerts​

POST /api/service_topology/<topology_id>/suggest
Content-Type: application/json
Authorization: Key YOUR_API_KEY

Proposes the environment and resource nodes (and edges) that the service's active alerts mention but the topology does not have yet. Nothing is saved: the client adds the suggestions it accepts and then saves with PUT. This is what Alert-driven service topology in the graph editor calls. Requires read:service_topologies.

Request body​

FieldTypeDescription
severityArrayOnly alerts with these severities (same as the severity query parameter)
includeExcludedBooleantrue to ignore the service's impact filters

Both can also be passed as query parameters.

How suggestions are built​

For every alert whose (environment, resource) is not already a node:

  1. An environment node is proposed when the alert's environment is not in the topology, hanging from the service node.
  2. The parent chain of the resource is resolved, from the alert's own resourceParent first and, when the alert has none, from the resource's main parent in the resource graph. The walk continues parent by parent (at most 10 levels, no loops) and stops at the first resource that is already a node.
  3. One resource node is proposed per resource of the chain that is not a node yet, chained with edges from the first existing node (or the environment node) down to the alert's resource.

Alerts without an environment are skipped. Proposed nodes get new IDs, suggested: true, and positions computed with the topology's orientation around the existing nodes.

Example​

Propose nodes for the resources that raise alerts on this service

curl -X POST "http://your-tobealert.com:9000/api/service_topology/3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01/suggest" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"severity": [
"critical",
"major",
"minor",
"warning"
]
}'

Response​

{
"status": "ok",
"nodes": [
{
"id": "9c8b7a6f-5e4d-4c3b-a291-0f1e2d3c4b5a",
"kind": "resource",
"label": "vm-7",
"environment": "Production",
"resource": "vm-7",
"icon": "fa:server",
"params": {},
"hierarchyConfig": {},
"position": { "x": 300, "y": 550 },
"suggested": true
}
],
"edges": [
{ "id": "…", "source": "orders-app", "target": "9c8b7a6f-5e4d-4c3b-a291-0f1e2d3c4b5a", "suggested": true }
],
"summary": {
"newEnvironments": [],
"newResources": [{ "environment": "production", "resource": "vm-7" }],
"skippedEnvironments": ["Production"],
"skippedResources": [{ "environment": "production", "resource": "orders-db" }]
}
}
FieldMeaning
nodes / edgesProposed nodes and edges, ready to be added to the graph and saved with PUT
summary.newEnvironments / newResourcesProposed environment names, and proposed resources as {environment, resource} (the same resource name can exist in two environments)
summary.skippedEnvironments / skippedResourcesEnvironments and {environment, resource} pairs mentioned by alerts that are already nodes
CodeReason
404Topology not found

Export a topology​

GET /api/service_topology/<topology_id>/export
Authorization: Key YOUR_API_KEY

Returns a self-contained JSON document with everything needed to recreate the service elsewhere: the topology record, the complete graph (IDs and positions included), the weight policies that apply to the service, and the service's catalog entry. The response is sent as an attachment (Content-Disposition: attachment; filename="topology-<service>-<YYYYMMDD-HHmm>.json"). Requires read:service_topologies.

GET /api/service_topology/export?ids=<id1>,<id2>

Returns a bundle with several topologies (topologies-<YYYYMMDD-HHmm>.json). Without ids, the bundle holds every topology. 404 if one of the IDs does not exist.

Export document​

{
"kind": "tobealert-service-topology",
"schemaVersion": 2,
"exportedAt": "2026-10-09T10:42:00Z",
"appVersion": "1.12.0",
"topology": {
"id": "3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01",
"name": "Orders",
"description": "Orders service topology",
"service": "orders",
"serviceKpi": null,
"orientation": "TB"
},
"nodes": [ /* node objects, as in GET /api/service_topology/<id> */ ],
"edges": [ /* edge objects */ ],
"weightPolicies": [
{
"id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
"name": "orders-critical-weight",
"description": "Critical alerts of orders weigh 100",
"filters": { "operator": "AND", "clauses": [ { "key": "service", "op": "=", "value": "orders" } ] },
"weight": 100,
"status": "active"
}
],
"catalogService": {
"serviceId": "orders",
"name": "Orders",
"description": "Order intake and fulfilment",
"icon": "fa:cart-shopping",
"alertImpact": { "severities": [], "statuses": [], "whitelist": { "operator": "AND", "clauses": [] }, "blacklist": { "operator": "AND", "clauses": [] } }
}
}
KeyContent
kindtobealert-service-topology; a bundle is tobealert-service-topology-bundle with the documents under topologies
schemaVersion2 — the only version the importer accepts
exportedAt / appVersionInformational
topologyid, name, description, service, serviceKpi, orientation
nodes / edgesThe graph, in the node and edge format of this API
weightPoliciesPolicies tied to the topology (topologyId) plus global policies whose filters contain a clause on the service key with this service as value
catalogServiceThe Services catalog entry (serviceId, name, description, icon, alertImpact), or null when the service is not in the catalog

Export one topology

curl -X GET "http://your-tobealert.com:9000/api/service_topology/3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01/export" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Export every topology as a bundle

curl -X GET "http://your-tobealert.com:9000/api/service_topology/export" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Each exported topology writes a service-topology-exported audit entry. The web UI exposes the same files as Export / Export all on the Services tab — see Import and export.


Import topologies​

POST /api/service_topology/import?mode=replace|merge
Content-Type: application/json
Authorization: Key YOUR_API_KEY

Creates or updates topologies from an export document, a bundle, or a file from an earlier release. Requires write:service_topologies. Every topology in the request is planned first; if any of them is invalid, nothing is written (400). Use dryRun=1 to get the plan without applying it.

Query parameters​

ParameterRequiredDefaultDescription
modeYes—replace: the topology becomes exactly the file (nodes, edges and topology-tied weight policies that are not in the file are removed; catalog fields are overwritten). merge: nodes, edges and policies of the file are added or updated, nothing is removed, and only empty catalog fields are filled
dryRunNo01 / true: validate and return the previews, write nothing
positionsNokeepMerge only. keep: existing nodes keep their canvas position; file: positions come from the file
serviceNo—Service name for a file that does not carry one (a bare tree from an earlier release, or an editor export without topology.service)

Request body​

BodyRecognised by
An export documentkind: tobealert-service-topology
A bundlekind: tobealert-service-topology-bundle — every topology is imported
An editor export { "topology", "nodes", "edges" }nodes without kind. weightPolicies and catalogService are optional
A tree from an earlier release{ "service": "...", "tree": {...} }, or the tree itself (nodes with type and children); converted on the fly, warning legacyConverted
multipart/form-data with a file partAny of the above as a file upload

The body is limited to 2 MB (tooLarge) and each topology to 5,000 nodes.

How a document is applied​

  • The topology is identified by topology.service: it is updated when a topology for that service exists, created otherwise (the license limit applies to creation: 403 when it is reached). topology.id in the file is ignored.
  • Node IDs are kept. IDs that belong to another topology are regenerated and the edges remapped (idsRegenerated in the preview).
  • Nodes are matched by identity, not by ID: the service node, environments by name, resources by environment + name. Matched nodes take label, icon, params and hierarchyConfig from the file.
  • After merging, the whole graph is validated again with the validation rules; failures are reported with the after merge: prefix.
  • Weight policies are matched by name (across the instance), created or updated with the file's description, filters, weight and status, and tied to the topology. In replace mode, policies tied to the topology that are not in the file are deleted.
  • The catalog service is created if missing. If it exists: replace overwrites description, icon and alertImpact with non-empty values from the file; merge only fills fields that are empty (description empty, icon server, alertImpact without rules).
  • The topology's resource-to-resource edges are synchronized to the resource graph (source topology), as on PUT.

The full comparison of both modes is in Import and export — Replace or merge.

Example​

Dry run of a merge into the orders topology

curl -X POST "http://your-tobealert.com:9000/api/service_topology/import?mode=merge&dryRun=1" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"kind": "tobealert-service-topology",
"schemaVersion": 2,
"topology": {
"service": "orders",
"name": "Orders",
"description": "Orders service topology",
"orientation": "TB"
},
"nodes": [
{
"id": "svc-orders",
"kind": "service",
"label": "orders",
"position": {
"x": 100,
"y": 100
}
},
{
"id": "env-prod",
"kind": "environment",
"label": "Production",
"position": {
"x": 100,
"y": 250
}
},
{
"id": "res-orders-app",
"kind": "resource",
"label": "orders-app",
"position": {
"x": 100,
"y": 400
}
},
{
"id": "res-orders-db",
"kind": "resource",
"label": "orders-db",
"icon": "fa:database",
"position": {
"x": 100,
"y": 550
}
}
],
"edges": [
{
"id": "e1",
"source": "svc-orders",
"target": "env-prod"
},
{
"id": "e2",
"source": "env-prod",
"target": "res-orders-app"
},
{
"id": "e3",
"source": "res-orders-app",
"target": "res-orders-db"
}
],
"weightPolicies": [],
"catalogService": null
}'

Uploading a file instead of a JSON body:

curl -X POST "http://your-tobealert.com:9000/api/service_topology/import?mode=replace" \
-H "Authorization: Key YOUR_API_KEY" \
-F "file=@topology-orders-20261009-1042.json"

Response​

A dry run returns one preview per topology:

{
"status": "ok",
"dryRun": true,
"previews": [
{
"topology": { "service": "orders", "action": "update", "id": "3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01" },
"nodes": { "create": 1, "update": 0, "remove": 0, "unchanged": 3 },
"edges": { "create": 1, "update": 0, "remove": 0, "unchanged": 2 },
"weightPolicies": { "create": 0, "update": 0, "remove": 0, "unchanged": 0 },
"catalogService": { "action": "skip", "fields": [] },
"idsRegenerated": 0,
"warnings": [
{ "code": "positionsKept", "message": "positions of existing nodes were kept" }
],
"errors": []
}
]
}
FieldMeaning
topology.actioncreate or update; id is the existing topology ID (null on create)
nodes / edges / weightPoliciesCounters create, update, remove, unchanged. remove is only non-zero in replace mode
catalogService.actioncreate, update (with the fields that change: description, icon, alertImpact), unchanged, or skip when the document has no catalogService
idsRegeneratedNode IDs that belonged to another topology and were replaced
warningslegacyConverted (with a report of remapped IDs, merged duplicates and dropped orphans), idsRegenerated, positionsKept
errorsEmpty on 200; on 400 the same codes are returned at the top level

A real import returns the previews plus one result per topology:

{
"status": "ok",
"dryRun": false,
"results": [
{
"service": "orders",
"topologyId": "3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01",
"action": "updated",
"counts": {
"nodes": { "create": 1, "update": 0, "remove": 0, "unchanged": 3 },
"edges": { "create": 1, "update": 0, "remove": 0, "unchanged": 2 },
"weightPolicies": { "create": 0, "update": 0, "remove": 0, "unchanged": 0 }
},
"catalogService": { "action": "skip", "fields": [] }
}
],
"previews": [ /* as in the dry run */ ]
}

action is created or updated. Each topology writes a service-topology-imported audit entry.

Errors​

400 with message: "invalid import" and an errors list in the same shape as the validation errors. In a bundle, path is prefixed with topologies[<index>]..

CodeReason
invalidJsonThe body is not valid JSON
invalidFormatThe JSON matches none of the accepted shapes
unsupportedSchemaVersionschemaVersion other than 2
emptyBundleA bundle without topologies
serviceMissingNo service name in the document and no ?service=
invalidOrientationtopology.orientation is not TB, LR or CI
tooLargeBody over 2 MB, or more than 5,000 nodes
Any validation codeThe document's graph, or the merged graph (after merge: prefix), breaks a rule

Other responses: 400 with a plain message when mode is missing or positions is invalid; 403 when the import would create a topology beyond the license limit.


Weight policies​

A weight policy sets the weight attribute of alerts that match its filters. Policies are evaluated when an alert is received:

  • Only active policies are considered, in creation order; the first one whose filters match sets attributes.weight and the evaluation stops.
  • An alert that already carries attributes.weight in the payload is left as it is: policies never overwrite a weight sent by the monitoring system.
  • The alert history records the policy execution with the policy as actor.

Policies can optionally be tied to a topology with topologyId.

Policy object​

{
"id": "5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a",
"name": "production-critical-weight",
"description": "Critical alerts in Production weigh 100",
"filters": { "clauses": [ /* query builder clauses */ ] },
"weight": 100,
"topologyId": "3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01",
"status": "active",
"createTime": "2026-10-09T08:15:00.000Z"
}
FieldMeaning
name / descriptionRequired
filtersAlert conditions: { "operator": "AND" | "OR", "clauses": [ { "key": "<alert field>", "op": "=", "value": … }, … ] }, the same format the web UI query builder produces (a clause may itself hold clauses for a nested group; operators include =, !=, <, >, LIKE, ~, IN, CONTAINS and their negations). Empty filters match every alert
weightNumeric weight written to attributes.weight
topologyIdOptional ID of the topology the policy belongs to. Must match an existing topology (400 otherwise); null for a global policy
statusactive or inactive. Only active policies are evaluated

Endpoints​

MethodEndpointPurpose
GET/api/service_topology_policyList policies. Filters: id, topologyId, name, description, status, weight, createTime; sort-by, page, page-size
POST/api/service_topology_policyCreate a policy (201)
GET/api/service_topology_policy/<policy_id>Read one policy
PUT/api/service_topology_policy/<policy_id>Update fields (name, description, filters, weight, topologyId, status)
DELETE/api/service_topology_policy/<policy_id>Delete a policy
POST/api/service_topology_policies/bulk/deleteBody { "policies": [ids] } — delete several policies
POST/api/service_topology_policies/bulk/updateBody { "policies": [ids], "status": "active" | "inactive" } — enable or disable several policies

Reading requires read:service_topologies; everything else write:service_topologies. Responses wrap the policy under the key topology ({ "status": "ok", "topology": { /* policy */ } }); the list returns topologies.

Examples​

Create a weight policy tied to the orders topology

curl -X POST "http://your-tobealert.com:9000/api/service_topology_policy" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"name": "production-critical-weight",
"description": "Critical alerts in Production weigh 100",
"weight": 100,
"topologyId": "3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01",
"filters": {
"operator": "AND",
"clauses": [
{
"key": "environment",
"op": "=",
"value": "Production"
},
{
"key": "severity",
"op": "=",
"value": "critical"
}
]
}
}'

Active policies of one topology

curl -X GET "http://your-tobealert.com:9000/api/service_topology_policy?topologyId=3f6a1c2e-8b4d-4e0f-9a71-5c2d7e8f9a01&status=active" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Disable a policy

curl -X PUT "http://your-tobealert.com:9000/api/service_topology_policy/5d4c3b2a-1f0e-4d9c-8b7a-6f5e4d3c2b1a" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"status": "inactive"
}'

See Weight policies for the web UI.


Migration of older topologies​

Topologies created before this release were stored as a single JSON tree (a service root with nested environment and resource children). When the backend starts on this release, a migration converts every tree-based topology to nodes and edges:

  • Node IDs are regenerated (UUIDs); the old default / e-<env> / r-<resource> IDs are not kept.
  • A resource that appeared under two parents in the tree becomes one node with two parents.
  • The old tree is kept internally as a backup for one release; the API only exposes the graph.
  • Each resource → resource edge is written to the resource graph with source topology.

Two cases need your attention and are reported in the backend log:

SituationEffect
Several topologies for the same serviceThey are reported; keep one and delete the others
A tree that breaks a validation rule — typically the same resource under two environmentsThe topology is not migrated; it is still served, converted on the fly (schemaVersion: 1), until you fix it in the editor and save, which stores it as version 2

Audit trail​

EventWhen
service-topology-createdPOST /api/service_topology
service-topology-updatedPUT /api/service_topology/<id>
service-topology-deletedDELETE /api/service_topology/<id>
service-topology-policy-createdPOST /api/service_topology_policy
service-topology-exportedGET /api/service_topology/<id>/export, and once per topology of a bundle export
service-topology-importedPOST /api/service_topology/import — once per topology created or updated (the message carries the mode and the action)

Assignments, suggestions, and bulk deletes do not write audit entries.

Best practices​

  1. Send the whole graph on PUT — a partial nodes list deletes what it does not contain. Read the topology first, modify, and send it back.
  2. Model shared resources as one node with several parents rather than duplicating the node; duplicates are rejected (duplicateResource).
  3. Keep one node per environment for resources that exist in several environments — the same label under Production and Staging is two different nodes.
  4. Use suggest before building by hand — it already knows the parents from resourceParent and the resource graph.
  5. Tie weight policies to a topology with topologyId when they only make sense for that service, and remember that the first matching active policy wins: order of creation matters when filters overlap.