Skip to main content
Version: 1.3

Resource graph

Overview​

The resource graph records which resource is the parent of which — a virtual machine on a hypervisor, a hypervisor in a cluster, a database used by an application. Relations are scoped by environment: an alert's environment and resource identify a resource, and an empty environment is allowed. Environments are compared exactly, so "" and "Production" are different resources even when the name matches.

A resource may have several parents (a database shared by two applications), and one parent may have any number of children. The graph is shared by alert events, policies, service topologies, and CMDB imports. Each relation remembers who declared it:

sourceDeclared by
manualThis API (POST /api/resources/relations, bulk)
eventAn alert payload with resourceParent / resourceChildren — see Sending alerts
cmdbA CMDB import: flat records with a parent column, loaded through the API or the web UI. sourceRef is the name given to the source (default cmdb)
topologyA saved service topology: every resource → resource edge of the graph you draw. sourceRef is the topology ID
policyThe Resource relation policy modifier with source Value — see Modifiers and tools — Resource relation. sourceRef is the policy ID

When the same relation (environment, resource, parent) arrives from several sources, the highest-ranked source wins and is kept on the relation: manual > event > cmdb > topology > policy. A relation you add manually is never overwritten by an alert payload, a topology, or a policy. Relations come from alerts, from service topologies, from policies, from CMDB imports, and from this API.

Policies also read the graph: the Resource relation modifier can copy a resource's main parent into the alert (resourceParent), and Mark as effect can take the cause from an open alert on a parent resource — see Cause and effect alerts — Cause on a parent resource.

The alert detail (GET /api/alert/<alert_id>) already includes the parents, children, and ancestors of the alert's resource — see Querying alerts — Resource relation fields. The alert details drawer shows the same information.

Authentication and permissions​

All endpoints take an API key:

Authorization: Key YOUR_API_KEY_HERE
ActionScope
Read the graph or list relationsread:service_topologies
Create, bulk-load, or delete relationswrite:service_topologies
Import CMDB recordswrite:service_topologies (relations get source cmdb without the admin scope)
Download the CMDB import templateread:service_topologies
Write a relation with a source other than manualadmin:service_topologies (or admin) in addition to write

See API keys for scopes.

Relation object​

Every relation returned by the API has the same shape:

{
"id": "6f1c0b2e-4a7d-4f2e-9d3a-1b2c3d4e5f60",
"environment": "Production",
"resource": "vm-7",
"parent": "esx-01",
"source": "event",
"sourceRef": null,
"createTime": "2026-10-09T08:15:00.000Z",
"updateTime": "2026-10-09T08:15:00.000Z"
}
FieldMeaning
idRelation ID, used by DELETE /api/resources/relations/<id>
environmentEnvironment of both resources ("" when none)
resourceThe child
parentThe parent
sourceWho declared the relation (see table above)
sourceRefOptional free text identifying the origin (an import batch, a ticket); set automatically to the topology ID for topology relations and to the policy ID for policy relations
createTime / updateTimeWhen the relation was first stored and last confirmed or changed

Resource and parent names are trimmed. A relation is unique per (environment, resource, parent): sending it again updates the existing row instead of creating a second one.


Get the graph of a resource​

GET /api/resources/graph?resource=<name>&environment=<env>
Authorization: Key YOUR_API_KEY

Returns the direct parents and children of a resource and, depending on direction, its ancestors (parents of parents) and descendants.

Query parameters​

ParameterRequiredDefaultDescription
resourceYes—Resource name
environmentNo""Environment of the resource. Omit it for resources with no environment
directionNobothancestors, descendants, or both
depthNo10How many levels to walk, 1–20

Example​

Read the ancestors of vm-7 in Production

curl -X GET "http://your-tobealert.com:9000/api/resources/graph?resource=vm-7&environment=Production&direction=ancestors" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Response​

