Guides

05.08.2026

How to build a knowledge graph for oil & gas

Last updated: 05.08.2026

A resource is a thing in your world — a pump, a valve, a tank, a sensor. Resources are the nodes of your network. You connect them with relationships so the network mirrors how the equipment actually fits together.

This guide builds a small piece of an oil rig: a separator, its inlet valve, and the sensor that watches the valve.

What we are building

Rig A
  └── contains ── Separator A
                    └── contains ── Inlet valve
                                      └── monitors ── Pressure sensor

Four resources, three relationships. Once this works, the same steps scale to the rest of the rig.

Every resource needs at least one label. The label classifies the resource and gives the node its color in the graph, so the network stays readable as it grows.

If your equipment already has tag numbers, use them as the external ID — 20-PT-1234 is a better handle than pressure_transmitter_3, because it is the one your maintenance system already knows.

Build the network

Pick how you want to work. The console is the place to start if you are learning the model; the API and the SDKs are how you load a real plant.

1. Create the first resource

  1. Open Resources in the top menu.
  2. Click the create button.
  3. Fill in the form:
    • NameRig A
    • External IDrig_a
    • Label — click + and pick a label, for example ASSET.
    • Metadata — optional, skip it for now.
  4. Click Save.

2. Open it in the graph

Click Rig A in the resource list. It opens in the network view as a single node. This is your starting point — everything else hangs off it.

3. Add a connected resource

  1. Click the Rig A node.
  2. Choose the + action.
  3. Fill in the form:
    • Relationship — pick one, for example CONTAINS. This names how the two resources relate.
    • LabelASSET
    • NameSeparator A
  4. Click Save.

You now have two nodes with a line between them. The line is the relationship.

4. Keep going

Repeat step 3 for the rest:

  • Separator A contains Inlet valve
  • Pressure sensor monitors Inlet valve

Create every resource and every relationship in one request. Relations reference resources from the same request by their external ID.

curl -X POST "$BASE_URL/resources/create" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "nodes": [
      { "externalId": "rig_a",          "name": "Rig A",           "labels": ["ASSET"] },
      { "externalId": "separator_a",    "name": "Separator A",     "labels": ["ASSET"] },
      { "externalId": "inlet_valve",    "name": "Inlet valve",     "labels": ["ASSET"] },
      { "externalId": "pressure_sensor","name": "Pressure sensor", "labels": ["ASSET"] }
    ],
    "relations": [
      { "fromExternalId": "rig_a",           "toExternalId": "separator_a", "relationshipType": "contains" },
      { "fromExternalId": "separator_a",     "toExternalId": "inlet_valve", "relationshipType": "contains" },
      { "fromExternalId": "pressure_sensor", "toExternalId": "inlet_valve", "relationshipType": "monitors" }
    ]
  }'

The call is all-or-nothing. If one resource or relation fails validation, nothing is created — fix that entry and send the request again.

You get the same resources back with server-assigned numeric ids.

import datahub_sdk

nodes = [
    datahub_sdk.Resource(external_id="rig_a", name="Rig A", labels=["ASSET"]),
    datahub_sdk.Resource(external_id="separator_a", name="Separator A", labels=["ASSET"]),
    datahub_sdk.Resource(external_id="inlet_valve", name="Inlet valve", labels=["ASSET"]),
    datahub_sdk.Resource(external_id="pressure_sensor", name="Pressure sensor", labels=["ASSET"]),
]
edges = [
    datahub_sdk.RelForm.by_external_ids("rig_a", "separator_a", "contains"),
    datahub_sdk.RelForm.by_external_ids("separator_a", "inlet_valve", "contains"),
    datahub_sdk.RelForm.by_external_ids("pressure_sensor", "inlet_valve", "monitors"),
]

graph = client.resources.create(nodes, edges)
print(len(graph.nodes), "nodes,", len(graph.relations), "edges")
ResourceForm rig = new ResourceForm();
rig.setExternalId("rig_a");
rig.setName("Rig A");
rig.setLabels(List.of("ASSET"));        // at least one label is required

ResourceForm separator = new ResourceForm();
separator.setExternalId("separator_a");
separator.setName("Separator A");
separator.setLabels(List.of("ASSET"));

ResourceForm valve = new ResourceForm();
valve.setExternalId("inlet_valve");
valve.setName("Inlet valve");
valve.setLabels(List.of("ASSET"));

