Topology API
These endpoints manage the topology graph (business services, applications, components, hosts, databases, clusters) and the metric bindings that connect data to it. The concepts are in Topology & CMDB Integration.
There are two ways in:
POST /v1/topology— the machine-to-machine bulk push for CMDB sync. Idempotent, transactional, external-ID addressed. This is what most integrations use./api/topology/...— fine-grained CRUD used by the console.
Permissions: reads require CanViewAlerts; all writes (including the push) require
topology write permission (CanManageRules).
Node & edge types
nodeType | Meaning |
|---|---|
BusinessService | A customer- or business-facing service (the unit of impact). |
Application | The logical representation of a business application. |
Component | An operational deployment unit — a runtime boundary that emits telemetry (a farm, tier, cluster, …); not necessarily a single microservice. |
Host | A machine — server, VM, or node. |
Database | A data-tier dependency. |
Cluster | A homogeneous redundancy group ("farm") of like members. |
Other is not accepted (it is a reserved client-side rendering fallback); unknown node
types are rejected.
edgeType | Meaning |
|---|---|
contains | Hierarchical containment (the browsable tree), parent → child. |
depends_on | Runtime dependency, source → target. |
runs_on | Hosting/placement, workload → infrastructure (Host/Cluster). |
routes_to | Load-balanced distribution toward redundant members. |
POST /v1/topology — CMDB bulk push
Synchronous, idempotent, transactional upsert. Clients reference everything by their own
stable externalId; Lumetry numeric IDs are never required.
Request
{
"source": "servicenow",
"importId": "servicenow-2026-06-02T09:00:00Z",
"nodes": [
{
"externalId": "servicenow:svc:mobile-banking",
"nodeType": "BusinessService",
"displayName": "Mobile Banking",
"environment": "production",
"ownerTeam": "channels-platform",
"metadata": { "cmdbClass": "cmdb_ci_service", "sysId": "2cdd4c4f" }
},
{
"externalId": "servicenow:app:mobile-application",
"nodeType": "Application",
"displayName": "Mobile Application",
"environment": "production"
},
{
"externalId": "servicenow:cmp:mobile-frontend",
"nodeType": "Component",
"displayName": "Mobile Frontend",
"environment": "production"
},
{
"externalId": "servicenow:host:host01",
"nodeType": "Host",
"displayName": "host01",
"environment": "production"
}
],
"edges": [
{ "sourceExternalId": "servicenow:svc:mobile-banking", "targetExternalId": "servicenow:app:mobile-application", "edgeType": "contains" },
{ "sourceExternalId": "servicenow:app:mobile-application", "targetExternalId": "servicenow:cmp:mobile-frontend", "edgeType": "contains" },
{ "sourceExternalId": "servicenow:cmp:mobile-frontend", "targetExternalId": "servicenow:host:host01", "edgeType": "runs_on" }
],
"metricBindings": [
{ "metricId": "mobile.frontend.latency.p95", "nodeExternalId": "servicenow:cmp:mobile-frontend", "bindingType": "emits" }
]
}
| Field | Required | Meaning |
|---|---|---|
source | yes | Originating CMDB name (servicenow, bmc-helix, device42, custom-cmdb, …). |
importId | no | Caller-generated run identifier for traceability. |
nodes[] | no | Nodes to create/update. externalId, nodeType, displayName required; environment, ownerTeam, metadata (JSON object) optional. |
edges[] | no | Relationships. Referenced nodes must exist already or be in the same payload. |
metricBindings[] | no | Metric-to-node bindings. metricId must match an existing metric key; the push does not create metric definitions. |
Modeling tips
- A
Componentmayruns_onseveralHosts, and oneHostmay carry severalComponents — send oneruns_onedge per pair. - Model hosting as
Component/Application/Databaseruns_onHost/Cluster, never the reverse. Infrastructure cannot depend "upward" on applications or components. - For a farm, send
Cluster contains Hostfor the members androutes_tofor the load-balanced front.
Behavior
- Upsert keys: nodes by
externalId; edges by(sourceExternalId, targetExternalId, edgeType); bindings by(metricId, nodeExternalId). - Idempotent: re-sending the same payload creates no duplicates.
- Upsert-only: it does not prune nodes/edges/bindings missing from a later payload.
- Transactional: the entire payload is validated and applied in one transaction; on any validation error, nothing is written.
Response 200
{
"source": "servicenow",
"importId": "servicenow-2026-06-02T09:00:00Z",
"importedAt": "2026-06-02T09:00:01.318Z",
"nodesReceived": 4, "nodesCreated": 4, "nodesUpdated": 0, "nodesUnchanged": 0,
"edgesReceived": 3, "edgesCreated": 3, "edgesUpdated": 0, "edgesUnchanged": 0,
"metricBindingsReceived": 1, "metricBindingsCreated": 1, "metricBindingsUpdated": 0, "metricBindingsUnchanged": 0
}
The *Received/Created/Updated/Unchanged counts let a sync job verify exactly what changed.
Validation error 400 (nothing written)
{
"message": "Topology import payload validation failed.",
"errors": [
{ "path": "edges[0]", "message": "Edge not allowed: a 'Host' cannot 'runs_on' a 'Component'." }
]
}
Common causes: unknown nodeType/edgeType; duplicate external IDs in one request; an edge
or binding referencing an unknown node; invalid containment/hosting/dependency direction;
metadata that is not a JSON object.
curl -X POST http://localhost:8080/v1/topology \
-H "Authorization: Bearer ${LUMETRY_API_TOKEN}" \
-H "Content-Type: application/json" \
-d @cmdb-topology.json
Console CRUD
Nodes
| Method & path | Purpose |
|---|---|
GET /api/topology/nodes | List nodes. |
GET /api/topology/nodes/{id} | Get a node by integer id. |
POST /api/topology/nodes | Create. Body: externalId, nodeType, displayName, environment (default production), optional ownerTeam, metadata (JSON string, default {}). |
PUT /api/topology/nodes/{id} | Update. |
DELETE /api/topology/nodes/{id} | Soft-delete the node and its active edges/bindings. |
A returned node (TopologyNodeDto) includes id, externalId, nodeType, displayName,
environment, ownerTeam, metadata, createdAt, updatedAt.
Tree browsing (lazy)
| Method & path | Purpose |
|---|---|
GET /api/topology/tree?nodeType=&environment= | The containment forest. Filters optional; omitted filters return the full forest. |
GET /api/topology/tree/children?parentId=&nodeType=&environment= | One containment level for lazy expansion. Omitting parentId returns containment roots. |
Each tree node reports hasChildren, outboundDependencyCount, and
inboundDependencyCount so the UI can render expandable rows without loading everything.
Dependency counts are indicators — runtime edges are not tree children.
Dependencies & edges
| Method & path | Purpose |
|---|---|
GET /api/topology/nodes/{id}/dependencies | The runtime relationship edges for a node, surfaced on demand. |
POST /api/topology/edges | Create an edge. Body: sourceNodeId, targetNodeId, edgeType, optional metadata. Direction and containment are validated. |
DELETE /api/topology/edges/{id} | Soft-delete an edge. |
Bindings
| Method & path | Purpose |
|---|---|
GET /api/topology/bindings?metricId={metricKey} | Bindings for a metric. |
POST /api/topology/bindings | Bind a metric to a node. Body: metricId (metric key), nodeId, optional bindingType (default emits). |
Bindings are what resolve a metric to a service/CI, populating the related-service/CI context on alerts and incidents.