{
"status": "ok",
"environment": "Production",
"resource": "vm-7",
"parents": [
{
"id": "6f1c0b2e-4a7d-4f2e-9d3a-1b2c3d4e5f60",
"environment": "Production",
"resource": "vm-7",
"parent": "esx-01",
"source": "event",
"sourceRef": null,
"createTime": "2026-10-09T08:15:00.000Z",
"updateTime": "2026-10-09T08:15:00.000Z"
}
],
"children": [],
"ancestors": [
{ "resource": "esx-01", "depth": 1, "path": ["vm-7", "esx-01"] },
{ "resource": "cluster-a", "depth": 2, "path": ["vm-7", "esx-01", "cluster-a"] }
]
}
FieldMeaning
parents / childrenDirect relations, as relation objects. Always present
ancestorsResources above this one, with depth (1 = direct parent) and the path walked from the resource. Present with direction=ancestors or both
descendantsResources below this one, same shape. Present with direction=descendants or both

Traversals are cycle-safe: a loop in the graph (for example a → b → a, which the storage tolerates) is walked once and stops; the walk also stops at depth.

Errors​

CodeReason
400resource missing, invalid direction, or depth outside 1–20

List relations​

GET /api/resources/relations
Authorization: Key YOUR_API_KEY

Returns a paginated list of relations with optional filters.

Query parameters​

ParameterDescriptionExample
environmentFilter by environment?environment=Production
resourceFilter by child?resource=vm-7
parentFilter by parent?parent=esx-01
sourceFilter by source?source=manual
sourceRefFilter by source reference?sourceRef=cmdb-2026-10
sort-bySort column; prefix with - for descending. Columns: environment, resource, parent, source, sourceRef, createTime, updateTime?sort-by=-updateTime
page / page-sizePagination?page=2&page-size=100

Example​

List everything that hangs from esx-01 in Production

curl -X GET "http://your-tobealert.com:9000/api/resources/relations?parent=esx-01&environment=Production&sort-by=-updateTime" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Response​

{
"status": "ok",
"relations": [ /* array of relation objects */ ],
"total": 2,
"page": 1,
"pageSize": 50,
"pages": 1,
"more": false
}

Create a relation​

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

Adds one relation. Requires write:service_topologies. The call is idempotent: repeating it for the same (environment, resource, parent) updates the existing relation (its source, sourceRef, and updateTime) instead of creating a duplicate.

Request body​

FieldTypeRequiredDefaultDescription
resourceStringYes—The child
parentStringYes—The parent. Must differ from resource
environmentStringNo""Environment of both resources
sourceStringNomanualOne of manual, event, cmdb, topology, policy. Anything other than manual requires admin:service_topologies
sourceRefStringNonullFree text identifying the origin

Example​

Declare orders-app as a parent of orders-db (source manual)

curl -X POST "http://your-tobealert.com:9000/api/resources/relations" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"environment": "Production",
"resource": "orders-db",
"parent": "orders-app",
"sourceRef": "CHG-1042"
}'

Response​

201 Created:

{
"status": "ok",
"relation": {
"id": "0c9f2a61-7b3e-4d1c-a8e5-2f6b7c8d9e01",
"environment": "Production",
"resource": "orders-db",
"parent": "orders-app",
"source": "manual",
"sourceRef": "CHG-1042",
"createTime": "2026-10-09T09:00:00.000Z",
"updateTime": "2026-10-09T09:00:00.000Z"
}
}

Errors​

CodeReason
400resource or parent missing, resource equal to parent (a resource cannot be its own parent), or unknown source
403source other than manual without admin:service_topologies

Bulk-load relations​

POST /api/resources/relations/bulk
Content-Type: application/json
Authorization: Key YOUR_API_KEY

Stores up to 10,000 relations in one request (for example an export from a CMDB). Requires write:service_topologies.

Request body​

FieldTypeRequiredDescription
relationsArrayYesNon-empty list of {resource, parent, environment?}. An item without environment uses the top-level one
environmentStringNoDefault environment for items that do not set their own ("" if omitted)
sourceStringNoSource for the whole batch, default manual. Other values require admin:service_topologies
sourceRefStringNoReference stored on every relation of the batch

The whole batch is validated before anything is written: one invalid item (missing resource or parent, or resource equal to parent) rejects the request with 400 and the item index.

Example​

Load a small hierarchy; orders-db gets two parents