ResourceForm sensor = new ResourceForm();
sensor.setExternalId("pressure_sensor");
sensor.setName("Pressure sensor");
sensor.setLabels(List.of("ASSET"));

RelForm rigSeparator = new RelForm();
rigSeparator.setRelationshipType("contains");
rigSeparator.setFromExternalId("rig_a");
rigSeparator.setToExternalId("separator_a");

RelForm separatorValve = new RelForm();
separatorValve.setRelationshipType("contains");
separatorValve.setFromExternalId("separator_a");
separatorValve.setToExternalId("inlet_valve");

RelForm sensorValve = new RelForm();
sensorValve.setRelationshipType("monitors");
sensorValve.setFromExternalId("pressure_sensor");
sensorValve.setToExternalId("inlet_valve");

var graph = client.resources().create(
    List.of(rig, separator, valve, sensor),
    List.of(rigSeparator, separatorValve, sensorValve));

System.out.println(graph.getNodes().size() + " nodes, "
    + graph.getRelations().size() + " edges");
use dataplatform_rust_sdk::resources::Resource;
use dataplatform_rust_sdk::relations::RelForm;

let mut rig = Resource::new();
rig.external_id = "rig_a".into();
rig.name = "Rig A".into();
rig.labels = Some(vec!["ASSET".into()]);   // at least one label is required

let mut separator = Resource::new();
separator.external_id = "separator_a".into();
separator.name = "Separator A".into();
separator.labels = Some(vec!["ASSET".into()]);

let mut valve = Resource::new();
valve.external_id = "inlet_valve".into();
valve.name = "Inlet valve".into();
valve.labels = Some(vec!["ASSET".into()]);

let mut sensor = Resource::new();
sensor.external_id = "pressure_sensor".into();
sensor.name = "Pressure sensor".into();
sensor.labels = Some(vec!["ASSET".into()]);

let graph = api.resources.create(
    vec![rig, separator, valve, sensor],
    vec![
        RelForm::by_external_ids("rig_a", "separator_a", "contains"),
        RelForm::by_external_ids("separator_a", "inlet_valve", "contains"),
        RelForm::by_external_ids("pressure_sensor", "inlet_valve", "monitors"),
    ],
).await?;

Pick relationship names that describe reality — CONTAINS, MONITORS, POWERS, FEEDS. Someone reading the graph in a year should be able to tell what the line means without asking you.

Read the network back

Fetch a resource by its external ID and you get its outgoing edges with it.

Open Resources and click any resource in the list. It opens in the network view with everything it connects to.

Dim the rest of the graph and a single path stands out — that is how you follow a dependency by eye.

curl -X POST "$BASE_URL/resources/byids" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "items": [ { "externalId": "inlet_valve" } ] }'
valve = client.resources.by_ids(["inlet_valve"])[0]
for edge in valve.relations:
    print(edge.relationship_type, "->", edge.end)
var some = client.resources().byIds(List.of(
    IdCollection.createFromExternalId("inlet_valve")));

Resource valve = some.getItems().iterator().next();
valve.getRelations().forEach(edge ->
    System.out.println(edge.getType() + " -> " + edge.getEnd()));
use dataplatform_rust_sdk::generic::IdAndExtId;

let some = api.resources.by_ids(&vec![IdAndExtId::from_external_id("inlet_valve")]).await?;
if let Some(valve) = some.nodes().first() {
    for edge in valve.relations.iter().flatten() {
        println!("{} -> {}", edge.relationship_type, edge.end);
    }
}

Add context with metadata

Metadata is free key/value text on a resource. There is no schema to change and no migration to run — you add the keys you need.

Useful keys on an oil rig:

  • locationDeck 2, North
  • tag20-PT-1234
  • owner — the team responsible
  • last_calibration — a date
  • criticality — how bad it is when this fails

Add metadata for anything you will later want to search, filter, or group by.

Why this is worth the effort

Once the network is in place you can trace it. Dim the rest of the graph and a path stands out — which sensors feed a separator, what a shutdown would ripple into, which assets a contractor touched last week.

That is the difference between a list of equipment and a picture of the rig.

What to do next

  • Attach measurements to your resources — see the guide on browsing events and timeseries.
  • Link inspection files to the assets they cover — see the guide on files.