Applies to BloodHound Enterprise and CE

Schema

In this section, you will find all the information to create a JSON file that BloodHound can ingest and use to display your Nodes and Edges. The most up-to-date JSON Schema can always be found in our CE repository. Currently, the location of the node and edge schema files in our source code can be found here.

Ingesting Generic Formatted Data

File Requirements

Acceptable formats: .json, .zip You can mix file types in a single upload (e.g. Sharphound + Generic). Compressed ZIPs containing multiple file types are supported.

JSON Format

The standard Bloodhound UI upload screen now accepts files in a generic format. You can continue using it as before. At minimum, your JSON file should have these elements:
{
  "graph": {
    "nodes": [],
    "edges": []
  }
}
The nodes and edges must conform to our JSON Schema, see details below. The validation of the data occurs at upload time. When ingest completes, the generic data will be available via Cypher search ONLY. Generic data is not searchable via the pathfinding feature (yet). Entity Panels: clicking on a generic node or edge will only render the entity’s property bag. At this time there is no support for defining entity panels for generic entities.

Nodes

Property Rules

Properties must be primitive types or arrays of primitive types Nested objects and arrays of objects are not allowed Arrays must be homogeneous (e.g. all strings or all numbers) An array of kind labels for the node. The first element is treated as the node’s primary kind and is used to determine which icon to display in the graph UI. This primary kind is only used for visual representation and has no semantic significance for data processing.

Node JSON

{
    "title": "Generic Ingest Node",
    "description": "A node used in a generic graph ingestion system. Each node must have a unique identifier (`id`) and at least one kind describing its role or type. Nodes may also include a `properties` object containing custom attributes.",
    "type": "object",
    "properties": {
        "id": { "type": "string" },
        "properties": {
            "type": ["object", "null"],
            "description": "A key-value map of node attributes. Values must not be objects. If a value is an array, it must contain only primitive types (e.g., strings, numbers, booleans) and must be homogeneous (all items must be of the same type).",
            "additionalProperties": {
                "type": ["string", "number", "boolean", "array"],
                "items": {
                    "not": {
                        "type": "object"
                    }
                }
            }
        },
        "kinds": {
            "type": ["array"],
            "items": { "type": "string" },
            "maxItems": 3,
            "minItems": 1,
            "description": "An array of kind labels for the node. The first element is treated as the node's primary kind and is used to determine which icon to display in the graph UI. This primary kind is only used for visual representation and has no semantic significance for data processing."
        }
    },
    "required": ["id", "kinds"],
    "examples": [
        {
            "id": "user-1234",
            "kinds": ["Person"]
        },
        {
            "id": "device-5678",
            "properties": {
                "manufacturer": "Brandon Corp",
                "model": "4000x",
                "isActive": true,
                "rating": 43.50
            },
            "kinds": ["Device", "Asset"]
        },
        {
            "id": "location-001",
            "properties": null,
            "kinds": ["Location"]
        }
    ]
}

Edges

Edge JSON

{
    "title": "Generic Ingest Edge",
    "description": "Defines an edge between two nodes in a generic graph ingestion system. Each edge specifies a start and end node using either a unique identifier (id) or a name-based lookup. A kind is required to indicate the relationship type. Optional properties may include custom attributes. You may optionally constrain the start or end node to a specific kind using the kind field inside each reference.",
    "type": "object",
    "properties": {
        "start": {
            "type": "object",
            "properties": {
                "match_by": {
                    "type": "string",
                    "enum": ["id", "name"],
                    "default": "id",
                    "description": "Whether to match the start node by its unique object ID or by its name property."
                },
                "value": {
                    "type": "string",
                    "description": "The value used for matching — either an object ID or a name, depending on match_by."
                },
                "kind": {
                    "type": "string",
                    "description": "Optional kind filter; the referenced node must have this kind."
                }
            },
            "required": ["value"]
        },
        "end": {
            "type": "object",
            "properties": {
                "match_by": {
                    "type": "string",
                    "enum": ["id", "name"],
                    "default": "id",
                    "description": "Whether to match the start node by its unique object ID or by its name property."
                },
                "value": {
                    "type": "string",
                    "description": "The value used for matching — either an object ID or a name, depending on match_by."
                },
                "kind": {
                    "type": "string",
                    "description": "Optional kind filter; the referenced node must have this kind."
                }
            },
            "required": ["value"]
        },
        "kind": { "type": "string" },
        "properties": {
            "type": ["object", "null"],
            "description": "A key-value map of edge attributes. Values must not be objects. If a value is an array, it must contain only primitive types (e.g., strings, numbers, booleans) and must be homogeneous (all items must be of the same type).",
            "additionalProperties": {
                "type": ["string", "number", "boolean", "array"],
                "items": {
                    "not": {
                        "type": "object"
                    }
                }
            }
        }
    },
    "required": ["start", "end", "kind"],
    "examples": [
        {
            "start": {
                "match_by": "id",
                "value": "user-1234"
            },
            "end": {
                "match_by": "id",
                "value": "server-5678"
            },
            "kind": "has_session",
            "properties": {
                "timestamp": "2025-04-16T12:00:00Z",
                "duration_minutes": 45
            }
        },
        {
            "start": {
                "match_by": "name",
                "value": "alice",
                "kind": "User"
            },
            "end": {
                "match_by": "name",
                "value": "file-server-1",
                "kind": "Server"
            },
            "kind": "accessed_resource",
            "properties": {
                "via": "SMB",
                "sensitive": true
            }
        },
        {
            "start": {
                "value": "admin-1"
            },
            "end": {
                "value": "domain-controller-9"
            },
            "kind": "admin_to",
            "properties": {
                "reason": "elevated_permissions",
                "confirmed": false
            }
        },
        {
            "start": {
                "match_by": "name",
                "value": "Printer-007"
            },
            "end": {
                "match_by": "id",
                "value": "network-42"
            },
            "kind": "connected_to",
            "properties": null
        }
    ]
}

Optional Metadata Field

You can optionally include a metadata object at the top level of your JSON payload. This metadata currently supports a single field:
  • source_kind: a string that applies to all nodes in the file, used to attribute a source to ingested nodes (e.g. Github, Snowflake, MSSQL). This is useful for tracking where a node originated. We internally use this concept already for AD/Azure, using the labels “Base” and “AZBase” respectively.
Example:
{
  "metadata": {
    "source_kind": "GHBase"
  },
  "graph": {
    "nodes": [],
    "edges": []
  }
}
If present, the source_kind will be added to the kinds list of all nodes in the file during ingest. This feature is optional.

Minimal Working JSON

The following is the most simple JSON file we could come up with. You can use it as a starting point to build your own Open Graph.
{
  "graph": {
    "nodes": [
      {
        "id": "123",
        "kinds": [
          "Person",
          "Base"
        ],
        "properties": {
          "displayname": "bob",
          "property": "a",
          "objectid": "123",
          "name": "BOB"
        }
      },
      {
        "id": "234",
        "kinds": [
          "Person",
          "Base"
        ],
        "properties": {
          "displayname": "alice",
          "property": "b",
          "objectid": "234",
          "name": "ALICE"
        }
      }
    ],
    "edges": [
      {
        "kind": "Knows",
        "start": {
          "value": "123",
          "match_by": "id"
        },
        "end": {
          "value": "234",
          "match_by": "id"
        }
      }
    ]
  }
}
To test the ingestion in your BloodHound instance, navigate to ExploreCypher. Enter the following query and hit Run:
match p=()-[:Knows]-()
return p
You should get something that looks like this: BOB->Knows->Alice