Save every rule under a tag key at once
Declarative batch behind one Save. The body carries the rules the key should end up with, in the order it should evaluate them: a member with an id updates that rule, a member without one creates a rule, and any existing rule the body omits is deleted. Array order is rank order, and rank is precedence, so the submitted order is the attribution model. expected_rules_revision is the key’s rules_revision as the client loaded it. It counts writes to the rules under the key and nothing else, so editing the key’s description or regenerating its rules elsewhere no longer invalidates an open editor. The revision is re-read inside the key’s lock and any mismatch returns 409 without writing anything. It is required: omitting it is a 400 rather than an unconditional overwrite, so a client that forgets the field cannot silently discard another editor’s work. rules is required too: omitting it is a 400, and only an explicit empty array clears the key. Every member is compiled before the transaction opens, so a batch that would partially fail returns 400 with per-member errors and writes nothing. Editing a rule an agent wrote promotes it to owned, while reordering or pausing it does not. Every member is written as an expression, a clause or a node, exactly one of the three, on the same terms as the single-rule endpoints. Responds with the key and its rules in their new order: key carries the rules_revision this save produced, read back inside the same transaction, so it is exactly what the next save must send as expected_rules_revision, and the client replaces its state from the response without refetching.
curl --request PUT \
--url https://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--header 'X-CostGraph-Tenant-ID: <x-costgraph-tenant-id>' \
--data '
{
"expected_rules_revision": 123,
"rules": [
{
"active": true,
"clause": {
"field": "<string>",
"match_value": "<string>",
"operator": "<string>"
},
"description": "<string>",
"expression": "<string>",
"id": "<string>",
"inherited_from": "<string>",
"key": "<string>",
"node": {
"children": "<array>",
"clause": {
"field": "<string>",
"match_value": "<string>",
"operator": "<string>"
},
"kind": "<string>"
},
"origin": "<string>",
"rule_type": "<string>",
"scope": "<string>",
"source_text": "<string>",
"valid_from": "<string>",
"valid_to": "<string>",
"value": "<string>"
}
]
}
'import requests
url = "https://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules"
payload = {
"expected_rules_revision": 123,
"rules": [
{
"active": True,
"clause": {
"field": "<string>",
"match_value": "<string>",
"operator": "<string>"
},
"description": "<string>",
"expression": "<string>",
"id": "<string>",
"inherited_from": "<string>",
"key": "<string>",
"node": {
"children": "<array>",
"clause": {
"field": "<string>",
"match_value": "<string>",
"operator": "<string>"
},
"kind": "<string>"
},
"origin": "<string>",
"rule_type": "<string>",
"scope": "<string>",
"source_text": "<string>",
"valid_from": "<string>",
"valid_to": "<string>",
"value": "<string>"
}
]
}
headers = {
"X-CostGraph-Tenant-ID": "<x-costgraph-tenant-id>",
"Authorization": "<api-key>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {
'X-CostGraph-Tenant-ID': '<x-costgraph-tenant-id>',
Authorization: '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
expected_rules_revision: 123,
rules: [
{
active: true,
clause: {field: '<string>', match_value: '<string>', operator: '<string>'},
description: '<string>',
expression: '<string>',
id: '<string>',
inherited_from: '<string>',
key: '<string>',
node: {
children: '<array>',
clause: {field: '<string>', match_value: '<string>', operator: '<string>'},
kind: '<string>'
},
origin: '<string>',
rule_type: '<string>',
scope: '<string>',
source_text: '<string>',
valid_from: '<string>',
valid_to: '<string>',
value: '<string>'
}
]
})
};
fetch('https://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules', 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://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'expected_rules_revision' => 123,
'rules' => [
[
'active' => true,
'clause' => [
'field' => '<string>',
'match_value' => '<string>',
'operator' => '<string>'
],
'description' => '<string>',
'expression' => '<string>',
'id' => '<string>',
'inherited_from' => '<string>',
'key' => '<string>',
'node' => [
'children' => '<array>',
'clause' => [
'field' => '<string>',
'match_value' => '<string>',
'operator' => '<string>'
],
'kind' => '<string>'
],
'origin' => '<string>',
'rule_type' => '<string>',
'scope' => '<string>',
'source_text' => '<string>',
'valid_from' => '<string>',
'valid_to' => '<string>',
'value' => '<string>'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"Content-Type: application/json",
"X-CostGraph-Tenant-ID: <x-costgraph-tenant-id>"
],
]);
$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://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules"
payload := strings.NewReader("{\n \"expected_rules_revision\": 123,\n \"rules\": [\n {\n \"active\": true,\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"description\": \"<string>\",\n \"expression\": \"<string>\",\n \"id\": \"<string>\",\n \"inherited_from\": \"<string>\",\n \"key\": \"<string>\",\n \"node\": {\n \"children\": \"<array>\",\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"kind\": \"<string>\"\n },\n \"origin\": \"<string>\",\n \"rule_type\": \"<string>\",\n \"scope\": \"<string>\",\n \"source_text\": \"<string>\",\n \"valid_from\": \"<string>\",\n \"valid_to\": \"<string>\",\n \"value\": \"<string>\"\n }\n ]\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("X-CostGraph-Tenant-ID", "<x-costgraph-tenant-id>")
req.Header.Add("Authorization", "<api-key>")
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.put("https://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules")
.header("X-CostGraph-Tenant-ID", "<x-costgraph-tenant-id>")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"expected_rules_revision\": 123,\n \"rules\": [\n {\n \"active\": true,\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"description\": \"<string>\",\n \"expression\": \"<string>\",\n \"id\": \"<string>\",\n \"inherited_from\": \"<string>\",\n \"key\": \"<string>\",\n \"node\": {\n \"children\": \"<array>\",\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"kind\": \"<string>\"\n },\n \"origin\": \"<string>\",\n \"rule_type\": \"<string>\",\n \"scope\": \"<string>\",\n \"source_text\": \"<string>\",\n \"valid_from\": \"<string>\",\n \"valid_to\": \"<string>\",\n \"value\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["X-CostGraph-Tenant-ID"] = '<x-costgraph-tenant-id>'
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"expected_rules_revision\": 123,\n \"rules\": [\n {\n \"active\": true,\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"description\": \"<string>\",\n \"expression\": \"<string>\",\n \"id\": \"<string>\",\n \"inherited_from\": \"<string>\",\n \"key\": \"<string>\",\n \"node\": {\n \"children\": \"<array>\",\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"kind\": \"<string>\"\n },\n \"origin\": \"<string>\",\n \"rule_type\": \"<string>\",\n \"scope\": \"<string>\",\n \"source_text\": \"<string>\",\n \"valid_from\": \"<string>\",\n \"valid_to\": \"<string>\",\n \"value\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"message": "some message",
"status": "success",
"data": {
"key": {
"compiled_at": "<string>",
"created_at": "<string>",
"created_by": "<string>",
"created_by_user": {
"active": true,
"email": "<string>",
"id": "<string>",
"name": "<string>",
"role": "<string>"
},
"description": "<string>",
"exported": true,
"exported_to": "<string>",
"id": "<string>",
"key": "<string>",
"last_proposed_at": "<string>",
"proposal_fingerprint": "<string>",
"rule_count": 123,
"rules_revision": 123,
"tenant_id": "<string>",
"updated_at": "<string>",
"updated_by": "<string>",
"updated_by_kind": "<string>",
"updated_by_user": {
"active": true,
"email": "<string>",
"id": "<string>",
"name": "<string>",
"role": "<string>"
}
},
"rules": [
{
"active": true,
"created_at": "<string>",
"description": "<string>",
"expression": "<string>",
"expression_hash": "<string>",
"field": "<string>",
"id": "<string>",
"inherited_from": "<string>",
"key": "<string>",
"match_value": "<string>",
"operator": "<string>",
"origin": "<string>",
"rank": 123,
"rule_type": "<string>",
"scope": "<string>",
"source_text": "<string>",
"tenant_id": "<string>",
"updated_at": "<string>",
"valid_from": "<string>",
"valid_to": "<string>",
"value": "<string>"
}
]
}
}{
"message": "Validation failed",
"status": "error",
"errors": {}
}{
"message": "some message",
"status": "error"
}{
"message": "some message",
"status": "error"
}{
"message": "some message",
"status": "error"
}Authorizations
Enter "Bearer {token}"
Headers
Tenant ID
Path Parameters
Tag key
Body
The rules the key should end up with, in rank order
Was this page helpful?
curl --request PUT \
--url https://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules \
--header 'Authorization: <api-key>' \
--header 'Content-Type: application/json' \
--header 'X-CostGraph-Tenant-ID: <x-costgraph-tenant-id>' \
--data '
{
"expected_rules_revision": 123,
"rules": [
{
"active": true,
"clause": {
"field": "<string>",
"match_value": "<string>",
"operator": "<string>"
},
"description": "<string>",
"expression": "<string>",
"id": "<string>",
"inherited_from": "<string>",
"key": "<string>",
"node": {
"children": "<array>",
"clause": {
"field": "<string>",
"match_value": "<string>",
"operator": "<string>"
},
"kind": "<string>"
},
"origin": "<string>",
"rule_type": "<string>",
"scope": "<string>",
"source_text": "<string>",
"valid_from": "<string>",
"valid_to": "<string>",
"value": "<string>"
}
]
}
'import requests
url = "https://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules"
payload = {
"expected_rules_revision": 123,
"rules": [
{
"active": True,
"clause": {
"field": "<string>",
"match_value": "<string>",
"operator": "<string>"
},
"description": "<string>",
"expression": "<string>",
"id": "<string>",
"inherited_from": "<string>",
"key": "<string>",
"node": {
"children": "<array>",
"clause": {
"field": "<string>",
"match_value": "<string>",
"operator": "<string>"
},
"kind": "<string>"
},
"origin": "<string>",
"rule_type": "<string>",
"scope": "<string>",
"source_text": "<string>",
"valid_from": "<string>",
"valid_to": "<string>",
"value": "<string>"
}
]
}
headers = {
"X-CostGraph-Tenant-ID": "<x-costgraph-tenant-id>",
"Authorization": "<api-key>",
"Content-Type": "application/json"
}
response = requests.put(url, json=payload, headers=headers)
print(response.text)const options = {
method: 'PUT',
headers: {
'X-CostGraph-Tenant-ID': '<x-costgraph-tenant-id>',
Authorization: '<api-key>',
'Content-Type': 'application/json'
},
body: JSON.stringify({
expected_rules_revision: 123,
rules: [
{
active: true,
clause: {field: '<string>', match_value: '<string>', operator: '<string>'},
description: '<string>',
expression: '<string>',
id: '<string>',
inherited_from: '<string>',
key: '<string>',
node: {
children: '<array>',
clause: {field: '<string>', match_value: '<string>', operator: '<string>'},
kind: '<string>'
},
origin: '<string>',
rule_type: '<string>',
scope: '<string>',
source_text: '<string>',
valid_from: '<string>',
valid_to: '<string>',
value: '<string>'
}
]
})
};
fetch('https://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules', 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://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "PUT",
CURLOPT_POSTFIELDS => json_encode([
'expected_rules_revision' => 123,
'rules' => [
[
'active' => true,
'clause' => [
'field' => '<string>',
'match_value' => '<string>',
'operator' => '<string>'
],
'description' => '<string>',
'expression' => '<string>',
'id' => '<string>',
'inherited_from' => '<string>',
'key' => '<string>',
'node' => [
'children' => '<array>',
'clause' => [
'field' => '<string>',
'match_value' => '<string>',
'operator' => '<string>'
],
'kind' => '<string>'
],
'origin' => '<string>',
'rule_type' => '<string>',
'scope' => '<string>',
'source_text' => '<string>',
'valid_from' => '<string>',
'valid_to' => '<string>',
'value' => '<string>'
]
]
]),
CURLOPT_HTTPHEADER => [
"Authorization: <api-key>",
"Content-Type: application/json",
"X-CostGraph-Tenant-ID: <x-costgraph-tenant-id>"
],
]);
$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://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules"
payload := strings.NewReader("{\n \"expected_rules_revision\": 123,\n \"rules\": [\n {\n \"active\": true,\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"description\": \"<string>\",\n \"expression\": \"<string>\",\n \"id\": \"<string>\",\n \"inherited_from\": \"<string>\",\n \"key\": \"<string>\",\n \"node\": {\n \"children\": \"<array>\",\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"kind\": \"<string>\"\n },\n \"origin\": \"<string>\",\n \"rule_type\": \"<string>\",\n \"scope\": \"<string>\",\n \"source_text\": \"<string>\",\n \"valid_from\": \"<string>\",\n \"valid_to\": \"<string>\",\n \"value\": \"<string>\"\n }\n ]\n}")
req, _ := http.NewRequest("PUT", url, payload)
req.Header.Add("X-CostGraph-Tenant-ID", "<x-costgraph-tenant-id>")
req.Header.Add("Authorization", "<api-key>")
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.put("https://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules")
.header("X-CostGraph-Tenant-ID", "<x-costgraph-tenant-id>")
.header("Authorization", "<api-key>")
.header("Content-Type", "application/json")
.body("{\n \"expected_rules_revision\": 123,\n \"rules\": [\n {\n \"active\": true,\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"description\": \"<string>\",\n \"expression\": \"<string>\",\n \"id\": \"<string>\",\n \"inherited_from\": \"<string>\",\n \"key\": \"<string>\",\n \"node\": {\n \"children\": \"<array>\",\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"kind\": \"<string>\"\n },\n \"origin\": \"<string>\",\n \"rule_type\": \"<string>\",\n \"scope\": \"<string>\",\n \"source_text\": \"<string>\",\n \"valid_from\": \"<string>\",\n \"valid_to\": \"<string>\",\n \"value\": \"<string>\"\n }\n ]\n}")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.costgraph.ai/api/v1/tenant/virtual-tag-keys/{key}/rules")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Put.new(url)
request["X-CostGraph-Tenant-ID"] = '<x-costgraph-tenant-id>'
request["Authorization"] = '<api-key>'
request["Content-Type"] = 'application/json'
request.body = "{\n \"expected_rules_revision\": 123,\n \"rules\": [\n {\n \"active\": true,\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"description\": \"<string>\",\n \"expression\": \"<string>\",\n \"id\": \"<string>\",\n \"inherited_from\": \"<string>\",\n \"key\": \"<string>\",\n \"node\": {\n \"children\": \"<array>\",\n \"clause\": {\n \"field\": \"<string>\",\n \"match_value\": \"<string>\",\n \"operator\": \"<string>\"\n },\n \"kind\": \"<string>\"\n },\n \"origin\": \"<string>\",\n \"rule_type\": \"<string>\",\n \"scope\": \"<string>\",\n \"source_text\": \"<string>\",\n \"valid_from\": \"<string>\",\n \"valid_to\": \"<string>\",\n \"value\": \"<string>\"\n }\n ]\n}"
response = http.request(request)
puts response.read_body{
"message": "some message",
"status": "success",
"data": {
"key": {
"compiled_at": "<string>",
"created_at": "<string>",
"created_by": "<string>",
"created_by_user": {
"active": true,
"email": "<string>",
"id": "<string>",
"name": "<string>",
"role": "<string>"
},
"description": "<string>",
"exported": true,
"exported_to": "<string>",
"id": "<string>",
"key": "<string>",
"last_proposed_at": "<string>",
"proposal_fingerprint": "<string>",
"rule_count": 123,
"rules_revision": 123,
"tenant_id": "<string>",
"updated_at": "<string>",
"updated_by": "<string>",
"updated_by_kind": "<string>",
"updated_by_user": {
"active": true,
"email": "<string>",
"id": "<string>",
"name": "<string>",
"role": "<string>"
}
},
"rules": [
{
"active": true,
"created_at": "<string>",
"description": "<string>",
"expression": "<string>",
"expression_hash": "<string>",
"field": "<string>",
"id": "<string>",
"inherited_from": "<string>",
"key": "<string>",
"match_value": "<string>",
"operator": "<string>",
"origin": "<string>",
"rank": 123,
"rule_type": "<string>",
"scope": "<string>",
"source_text": "<string>",
"tenant_id": "<string>",
"updated_at": "<string>",
"valid_from": "<string>",
"valid_to": "<string>",
"value": "<string>"
}
]
}
}{
"message": "Validation failed",
"status": "error",
"errors": {}
}{
"message": "some message",
"status": "error"
}{
"message": "some message",
"status": "error"
}{
"message": "some message",
"status": "error"
}