curl -X POST "http://your-tobealert.com:9000/api/resources/relations/bulk" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"environment": "Production",
"sourceRef": "cmdb-2026-10",
"relations": [
{
"resource": "esx-01",
"parent": "cluster-a"
},
{
"resource": "vm-7",
"parent": "esx-01"
},
{
"resource": "vm-8",
"parent": "esx-01"
},
{
"resource": "orders-db",
"parent": "orders-app"
},
{
"resource": "orders-db",
"parent": "billing-app"
}
]
}'

Response​

{
"status": "ok",
"inserted": 4,
"updated": 1
}

inserted counts new relations; updated counts relations that already existed and were refreshed.

Errors​

CodeReason
400relations empty or not a list, more than 10,000 items, an item that is not an object, an item without resource / parent, a resource that is its own parent, or unknown source
403source other than manual without admin:service_topologies

Delete a relation by ID​

DELETE /api/resources/relations/<relation_id>
Authorization: Key YOUR_API_KEY

Removes one relation. Requires write:service_topologies.

Delete a single relation

curl -X DELETE "http://your-tobealert.com:9000/api/resources/relations/0c9f2a61-7b3e-4d1c-a8e5-2f6b7c8d9e01" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Response​

{ "status": "ok" }

Errors​

CodeReason
404Relation not found

Delete the relations of a resource​

DELETE /api/resources/relations?resource=<name>&environment=<env>
Authorization: Key YOUR_API_KEY

Removes every relation in which the resource is the child, narrowed by the optional filters. Requires write:service_topologies.

Query parameters​

ParameterRequiredDescription
resourceYesThe child whose relations are removed
environmentNoEnvironment ("" if omitted)
parentNoOnly the relation to this parent
sourceNoOnly relations with this source
sourceRefNoOnly relations with this reference

Example​

Remove the manual parents of orders-db in Production

curl -X DELETE "http://your-tobealert.com:9000/api/resources/relations?resource=orders-db&environment=Production&source=manual" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Response​

{
"status": "ok",
"deleted": 2
}

