curl --request POST \
--url https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"custom_types": {
"KindA": {
"icon": {
"type": "font-awesome",
"name": "house",
"color": "#FFFFFF"
}
},
"KindB": {
"icon": {
"type": "font-awesome",
"name": "coffee",
"color": "#000000"
}
}
}
}
'import requests
url = "https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes"
payload = { "custom_types": {
"KindA": { "icon": {
"type": "font-awesome",
"name": "house",
"color": "#FFFFFF"
} },
"KindB": { "icon": {
"type": "font-awesome",
"name": "coffee",
"color": "#000000"
} }
} }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
custom_types: {
KindA: {icon: {type: 'font-awesome', name: 'house', color: '#FFFFFF'}},
KindB: {icon: {type: 'font-awesome', name: 'coffee', color: '#000000'}}
}
})
};
fetch('https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'custom_types' => [
'KindA' => [
'icon' => [
'type' => 'font-awesome',
'name' => 'house',
'color' => '#FFFFFF'
]
],
'KindB' => [
'icon' => [
'type' => 'font-awesome',
'name' => 'coffee',
'color' => '#000000'
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes"
payload := strings.NewReader("{\n \"custom_types\": {\n \"KindA\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"house\",\n \"color\": \"#FFFFFF\"\n }\n },\n \"KindB\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"coffee\",\n \"color\": \"#000000\"\n }\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"custom_types\": {\n \"KindA\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"house\",\n \"color\": \"#FFFFFF\"\n }\n },\n \"KindB\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"coffee\",\n \"color\": \"#000000\"\n }\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"custom_types\": {\n \"KindA\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"house\",\n \"color\": \"#FFFFFF\"\n }\n },\n \"KindB\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"coffee\",\n \"color\": \"#000000\"\n }\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"data": [
{
"id": 123,
"kindName": "<string>",
"config": {
"icon": {
"type": "<string>",
"name": "<string>",
"color": "<string>"
}
}
}
]
}{
"http_status": 400,
"timestamp": "2024-02-19T19:27:43.866Z",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"errors": [
{
"context": "clients",
"message": "The JSON payload could not be unmarshalled."
}
]
}{
"http_status": 401,
"timestamp": "2024-02-19T19:27:43.866Z",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"errors": [
{
"context": "login",
"message": "Unauthorized"
}
]
}{
"http_status": 409,
"timestamp": "2024-02-19T19:27:43.866Z",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"errors": [
{
"context": "customnodes",
"message": "Conflict: duplicate kind name"
}
]
}{
"http_status": 500,
"timestamp": "2024-02-19T19:27:43.866Z",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"errors": [
{
"context": "clients",
"message": "The request could not be handled due to an unexpected database error."
}
]
}Create custom nodes
This endpoint registers new node kinds along with optional display metadata such as icons and colors. The provided configuration controls how nodes of the specified kinds are interpreted and rendered in the UI. Only free, solid-style Font Awesome icons are supported. Hex color codes must follow the format #RGB or #RRGGBB. If no valid icon or color is provided, the system will fall back to a question mark icon and a white background color. Batch input is supported; you may submit multiple configuration objects in a single request.
curl --request POST \
--url https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '
{
"custom_types": {
"KindA": {
"icon": {
"type": "font-awesome",
"name": "house",
"color": "#FFFFFF"
}
},
"KindB": {
"icon": {
"type": "font-awesome",
"name": "coffee",
"color": "#000000"
}
}
}
}
'import requests
url = "https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes"
payload = { "custom_types": {
"KindA": { "icon": {
"type": "font-awesome",
"name": "house",
"color": "#FFFFFF"
} },
"KindB": { "icon": {
"type": "font-awesome",
"name": "coffee",
"color": "#000000"
} }
} }
headers = {
"Authorization": "Bearer <token>",
"Content-Type": "application/json"
}
response = requests.post(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'POST',
headers: {Authorization: 'Bearer <token>', 'Content-Type': 'application/json'},
body: JSON.stringify({
custom_types: {
KindA: {icon: {type: 'font-awesome', name: 'house', color: '#FFFFFF'}},
KindB: {icon: {type: 'font-awesome', name: 'coffee', color: '#000000'}}
}
})
};
fetch('https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "POST",
CURLOPT_POSTFIELDS => json_encode([
'custom_types' => [
'KindA' => [
'icon' => [
'type' => 'font-awesome',
'name' => 'house',
'color' => '#FFFFFF'
]
],
'KindB' => [
'icon' => [
'type' => 'font-awesome',
'name' => 'coffee',
'color' => '#000000'
]
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>",
"Content-Type: application/json"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"strings"
"net/http"
"io"
)
func main() {
url := "https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes"
payload := strings.NewReader("{\n \"custom_types\": {\n \"KindA\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"house\",\n \"color\": \"#FFFFFF\"\n }\n },\n \"KindB\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"coffee\",\n \"color\": \"#000000\"\n }\n }\n }\n}")
req, _ := http.NewRequest("POST", url, payload)
req.Header.Add("Authorization", "Bearer <token>")
req.Header.Add("Content-Type", "application/json")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.post("https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes")
.header("Authorization", "Bearer <token>")
.header("Content-Type", "application/json")
.body("{\n \"custom_types\": {\n \"KindA\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"house\",\n \"color\": \"#FFFFFF\"\n }\n },\n \"KindB\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"coffee\",\n \"color\": \"#000000\"\n }\n }\n }\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://your-tenant.bloodhoundenterprise.io/api/v2/custom-nodes")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Post.new(url)
request["Authorization"] = 'Bearer <token>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"custom_types\": {\n \"KindA\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"house\",\n \"color\": \"#FFFFFF\"\n }\n },\n \"KindB\": {\n \"icon\": {\n \"type\": \"font-awesome\",\n \"name\": \"coffee\",\n \"color\": \"#000000\"\n }\n }\n }\n}"
response = http.request(request)
puts response.read_body{
"data": [
{
"id": 123,
"kindName": "<string>",
"config": {
"icon": {
"type": "<string>",
"name": "<string>",
"color": "<string>"
}
}
}
]
}{
"http_status": 400,
"timestamp": "2024-02-19T19:27:43.866Z",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"errors": [
{
"context": "clients",
"message": "The JSON payload could not be unmarshalled."
}
]
}{
"http_status": 401,
"timestamp": "2024-02-19T19:27:43.866Z",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"errors": [
{
"context": "login",
"message": "Unauthorized"
}
]
}{
"http_status": 409,
"timestamp": "2024-02-19T19:27:43.866Z",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"errors": [
{
"context": "customnodes",
"message": "Conflict: duplicate kind name"
}
]
}{
"http_status": 500,
"timestamp": "2024-02-19T19:27:43.866Z",
"request_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"errors": [
{
"context": "clients",
"message": "The request could not be handled due to an unexpected database error."
}
]
}Authorizations
Authorization: Bearer $JWT_TOKEN
Headers
Prefer header, used to specify a custom timeout in seconds using the wait parameter as per RFC7240. Passing in wait=-1 bypasses all timeout limits when the feature is enabled.
^wait=(-1|[0-9]+)$Body
Show child attributes
Show child attributes
Response
CREATED
Show child attributes
Show child attributes