Update audit flags
When your system marks audits, set their flags with PATCH /v2/audits/{audit_id} for one audit or with POST /v2/audits/batch_update for up to 100 audits in one request. flags is the only field of an audit you can change, and the list you send replaces the current one. To add a flag, fetch the current list, merge your flag in, and write the full list back.
Prerequisites
- An access token in
$BRELLIUM_ACCESS_TOKEN, cached and reused (Authenticate with OAuth2 client credentials). - An
audit_id, for example fromGET /v2/audits?status=complete&limit=1(Page through and filter lists). This page uses the sample audita1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d, whose flags are["Failed", "Missing Signature"]. - With a live key, the flags you set change your organization's audit, and every response contains protected health information (PHI). A sandbox key changes the sample audit.
Steps
Fetch the current flags
PATCH replaces the whole list, so fetch the audit first. The samples print its status, flags and updated_at, and flags is the list you merge into.
curl "https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
--header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN" \
| jq '{status, flags, updated_at}'
const res = await fetch(
"https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
{ headers: { Authorization: `Bearer ${process.env.BRELLIUM_ACCESS_TOKEN}` } },
);
const body = await res.json();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body.error)}`);
const { status, flags, updated_at } = body;
console.log({ status, flags, updated_at });
import BrelliumClient from "@brellium/api-client";
const client = new BrelliumClient({ oAuth2: process.env.BRELLIUM_ACCESS_TOKEN });
const { status, flags, updated_at } = await client.audits.retrieve(
"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
);
console.log({ status, flags, updated_at });
import json
import os
from brellium_api_client import BrelliumClient
client = BrelliumClient(o_auth2=os.environ["BRELLIUM_ACCESS_TOKEN"])
audit = client.audits.retrieve("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d")
print(
json.dumps(
{
"status": audit.status,
"flags": audit.flags,
"updated_at": audit.updated_at,
},
indent=2,
default=str,
)
)
{
"status": "complete",
// the flags to merge into
"flags": ["Failed", "Missing Signature"],
// moves forward when the write in step 3 lands
"updated_at": "2026-09-24T02:37:12.105Z"
}
Merge in your flag
Add your flags to the current ones and remove duplicates. flags holds 0 to 50 unique strings of any text, each 1 to 100 characters, and [] removes every flag. Flags are case-sensitive, so "Reviewed" and "reviewed" are different flags. A list that breaks a rule returns 400 VALIDATION_ERROR; for the common validation errors, see Rate limits and errors.
The samples fetch the audit again, add "Reviewed" and print the merged list.
# Stored flags plus "Reviewed", de-duplicated. jq's unique also sorts, and
# order does not matter to the API.
NEW_FLAGS=$(
curl --silent \
"https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
--header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN" \
| jq --compact-output '.flags + ["Reviewed"] | unique'
)
echo "$NEW_FLAGS"
// Stored flags plus "Reviewed", de-duplicated and sorted like jq's unique (order
// does not matter to the API)
const res = await fetch(
"https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
{ headers: { Authorization: `Bearer ${process.env.BRELLIUM_ACCESS_TOKEN}` } },
);
const audit = await res.json();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${JSON.stringify(audit.error)}`);
const newFlags = [...new Set<string>([...audit.flags, "Reviewed"])].sort();
console.log(JSON.stringify(newFlags));
import BrelliumClient from "@brellium/api-client";
const client = new BrelliumClient({ oAuth2: process.env.BRELLIUM_ACCESS_TOKEN });
// Stored flags plus "Reviewed", de-duplicated and sorted like jq's unique (order
// does not matter to the API)
const audit = await client.audits.retrieve("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d");
const newFlags = [...new Set([...audit.flags, "Reviewed"])].sort();
console.log(JSON.stringify(newFlags));
import json
import os
from brellium_api_client import BrelliumClient
client = BrelliumClient(o_auth2=os.environ["BRELLIUM_ACCESS_TOKEN"])
# Stored flags plus "Reviewed", de-duplicated and sorted like jq's unique (order
# does not matter to the API)
audit = client.audits.retrieve("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d")
new_flags = sorted(set(audit.flags) | {"Reviewed"})
print(json.dumps(new_flags, separators=(",", ":")))
["Failed","Missing Signature","Reviewed"]
Write the merged list
Send the whole list with Content-Type: application/json.
curl --request PATCH \
"https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
--header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data "{\"flags\": $NEW_FLAGS}" \
--write-out "HTTP %{http_code}\n"
// the merged list from step 2
const newFlags = ["Failed", "Missing Signature", "Reviewed"];
const res = await fetch(
"https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
{
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.BRELLIUM_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ flags: newFlags }),
},
);
const body = await res.json();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body.error)}`);
console.log(body);
console.log(`HTTP ${res.status}`);
import BrelliumClient from "@brellium/api-client";
const client = new BrelliumClient({ oAuth2: process.env.BRELLIUM_ACCESS_TOKEN });
// the merged list from step 2
const newFlags = ["Failed", "Missing Signature", "Reviewed"];
const audit = await client.audits.update("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", {
flags: newFlags,
});
console.log(audit);
import os
from brellium_api_client import BrelliumClient
client = BrelliumClient(o_auth2=os.environ["BRELLIUM_ACCESS_TOKEN"])
# the merged list from step 2
new_flags = ["Failed", "Missing Signature", "Reviewed"]
audit = client.audits.update(
"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", flags=new_flags
)
print(audit.to_json())
The response is 200 with the updated audit (Update an audit's flags). It is the current record, so use it instead of fetching the audit again. For a few seconds after the update, GET /v2/audits/{audit_id} can still return the previous version, and lists can take a few seconds to include the changed audit.
A flag update changes only flags and updated_at; score and reviewed_at stay as they were. For the full body, see Retrieve an audit by id.
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"object": "audit",
"status": "complete",
"flags": ["Failed", "Missing Signature", "Reviewed"], // the list you sent
"updated_at": "2026-10-21T15:00:00.000Z" // bumped by the write
// ... score, set_id, reviewed_at, display_info, documents, people and responses,
// unchanged
}
The scripts below run steps 1 to 3 in one function and skip the update when the audit already has every flag you add.
// Keep credentials on the server; never in a browser or mobile app.
const token = process.env.BRELLIUM_ACCESS_TOKEN;
if (!token) throw new Error("BRELLIUM_ACCESS_TOKEN is not set");
const BASE_URL = "https://api.brellium.com";
type Audit = {
audit_id: string;
object: "audit";
status: string;
flags: string[];
updated_at: string;
};
type ApiError = {
error: { type: string; code: string; message: string; request_id?: string };
};
async function fail(res: Response): Promise<never> {
const body = (await res.json()) as ApiError;
throw new Error(
`${res.status} ${body.error.code}: ${body.error.message} (request_id ` +
`${body.error.request_id})`,
);
}
// Read, merge, write: the list sent to PATCH replaces the audit's flags.
async function addFlags(auditId: string, add: string[]): Promise<Audit> {
const read = await fetch(`${BASE_URL}/v2/audits/${auditId}`, {
headers: { Authorization: `Bearer ${token}` },
});
if (!read.ok) await fail(read);
const current = (await read.json()) as Audit;
// nothing new: skip the write
if (add.every((f) => current.flags.includes(f))) return current;
// Set keeps insertion order and drops duplicates, which would be a 400.
const flags = [...new Set([...current.flags, ...add])];
if (flags.length > 50) throw new Error(`${flags.length} flags; the limit is 50`);
const write = await fetch(`${BASE_URL}/v2/audits/${auditId}`, {
method: "PATCH",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ flags }),
});
if (!write.ok) await fail(write);
return (await write.json()) as Audit;
}
async function main() {
const audit = await addFlags("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", [
"Reviewed",
]);
console.log(audit.flags, audit.updated_at);
}
main();
# Keep credentials on the server; never in a browser or mobile app.
import os
import requests
TOKEN = os.environ["BRELLIUM_ACCESS_TOKEN"]
BASE_URL = "https://api.brellium.com"
HEADERS = {"Authorization": f"Bearer {TOKEN}"}
def raise_for_error(res: requests.Response) -> None:
if res.ok:
return
err = res.json()["error"]
raise RuntimeError(
f"{res.status_code} {err['code']}: {err['message']} "
f"(request_id {err.get('request_id')})"
)
def add_flags(audit_id: str, add: list[str]) -> dict:
"""Read, merge, write: the list sent to PATCH replaces the audit's flags."""
read = requests.get(
f"{BASE_URL}/v2/audits/{audit_id}", headers=HEADERS, timeout=30
)
raise_for_error(read)
current = read.json()
if all(f in current["flags"] for f in add):
return current # nothing new: skip the write
# dict.fromkeys keeps order and drops duplicates, which would be a 400.
flags = list(dict.fromkeys(current["flags"] + add))
if len(flags) > 50:
raise ValueError(f"{len(flags)} flags; the limit is 50")
write = requests.patch(
f"{BASE_URL}/v2/audits/{audit_id}",
headers={**HEADERS, "Content-Type": "application/json"},
json={"flags": flags},
timeout=30,
)
raise_for_error(write)
return write.json()
audit = add_flags("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", ["Reviewed"])
print(audit["flags"], audit["updated_at"])
The update moves updated_at, so the audit appears in your next updated_after window (Keep audits in sync with updated_after). Resending the same list is safe; it only moves updated_at again. Two updates to the same audit do not merge, and the last to arrive wins.
Update many audits in one call
POST /v2/audits/batch_update (Update flags on many audits) takes 1 to 100 { audit_id, flags } items. Each flags list follows the rules in step 2 and replaces that audit's flags; when one audit_id appears in several items, the last wins.
To add a flag to many audits, fetch them with a batch get, GET /v2/audits?audit_id=<id>&audit_id=<id>, merge your flag into each list, then send one batch update. The page size still applies, so send limit=100 or follow has_more. A query string over 2,048 bytes is rejected with a bare 403 and no JSON body, so about 44 audit_id values fit in one request; split larger batches.
To find audits by flag, send GET /v2/audits?flags=Failed&flags=Missing%20Signature, which lists the audits that have any of the repeated flags. You can repeat flags up to 50 times; each value matches a flag exactly, and the match is case-sensitive.
curl --request POST "https://api.brellium.com/v2/audits/batch_update" \
--header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"items": [
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"flags": ["Failed", "Missing Signature", "Reviewed"]
},
{
"audit_id": "11111111-1111-4111-8111-111111111111",
"flags": ["Reviewed"]
}
]
}' \
--write-out "HTTP %{http_code}\n"
const res = await fetch("https://api.brellium.com/v2/audits/batch_update", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BRELLIUM_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
items: [
{
audit_id: "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
flags: ["Failed", "Missing Signature", "Reviewed"],
},
{ audit_id: "11111111-1111-4111-8111-111111111111", flags: ["Reviewed"] },
],
}),
});
const body = await res.json();
if (!res.ok) throw new Error(`HTTP ${res.status}: ${JSON.stringify(body.error)}`);
console.log(body);
console.log(`HTTP ${res.status}`);
import BrelliumClient from "@brellium/api-client";
const client = new BrelliumClient({ oAuth2: process.env.BRELLIUM_ACCESS_TOKEN });
const result = await client.audits.batchUpdateFlags({
items: [
{
audit_id: "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
flags: ["Failed", "Missing Signature", "Reviewed"],
},
{ audit_id: "11111111-1111-4111-8111-111111111111", flags: ["Reviewed"] },
],
});
console.log(result);
import os
from brellium_api_client import BrelliumClient
client = BrelliumClient(o_auth2=os.environ["BRELLIUM_ACCESS_TOKEN"])
result = client.audits.batch_update_flags(
items=[
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"flags": ["Failed", "Missing Signature", "Reviewed"],
},
{"audit_id": "11111111-1111-4111-8111-111111111111", "flags": ["Reviewed"]},
],
)
print(result.to_json())
A 200 means the body passed validation, even when every item failed. data has one result per item, in request order, and each result is the updated audit or { audit_id, error }. has_more is always false.
{
"object": "list",
"data": [
{
// written: the full audit, as in step 3
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"object": "audit",
"flags": ["Failed", "Missing Signature", "Reviewed"],
"updated_at": "2026-10-21T15:00:00.000Z"
// ...
},
{
// not written: unknown audit_id
"audit_id": "11111111-1111-4111-8111-111111111111",
"error": {
"type": "not_found_error",
"code": "NOT_FOUND",
"message": "Audit not found"
}
}
],
"has_more": false
}
The scripts below send one batch update and return the updated audits and the failed items as two lists.
// Keep credentials on the server; never in a browser or mobile app.
const token = process.env.BRELLIUM_ACCESS_TOKEN;
if (!token) throw new Error("BRELLIUM_ACCESS_TOKEN is not set");
type Audit = {
audit_id: string;
object: "audit";
flags: string[];
updated_at: string;
};
type ItemError = {
audit_id: string;
error: { type: string; code: string; message: string };
};
type BatchResult = { object: "list"; data: (Audit | ItemError)[]; has_more: false };
async function batchUpdateFlags(items: { audit_id: string; flags: string[] }[]) {
if (items.length < 1 || items.length > 100)
throw new Error("send 1 to 100 items per call");
const res = await fetch("https://api.brellium.com/v2/audits/batch_update", {
method: "POST",
headers: {
Authorization: `Bearer ${token}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ items }),
});
// A 400 means the body was rejected and nothing was written; a 500 may follow
// writes that landed, so re-read before retrying.
if (!res.ok)
throw new Error(
`batch rejected: HTTP ${res.status} (x-request-id ` +
`${res.headers.get("x-request-id")})`,
);
const body = (await res.json()) as BatchResult;
// HTTP 200 covers mixed outcomes: inspect every entry.
const updated = body.data.filter(
(d): d is Audit => "object" in d && d.object === "audit",
);
const failed = body.data.filter((d): d is ItemError => "error" in d);
return { updated, failed };
}
async function main() {
const { updated, failed } = await batchUpdateFlags([
{
audit_id: "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
flags: ["Failed", "Missing Signature", "Reviewed"],
},
{ audit_id: "11111111-1111-4111-8111-111111111111", flags: ["Reviewed"] },
]);
for (const a of updated)
console.log(`updated ${a.audit_id}: ${a.flags.join(", ")}`);
for (const f of failed)
console.log(`failed ${f.audit_id}: ${f.error.code} ${f.error.message}`);
}
main();
# Keep credentials on the server; never in a browser or mobile app.
import os
import requests
TOKEN = os.environ["BRELLIUM_ACCESS_TOKEN"]
BASE_URL = "https://api.brellium.com"
def batch_update_flags(items: list[dict]) -> tuple[list[dict], list[dict]]:
if not 1 <= len(items) <= 100:
raise ValueError("send 1 to 100 items per call")
res = requests.post(
f"{BASE_URL}/v2/audits/batch_update",
headers={
"Authorization": f"Bearer {TOKEN}",
"Content-Type": "application/json",
},
json={"items": items},
timeout=60,
)
# A 400 means the body was rejected and nothing was written; a 500 may follow
# writes that landed, so re-read before retrying.
if not res.ok:
raise RuntimeError(
f"batch rejected: HTTP {res.status_code} (x-request-id "
f"{res.headers.get('x-request-id')})"
)
# HTTP 200 covers mixed outcomes: inspect every entry.
data = res.json()["data"]
updated = [d for d in data if d.get("object") == "audit"]
failed = [d for d in data if "error" in d]
return updated, failed
updated, failed = batch_update_flags(
[
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"flags": ["Failed", "Missing Signature", "Reviewed"],
},
{"audit_id": "11111111-1111-4111-8111-111111111111", "flags": ["Reviewed"]},
]
)
for a in updated:
print(f"updated {a['audit_id']}: {', '.join(a['flags'])}")
for f in failed:
print(f"failed {f['audit_id']}: {f['error']['code']} {f['error']['message']}")
Validation covers the whole request, so one non-UUID audit_id or one repeated flag anywhere in items returns 400 VALIDATION_ERROR and no audit changes. The error's details names the failing field, for example path: ["items", 0, "audit_id"].
After validation, the API updates the items in request order, and a failed item does not undo the items before it or stop the ones after it. When item 40 fails, items 1 to 39 keep their new flags and items 41 onward still get theirs. A failed item's error.code is NOT_FOUND (Audit not found) when your organization has no audit with that id. It is INTERNAL_ERROR (Failed to update audit, type api_error) when the update could not be saved; send that item again.
Handle a rejected request
A body that breaks a rule in step 2 returns 400 VALIDATION_ERROR with the path of the failing field, and the audit does not change. This example repeats a flag:
curl --request PATCH \
"https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
--header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN" \
--header "Content-Type: application/json" \
--data '{"flags": ["Reviewed", "Reviewed"]}' \
--write-out "HTTP %{http_code}\n"
const res = await fetch(
"https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
{
method: "PATCH",
headers: {
Authorization: `Bearer ${process.env.BRELLIUM_ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ flags: ["Reviewed", "Reviewed"] }),
},
);
// Expect HTTP 400 with the error envelope
console.log(await res.json());
console.log(`HTTP ${res.status}`);
import BrelliumClient, { APIError } from "@brellium/api-client";
const client = new BrelliumClient({ oAuth2: process.env.BRELLIUM_ACCESS_TOKEN });
try {
await client.audits.update("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", {
flags: ["Reviewed", "Reviewed"],
});
} catch (err) {
if (!(err instanceof APIError)) throw err;
console.log(err.status, err.error); // 400 and the error envelope
}
import os
from brellium_api_client import APIStatusError, BrelliumClient
client = BrelliumClient(o_auth2=os.environ["BRELLIUM_ACCESS_TOKEN"])
try:
client.audits.update(
"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d", flags=["Reviewed", "Reviewed"]
)
except APIStatusError as err:
print(err.status_code, err.body) # 400 and the error envelope
{
"error": {
"type": "invalid_request_error",
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"details": [
{
"code": "custom",
"path": ["flags"],
"message": "flags must be unique"
}
],
"request_id": "6b0d2e8a-3c4f-4a1b-9e7d-5f2c1a0b9d8e"
}
}
PATCH returns 404 NOT_FOUND (Audit not found) when your organization has no audit with this id; another organization's id returns 404 too. An id that is not a UUID returns 400 VALIDATION_ERROR, and invalid JSON returns 400 HTTP_ERROR (Malformed JSON in request body) without details. For every code, see Rate limits and errors.
Common mistakes
| Mistake | What you see | Fix |
|---|---|---|
Sending only the new flag, as with v1 POST /v1/update-audits-flags |
200, and the audit has only ["Reviewed"] |
v1 adds the flags you send to the audit's list; v2 replaces the list. Fetch the current flags and merge yours in first. To restore lost flags, PATCH the full list. |
Leaving out Content-Type: application/json |
400 INVALID_CONTENT_TYPE, and the flags do not change |
Set the header on every request with a body. |
Next
Retrieve an audit by id
Retrieve one audit, check status before trusting score and flags, handle 404 and 400.
Keep audits in sync with updated_after
Backfill once, then poll updated_after windows that overlap and upsert by id.
Migrate from v1
Move an integration from the v1 API to v2, one resource at a time.