Deleting a relation does not change the resourceParent stored on existing alerts; it only removes the row from the graph. Child relations of a deleted parent are kept (the parent's own children still point to it).


Import from a CMDB​

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

Loads a flat list of resources — one record per resource with its environment, its parent and the services it belongs to — into the resource graph and into one service topology per service. Records carry no IDs, edges or positions, so the same CMDB extract can be imported again and again: an unchanged file changes nothing. Requires write:service_topologies (relations are written with source cmdb without admin:service_topologies).

The web UI offers the same import in the Import dialog of the Services tab, format CMDB (simple) — see Importing from a CMDB for the full semantics.

Query parameters​

ParameterRequiredDefaultDescription
formatNoby contentjson or csv. Without it: the file extension of a file upload, text/csv as Content-Type, otherwise a body starting with { or [ is JSON and anything else CSV
modeNomergemerge: add and update, never remove. replace: also remove the relations and topology nodes of this source that are not in the file (nodes drawn by hand or imported from another source are kept)
dryRunNo01 / true: plan and return the counters, write nothing
sourceNocmdbName of the CMDB (at most 100 characters). Stored as sourceRef on every relation and as params.cmdbSource on every topology node, and used to scope replace
environmentNo""Environment for records without one (warning environmentDefaulted)

Request body​

JSON (a list, or { "items": [ ... ] }), raw CSV (Content-Type: text/csv), or multipart/form-data with a file part. At most 50,000 records (tooManyRecords) and 20 MB (bodyTooLarge).

FieldRequiredDescription
resourceYesResource name (≤ 255 characters, trimmed)
environmentNoEnvironment (≤ 255). Empty → ?environment=, otherwise "". environment + resource identify the record
parentNoParent resource in the same environment; need not be in the file (unknownParent)
servicesNoJSON list, or a string separated by ; or |; service (singular) is also read. Empty → the record only feeds the graph
typeNo≤ 100 characters, stored as params.type of the node
attributesNoObject of strings, stored as params.attributes. In CSV: columns attr.<key>

CSV: UTF-8 (BOM tolerated), header row with at least resource, , or ; as separator (detected), empty lines skipped, unknown columns ignored with unknownColumn.

Template​

GET /api/resources/import/template?format=json|csv

Returns a six-record example (cmdb-import-template.json / .csv) with two environments, a parent that is not in the file and a record without services. Requires read:service_topologies.

What is written​

  • Relations: resource → parent per record with a parent, source cmdb, sourceRef = source. A resource keeps one parent per source: a different parent replaces the previous relation (update). A relation that already exists from a higher-ranked source (manual, event) keeps its source.
  • Catalog: a service entry per distinct service name, created when missing (serviceCreated); existing entries are not modified.
  • Topologies: per service, created when missing (topologyCreated): one environment node per environment, one resource node per record (params.cmdbSource, type, attributes), edges service → environment, parent → resource when the parent is in the service (or already a node), environment → resource otherwise (parentNotInService). Existing nodes keep their positions; new ones are laid out automatically. The resulting graph goes through the validation rules.

Example​

Dry run of a CMDB extract named servicenow

curl -X POST "http://your-tobealert.com:9000/api/resources/import?mode=replace&dryRun=1&source=servicenow" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"items": [
{
"resource": "host-3",
"environment": "prod",
"services": [
"payments"
],
"type": "hypervisor",
"attributes": {
"site": "madrid-1"
}
},
{
"resource": "vm-7",
"environment": "prod",
"parent": "host-3",
"services": [
"payments"
],
"type": "vm"
},
{
"resource": "db-01",
"environment": "prod",
"parent": "vm-7",
"services": [
"payments",
"billing"
],
"type": "database"
},
{
"resource": "api-1",
"environment": "prod",
"parent": "k8s-cluster",
"services": [
"payments"
],
"type": "deployment"
},
{
"resource": "db-01",
"environment": "staging",
"services": [
"payments"
],
"type": "database"
},
{
"resource": "backup-nas",
"environment": "prod",
"services": [],
"type": "storage"
}
]
}'

The same records as CSV:

curl -X POST "http://your-tobealert.com:9000/api/resources/import?mode=replace&source=servicenow" \
-H "Authorization: Key YOUR_API_KEY" \
-H "Content-Type: text/csv" \
--data-binary @cmdb-export.csv

Response​

The same document for a dry run and a real import; applied says which one it was. topologyId is null in a dry run for topologies that would be created.

{
"status": "ok",
"applied": false,
"source": "servicenow",
"mode": "replace",
"records": { "total": 6, "withServices": 5, "graphOnly": 1 },
"relations": { "create": 3, "update": 0, "remove": 0, "unchanged": 0 },
"topologies": [
{
"service": "billing",
"action": "create",
"topologyId": null,
"nodes": { "create": 3, "update": 0, "remove": 0, "unchanged": 0 },
"edges": { "create": 2, "update": 0, "remove": 0, "unchanged": 0 },
"errors": []
},
{
"service": "payments",
"action": "create",
"topologyId": null,
"nodes": { "create": 8, "update": 0, "remove": 0, "unchanged": 0 },
"edges": { "create": 7, "update": 0, "remove": 0, "unchanged": 0 },
"errors": []
}
],
"catalogServices": { "create": 2, "unchanged": 0 },
"warnings": [
{ "code": "unknownParent", "message": "parent \"k8s-cluster\" of \"api-1\" is not in the import; the relation is created anyway", "row": 3, "params": { "resource": "api-1", "parent": "k8s-cluster" } },
{ "code": "serviceCreated", "message": "catalog service \"billing\" will be created", "row": null, "params": { "service": "billing" } },
{ "code": "topologyCreated", "message": "topology for service \"billing\" will be created", "row": null, "params": { "service": "billing" } },
{ "code": "parentNotInService", "message": "parent \"vm-7\" of \"db-01\" is not in service \"billing\"; the resource hangs from the environment", "row": 2, "params": { "resource": "db-01", "parent": "vm-7", "service": "billing" } },
{ "code": "serviceCreated", "message": "catalog service \"payments\" will be created", "row": null, "params": { "service": "payments" } },
{ "code": "topologyCreated", "message": "topology for service \"payments\" will be created", "row": null, "params": { "service": "payments" } },
{ "code": "parentNotInService", "message": "parent \"k8s-cluster\" of \"api-1\" is not in service \"payments\"; the resource hangs from the environment", "row": 3, "params": { "resource": "api-1", "parent": "k8s-cluster", "service": "payments" } }
],
"errors": []
}
FieldMeaning
recordstotal records read; withServices feed topologies; graphOnly only feed the graph
relationsGraph relations of this source to create, update (parent changed), remove (replace only) or leave unchanged
topologies[]One entry per service: action (create / update), topologyId, and node / edge counters
catalogServicesCatalog entries to create or leave unchanged
warnings[]code, message, row (0-based index of the record; null for warnings about a service, 0 for CSV header warnings) and params

Re-importing the same file yields unchanged everywhere. A real import writes a resources-cmdb-imported audit entry.

Errors​

400 with message: "invalid CMDB import" and an errors list (code, path, message); nothing is written when any record is invalid.

CodeReason
missingResourceA record without resource or that is not an object
duplicateRecordThe same environment + resource twice
selfParentparent equal to resource
cycleThe parents of one environment form a loop
valueTooLongA name over 255 characters, or type over 100
invalidCsvHeaderEmpty CSV or no resource column
invalidJsonNot valid JSON / UTF-8, or not a list of records
tooManyRecords / bodyTooLargeMore than 50,000 records / more than 20 MB
Any topology validation codeThe resulting topology of a service would be invalid; path is prefixed with topologies[<service>].

Warnings (unknownParent, parentNotInService, unknownColumn, environmentDefaulted, serviceCreated, topologyCreated) do not stop the import.


Audit trail​

Writes through this API are written to the administrator audit trail with the type resource-relation:

EventWhen
resource-relation-createdPOST /api/resources/relations
resource-relations-bulkPOST /api/resources/relations/bulk (one entry per request, with the inserted / updated counts)
resource-relation-deletedEither DELETE endpoint (by ID, or by resource with the number of rows removed)

A CMDB import writes a resources-cmdb-imported entry (type resources) with the mode, the source name and the number of records; dry runs write nothing.

Relations declared by alert payloads (source: event) or by policies (source: policy) do not create audit entries; the alert and its history are the record.

Putting it together​

  1. Declare a parent from the alert — the monitoring system already knows the hierarchy, so send it with the event. This stores resourceParent on the alert and a relation with source event:

Alert that declares its parent

curl -X POST "http://your-tobealert.com:9000/api/alert" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"resource": "vm-7",
"event": "DiskFull",
"environment": "Production",
"severity": "major",
"resourceParent": "esx-01"
}'
  1. Add a relation by hand — something the events never mention, such as the application that depends on a database:

Manual relation (source manual)

curl -X POST "http://your-tobealert.com:9000/api/resources/relations" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY" \
-d '{
"environment": "Production",
"resource": "orders-db",
"parent": "orders-app"
}'
  1. Read the ancestors of a resource when triaging — where does this alert sit in the hierarchy?

Ancestors of vm-7, five levels at most

curl -X GET "http://your-tobealert.com:9000/api/resources/graph?resource=vm-7&environment=Production&direction=ancestors&depth=5" \
-H "Content-Type: application/json" \
-H "Authorization: Key YOUR_API_KEY"

Best practices​

  1. Use the same environment everywhere — a relation declared in Production is invisible to alerts whose environment is empty or spelled differently.
  2. Let events declare what they know (resourceParent, resourceChildren), let a Resource relation policy fill the gaps from attributes, and reserve manual relations for what monitoring cannot see; manual relations rank highest and will not be overwritten.
  3. Set sourceRef on bulk loads so you can list or delete one import (?sourceRef=…) without touching relations from other sources. For a CMDB, prefer the CMDB import: it is idempotent, replace mode removes what left the CMDB, and it keeps the service topologies in step with the graph.
  4. Avoid loops — storage tolerates a → b → a, but ancestors and descendants stop at the first repeated resource, so the output is less useful.
  5. Keep names stable — relations match on exact, trimmed strings; renaming a host in monitoring leaves its old relations behind until you delete them.