Migrate audit calls from v1
v1 has three audit calls: GET /v1/audits/:audit_id returns one audit, POST /v1/audits lists audits that match a filter body, and POST /v1/update-audits-flags adds flags to audits. In v2, GET /v2/audits/{audit_id} returns one audit and GET /v2/audits lists them with query parameters; PATCH /v2/audits/{audit_id} and POST /v2/audits/batch_update set flags.
A direct port of the list returns 20 audits, not 1,000. v1 POST /v1/audits returns up to pageSize audits (default 1,000) in one array. v2 GET /v2/audits returns 20 per page by default and at most 100, and sets has_more to true when more follow. Loop with starting_after until has_more is false.
Migration checklist
Copy this list into a ticket. Each item is one change to the code that calls the v1 audit endpoints.
- Send requests to
https://api.brellium.com/v2/...(v1 stays at/v1/...), with a v2 access token (Authenticate with OAuth2 client credentials). - Fetch one audit with
GET /v2/audits/{audit_id}instead ofGET /v1/audits/:audit_id. Remove theincludeDisplayInfo,includePeople,includeQuestionIdsandincludeLastReviewedquery parameters: v2 always returns every field. - List audits with
GET /v2/auditsand query parameters instead ofPOST /v1/auditswith a JSON body. Rename the filters:lastUpdatedStartDate/lastUpdatedEndDatebecomeupdated_after/updated_before, andauditStartDate/auditEndDatebecomecreated_after/created_before.documents,flags,statusandset_idkeep their names;statustakes one value, not an array. - Send dates as RFC 3339 timestamps in UTC ending in
Z, such as2026-09-01T00:00:00Z. An offset such as+02:00or a date alone (2026-09-01) returns400 VALIDATION_ERROR. - Replace
pageandpageSizewith cursor pagination:limit(1 to 100, default 20) andstarting_afterset to the lastaudit_idof the previous page. Read audits fromdataand stop whenhas_moreisfalse. - Remove
verbose,includeResponsesandincludeTestAudits. v2 always returnsscore,flagsandresponses. It never returns the test audits that v1 returns withincludeTestAudits, and it includes audits of coding question sets, which v1 lists leave out. - Filter by score in your own code, because v2 has no equivalent of
scoreLowerBoundandscoreUpperBound.sessionStartDate/sessionEndDatehave no v2 equivalent either. - Fetch related records from their own endpoints. An audit carries only their ids:
set_id,documents(a list ofdocument_idvalues) andpeople[].person_id.people_emailsis gone, so fetch each person's email address fromGET /v2/email_recipients/{person_id}. - Rename the response fields:
timestampbecomescreated_at,last_updatedbecomesupdated_at,last_reviewedbecomesreviewed_at, and in each item ofresponses,querybecomesquestionandcontextbecomesevidence. Inpeople[],key,titleandvaluebecomerole,role_titleandname.reviewed_by,metadata,document_id,people_emailsandresponses[].external_keyare gone. - Every field is present on every status. Check
status === "complete"before usingscore,flagsorresponses; do not check whether the field exists. - Change flag updates from
POST /v1/update-audits-flagstoPATCH /v2/audits/{audit_id}(one audit) orPOST /v2/audits/batch_update(up to 100). v2 replaces the flags with the list you send; v1 adds them. Fetch the audit, add your flag to its current list, then send the full list. Do not send the same flag twice in one list. - Replace a v1 flag update by
filterwith a list call followed bybatch_update. - Update your error handling for v2. Every error response is one JSON envelope:
{ "error": { "type", "code", "message", "param"?, "details"?, "request_id" } }. Branch ontypeandcode, which are stable;messageanddetailscan change. Handle400 VALIDATION_ERRORfor a bad parameter,404 NOT_FOUNDfor an unknown audit, and429 RATE_LIMITEDfor too many requests (waitRetry-Afterseconds, then retry).
Retrieve an audit
GET /v1/audits/:audit_id becomes GET /v2/audits/{audit_id}. The audit_id value is unchanged.
Request
- GET https://<your v1 host>/v1/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
- ?includeDisplayInfo=true&includePeople=true
- &includeQuestionIds=true&includeLastReviewed=true
+ GET https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
Authorization: Bearer $BRELLIUM_ACCESS_TOKEN
| v1 query parameter | v2 | Notes |
|---|---|---|
includeDisplayInfo=true |
no parameter | display_info is always present |
includePeople=true |
no parameter | people is always present |
includeQuestionIds=true |
no parameter | responses[].question_id is always present (null when the response is not linked to a question) |
includeLastReviewed=true |
no parameter | reviewed_at is always present |
v2 takes no query parameters on this call.
Response
v1 returns three fields while the audit is not complete:
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"status": "in_progress",
"timestamp": "2026-09-24T02:34:54.489Z"
}
v1 returns the full audit once status is complete. This response comes from a request with all four include* switches set to true:
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"status": "complete",
"score": 0.6666666666666666,
"flags": ["Failed", "Missing Signature"],
"reviewed_by": "",
"set_id": "3c2b1a09-8f7e-4d6c-b5a4-938271605f4e",
"timestamp": "2026-09-24T02:34:54.489Z",
"metadata": {},
"responses": [
{
"response_id": "e1d2c3b4-a596-4877-9867-5a4b3c2d1e0f",
"query": "Is the session note signed by the rendering provider?",
"answer": "No",
"context": "The signature block at the end of the note is empty.",
"question_id": "7d6c5b4a-3928-4170-a695-847362514f3e"
}
/* ... one entry per question */
],
"documents": ["0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b"],
"display_info": [
{
"order": 0,
"key": "provider",
"title": "Provider",
"value": "Jordan Rivera",
"format": "text",
"filter_type": "text"
}
],
"people": [
{
"order": 0,
"key": "client",
"title": "Client",
"value": "Sample Client",
"format": "text",
"filter_type": "text",
"person_id": null
},
{
"order": 1,
"key": "provider",
"title": "Provider",
"value": "Jordan Rivera",
"format": "text",
"filter_type": "text",
"person_id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d"
}
],
"people_emails": [
{ "title": "Client", "name": "Sample Client", "email": null },
{
"title": "Provider",
"name": "Jordan Rivera",
"email": "jordan.rivera@clinic.example"
}
],
"last_reviewed": "N/A"
}
v2 returns the same 13 fields on every status:
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"object": "audit",
"status": "complete",
"score": 0.6666666666666666,
"set_id": "3c2b1a09-8f7e-4d6c-b5a4-938271605f4e",
"flags": ["Failed", "Missing Signature"],
"created_at": "2026-09-24T02:34:54.489Z",
"updated_at": "2026-09-24T02:37:12.105Z",
"reviewed_at": null,
"display_info": [
{
"key": "provider",
"title": "Provider",
"value": "Jordan Rivera",
"format": "text"
},
{
"key": "date_of_service",
"title": "Date of service",
"value": "2026-09-24T02:34:54.489Z",
"format": "date"
}
],
"documents": ["0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b"],
"people": [
{
"person_id": null,
"role": "client",
"role_title": "Client",
"name": "Sample Client"
},
{
"person_id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
"role": "provider",
"role_title": "Provider",
"name": "Jordan Rivera"
}
],
"responses": [
{
"response_id": "e1d2c3b4-a596-4877-9867-5a4b3c2d1e0f",
"question_id": "7d6c5b4a-3928-4170-a695-847362514f3e",
"question": "Is the session note signed by the rendering provider?",
"answer": "No",
"evidence": "The signature block at the end of the note is empty.",
"remediation": "Ask the rendering provider to sign the note.",
"affirmation": null
}
/* ... one entry per question; the full sample is in Retrieve an audit by id */
]
}
Field by field
At the top level of the audit, the fields change as follows:
| v1 field | v2 field | Notes |
|---|---|---|
audit_id |
audit_id |
Unchanged |
| (none) | object |
Always "audit" |
status |
status |
Same values. v2 returns every field on every status; v1 returns only audit_id, status and timestamp until complete |
score |
score |
A number from 0 to 1, or null. v1 sends 0 when unset. Use it only when status is complete |
flags |
flags |
An array of strings; [] when there are none |
set_id |
set_id |
Unchanged; fetch the question set with GET /v2/question_sets/{set_id} |
timestamp |
created_at |
RFC 3339 in UTC. v2 created_at and v1 timestamp can differ, so do not match records on either |
| (none) | updated_at |
The time the audit last changed; the updated_after / updated_before filters use it |
last_reviewed (with includeLastReviewed) |
reviewed_at |
Always present. The time the audit was last marked reviewed in Brellium. It is null when the audit is not marked reviewed now (v1 "N/A") or was marked reviewed before November 26, 2024 |
reviewed_by |
no v2 equivalent | |
metadata |
no v2 equivalent | v1 always sends {} |
documents |
documents |
An array of document_id values; fetch a document with GET /v2/documents/{document_id} |
display_info (with includeDisplayInfo) |
display_info |
Always present; each item changes, see below |
people (with includePeople) |
people |
Always present; each item changes, see below |
people_emails |
no v2 equivalent | In v2, call GET /v2/email_recipients/{person_id} for each people[].person_id that is not null. A 404 NOT_FOUND means the person is not an email recipient (for example a client) or the recipient was deleted |
responses |
responses |
Always present, [] until the audit has responses; each item changes, see below |
The fields of each display_info item map to v2 as follows:
| v1 field | v2 field | Notes |
|---|---|---|
key |
key |
Unchanged |
title |
title |
Unchanged |
value |
value |
Always a string; dates and times are RFC 3339 in UTC; "" when empty |
format |
format |
Unchanged |
order |
no v2 equivalent | v2 returns the items already sorted by it |
filter_type |
no v2 equivalent |
Each person in people changes as follows:
| v1 field | v2 field | Notes |
|---|---|---|
key |
role |
Renamed, such as client or provider |
title |
role_title |
Renamed, such as Provider |
value |
name |
Renamed; the name as it appears on the audit |
person_id |
person_id |
The person's id, shared by the email recipient and user for that person, or null |
order |
no v2 equivalent | v2 returns the people already sorted by it |
format, filter_type |
no v2 equivalent |
Each response in responses changes as follows:
| v1 field | v2 field | Notes |
|---|---|---|
response_id |
response_id |
Unchanged |
query |
question |
Renamed; the question text |
answer |
answer |
"Yes", "No" or "N/A"; also "N/A" when no answer can be read. v1 can also send "-" |
context |
evidence |
Renamed. v2 returns the full evidence text, line breaks included; v1 returns only its first line. "" when none |
question_id (with includeQuestionIds) |
question_id |
Always present; null when the response is not linked to a question |
external_key |
no v2 equivalent | v1 sends it only for questions that have one |
| (none) | remediation |
The question's remediation text; null or "" when the question has none |
| (none) | affirmation |
The question's affirmation text; null or "" when the question has none |
Behavior changes that break a direct port
- Branch on
status, not on missing fields. v1 code that treats "has ascore" as "is complete" sees ascoreon every v2 audit. Teststatus === "complete". Before that,scoreisnullor0,flagsis[]andresponsescan be empty or partial. - Some audits that v1 returns by id return
404 NOT_FOUNDin v2. v1 returns any audit of your organization by id, test audits included. v2 never returns test audits. - An id that is not a UUID returns
400 VALIDATION_ERROR; v1 does not check the format. - For a few seconds after an audit is created or changed, such as on its completion,
GET /v2/audits/{audit_id}can return404or the previous version.
For the full v2 body and how to read it, see Retrieve an audit by id.
List and filter audits
POST /v1/audits with a JSON filter body becomes GET /v2/audits with query parameters.
Request
- POST https://<your v1 host>/v1/audits
- Content-Type: application/json
-
- {
- "status": ["complete"],
- "set_id": "3c2b1a09-8f7e-4d6c-b5a4-938271605f4e",
- "lastUpdatedStartDate": "2026-09-24T00:00:00Z",
- "lastUpdatedEndDate": "2026-09-25T00:00:00Z",
- "verbose": "true",
- "page": 1,
- "pageSize": 1000
- }
+ GET https://api.brellium.com/v2/audits
+ ?status=complete&set_id=3c2b1a09-8f7e-4d6c-b5a4-938271605f4e
+ &updated_after=2026-09-24T00:00:00Z&updated_before=2026-09-25T00:00:00Z
+ &limit=100
Authorization: Bearer $BRELLIUM_ACCESS_TOKEN
Each v1 body field maps to v2 as follows:
| v1 body field | v2 query parameter | Notes |
|---|---|---|
status (array) |
status |
One value, exact and case-sensitive. To match several statuses, make one call per value. Repeating status returns 400 VALIDATION_ERROR |
set_id |
set_id |
Unchanged; must be a UUID |
documents (array of document ids) |
documents |
Same name. Send up to 100 document_id values, repeated or separated by commas; it matches audits that cover any of them. See List a document's audits |
| (none) | external_id |
Matches audits that cover a document with this external_id |
flags (array) |
flags |
Repeat it, up to 50; it matches audits with any of them, as in v1. Exact and case-sensitive |
auditStartDate |
created_after |
Inclusive. v2 filters on created_at; v1 filters on timestamp, which can differ from created_at |
auditEndDate |
created_before |
Inclusive, on created_at; v1 filters on timestamp |
lastUpdatedStartDate |
updated_after |
Inclusive, on updated_at |
lastUpdatedEndDate |
updated_before |
Inclusive, on updated_at |
sessionStartDate, sessionEndDate |
no v2 equivalent | v1 filters on the audit's session date, and no v2 field carries that date. Contact Brellium support if you depend on it |
scoreLowerBound, scoreUpperBound |
no v2 equivalent | v1 takes percents (0 to 100). Filter in your own code, and keep the audits whose score * 100 is in your range |
page, pageSize |
limit, starting_after, ending_before |
Cursor pagination; see Pagination |
verbose |
no parameter | v2 always returns score, flags, set_id, documents and responses |
includeResponses |
no parameter | v1 ignores it (verbose controls responses); v2 always returns responses |
includeDisplayInfo |
no parameter | display_info is always present |
includePeople |
no parameter | people is always present |
includeQuestionIds |
no parameter | responses[].question_id is always present |
includeLastReviewed |
no parameter | reviewed_at is always present |
includeTestAudits |
no v2 equivalent | v1 "true" returns only test audits. v2 never returns them, and no parameter changes that |
| (none) | audit_id |
Repeat it to fetch specific audits; see Batch get by id |
| (none) | include_total |
true adds total_count, the number of matches across all pages |
Send dates as RFC 3339 timestamps in UTC ending in Z, such as 2026-09-24T00:00:00Z or 2026-09-24T00:00:00.000Z. A date alone (2026-09-24), a timestamp without Z and a timestamp with an offset (+02:00) each return 400 VALIDATION_ERROR. URL-encode spaces in a flag (Missing%20Signature).
Response
v1 returns a bare array. Which fields an item carries depends on verbose and the include* switches; this item comes from a request with verbose: "true":
[
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"status": "complete",
"score": 0.6666666666666666,
"flags": ["Failed", "Missing Signature"],
"reviewed_by": "",
"set_id": "3c2b1a09-8f7e-4d6c-b5a4-938271605f4e",
"timestamp": "2026-09-24T02:34:54.489Z",
"last_updated": "2026-09-24T02:37:12.105Z",
"responses": [ /* as in the v1 get response */ ],
"documents": ["0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b"],
"people_emails": [ /* as in the v1 get response */ ],
"document_id": "0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b"
}
]
v2 returns the list envelope. Each item in data is the full audit, the same object GET /v2/audits/{audit_id} returns:
{
"object": "list",
"data": [
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"object": "audit",
"status": "complete",
"score": 0.6666666666666666,
"set_id": "3c2b1a09-8f7e-4d6c-b5a4-938271605f4e",
"flags": ["Failed", "Missing Signature"],
"created_at": "2026-09-24T02:34:54.489Z",
"updated_at": "2026-09-24T02:37:12.105Z",
"reviewed_at": null,
"display_info": [ /* as in the v2 get response */ ],
"documents": ["0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b"],
"people": [ /* as in the v2 get response */ ],
"responses": [ /* as in the v2 get response */ ]
}
],
"has_more": true
}
Field by field
The v2 list envelope wraps the page in these fields:
| v1 | v2 | Notes |
|---|---|---|
| the top-level array | data |
One page of audits, not all of them |
| (none) | object |
Always "list" |
| (none) | has_more |
true when more audits follow; send the last audit_id as starting_after |
| (none) | total_count |
Present only with include_total=true |
Each item has every field of the GET /v2/audits/{audit_id} response, mapped as in Retrieve an audit. A v1 list item differs from the v1 GET /v1/audits/:audit_id response in these fields:
| v1 list field | v2 field | Notes |
|---|---|---|
last_updated |
updated_at |
Renamed |
document_id |
no v2 equivalent | v1 sends the smallest document_id (sorted as text) among the audit's documents, or among those in your documents filter. It is not necessarily the first document. To get the same value, take the smallest id in documents, counting only the ids in your filter when you sent one |
score, flags |
score, flags |
v1 sends them only with verbose and only on a complete audit; v2 always does |
responses, people_emails |
responses |
v1 sends responses only with verbose and only on a complete audit, and people_emails only with verbose |
set_id, documents, reviewed_by |
set_id, documents |
v1 sends them only with verbose; reviewed_by has no v2 equivalent |
metadata |
no v2 equivalent | The v1 list does not send it |
Pagination
v1 skips (page - 1) * pageSize audits and returns up to pageSize (default 1,000, no maximum). v2 returns up to limit audits (1 to 100, default 20) and has no page number; limit=1000 returns 400 VALIDATION_ERROR. Send the last audit_id of each page as starting_after until has_more is false. At limit=100, 1,000 audits take ten requests.
To fetch the second page, replace the v1 call as shown, where a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d is the last audit_id of the first page:
- POST /v1/audits
- { "status": ["complete"], "page": 2, "pageSize": 100 }
+ GET /v2/audits?status=complete&limit=100
+ &starting_after=a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
ending_before with the first audit_id of a page returns the page before it. For the pagination loop in code, see Page through and filter lists. To poll for changes, use updated_after and updated_before windows that overlap, as in Keep audits in sync with updated_after.
Batch get by id
v1 has no id filter; in v2, repeat audit_id in the list call, or send the ids in one value separated by commas. An id that matches no audit in your organization is left out, with no error.
curl --get "https://api.brellium.com/v2/audits" \
--data-urlencode "audit_id=a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
--data-urlencode "audit_id=b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e" \
--data-urlencode "limit=100" \
--header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN"
const params = new URLSearchParams();
params.append("audit_id", "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d");
params.append("audit_id", "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e");
params.append("limit", "100");
const res = await fetch(`https://api.brellium.com/v2/audits?${params}`, {
headers: { Authorization: `Bearer ${process.env.BRELLIUM_ACCESS_TOKEN}` },
});
if (!res.ok) throw new Error(JSON.stringify((await res.json()).error));
console.log(await res.json());
import BrelliumClient from "@brellium/api-client";
const client = new BrelliumClient({ oAuth2: process.env.BRELLIUM_ACCESS_TOKEN });
const audits = await client.audits.list({
audit_id: [
"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
],
limit: 100,
});
console.log(audits);
import os
from brellium_api_client import BrelliumClient
client = BrelliumClient(o_auth2=os.environ["BRELLIUM_ACCESS_TOKEN"])
audits = client.audits.list(
audit_id=[
"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
],
limit=100,
)
print(audits.to_json())
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 (about 43 documents values) fit in one request; split larger batches.
Behavior changes that break a direct port
- The sort order changes. v1 sorts by
timestamp, newest first, then byaudit_idascending; v2 sorts bycreated_at, newest first, then byaudit_iddescending. - v2 includes audits whose question set has
typecoding, which v1 leaves out. To drop them, fetch the question sets of typecodingwithGET /v2/question_sets?type=codingand skip the audits whoseset_idis one of them. includeTestAuditshas no v2 equivalent. v1"true"returns only test audits, and v2 never returns them. With the coding audits above, this means the v1 and v2 lists do not match one for one.statustakes one value. v1 takes an array of statuses; in v2, make one call per status.- Lists can take a few seconds to include a new or changed audit.
Update audit flags
POST /v1/update-audits-flags becomes PATCH /v2/audits/{audit_id} for one audit or POST /v2/audits/batch_update for up to 100.
A v2 flag update replaces the audit's flags, where v1 adds the flags you send to the current ones. v2 stores exactly the list you send, so {"flags": ["Reviewed"]} removes every other flag and {"flags": []} removes them all. To add a flag, fetch the audit, add the flag to its flags, then send the full list.
Request
For one audit, PATCH takes the full list:
- POST https://<your v1 host>/v1/update-audits-flags
- Content-Type: application/json
-
- { "audit_ids": ["a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d"], "flags": ["Reviewed"] }
+ PATCH https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d
+ Content-Type: application/json
+
+ { "flags": ["Failed", "Missing Signature", "Reviewed"] }
For several audits, batch_update takes one item per audit, each with that audit's full list:
- POST https://<your v1 host>/v1/update-audits-flags
- {
- "audit_ids": [
- "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
- "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
- ],
- "flags": ["Reviewed"]
- }
+ POST https://api.brellium.com/v2/audits/batch_update
+ {
+ "items": [
+ {
+ "audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
+ "flags": ["Failed", "Missing Signature", "Reviewed"]
+ },
+ {
+ "audit_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
+ "flags": ["Reviewed"]
+ }
+ ]
+ }
v1 can also select audits by their display fields with filter. v2 has no such mode, so list the audits and then update them by audit_id.
- POST https://<your v1 host>/v1/update-audits-flags
- { "filter": { "provider": "Jordan Rivera" }, "flags": ["Reviewed"] }
+ GET https://api.brellium.com/v2/audits?limit=100
+ (then keep the audits whose display_info has key "provider"
+ and value "Jordan Rivera")
+ POST https://api.brellium.com/v2/audits/batch_update
+ (items: each kept audit_id with its flags plus "Reviewed")
| v1 body field | v2 | Notes |
|---|---|---|
audit_ids (array) |
the {audit_id} path parameter of PATCH, or items[].audit_id of batch_update |
batch_update takes 1 to 100 items; v1 has no maximum. Each id must be a UUID |
flags (array, added to each audit) |
flags (PATCH) or items[].flags (batch_update) |
The full list, which replaces the audit's flags. Up to 50 strings of 1 to 100 characters, case-sensitive, no duplicates; [] removes every flag. v1 has no limits and removes duplicates itself |
filter (display field key and value pairs) |
no v2 equivalent | v1 matches audits whose display_info contains every pair. In v2, list the audits, match display_info in your own code, then batch_update them |
Response
v1 returns a message and the ids it changed:
{
"message": "Successfully updated flags for 2 audits",
"updated_audit_ids": [
"a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e"
]
}
PATCH returns the updated audit, the same object as GET /v2/audits/{audit_id}:
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"object": "audit",
"status": "complete",
"flags": ["Failed", "Missing Signature", "Reviewed"],
"updated_at": "2026-10-21T15:00:00.000Z"
/* ... the other audit keys, unchanged */
}
batch_update returns one result per item, in request order. Each result is the updated audit, or the item's audit_id with an error. The error is not_found_error / NOT_FOUND when your organization has no audit with that id, and api_error / INTERNAL_ERROR when the update could not be saved. has_more is always false.
{
"object": "list",
"data": [
{
"audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
"object": "audit",
"flags": ["Failed", "Missing Signature", "Reviewed"],
"updated_at": "2026-10-21T15:00:00.000Z"
/* ... the other audit keys */
},
{
"audit_id": "b2c3d4e5-f6a7-4b8c-9d0e-1f2a3b4c5d6e",
"error": {
"type": "not_found_error",
"code": "NOT_FOUND",
"message": "Audit not found"
}
}
],
"has_more": false
}
| v1 field | v2 | Notes |
|---|---|---|
message |
no v2 equivalent | |
updated_audit_ids |
the audit_id of each data item without error |
v2 also returns each updated audit in full |
Behavior changes that break a direct port
- A flag update replaces the audit's flags; v1 adds to them. Fetch the audit, add your flag to its
flags, then send the full list. - v2 rejects duplicate flags. A list with the same flag twice returns
400 VALIDATION_ERROR, withflags must be uniqueindetails, and v2 writes nothing. v1 removes duplicates silently. - An unknown audit no longer blocks the rest of the update. In v1, one unknown id or another organization's id fails the whole request: no audit changes, and v1 returns a
500. In v2,PATCHreturns404 NOT_FOUND, andbatch_updatereturns200with aNOT_FOUNDresult for that item and updates the others. v2 updates the items one at a time, in request order, and does not roll back the ones that succeed. - Validation covers the whole batch. One invalid item, such as a non-UUID id, a duplicate flag or a flag over 100 characters, returns
400 VALIDATION_ERROR, and v2 writes nothing. - Every v2 flag update sets a new
updated_at, so your own updates appear in your nextupdated_afterwindow. A v1 flag update does not changelast_updated. - v1 updates any audit of your organization, test audits included. v2 returns
404 NOT_FOUNDfor a test audit, so you cannot update its flags. GET /v2/audits/{audit_id}and lists can take a few seconds to show the new flags. The update response is the current audit, so use it instead of fetching the audit again.
For code that fetches, merges and updates the flags of one audit or a batch, see Update audit flags.
Error changes
| Case | v1 | v2 |
|---|---|---|
| Missing, invalid or expired token | 401, plain text Unauthorized |
401, authentication_error / UNAUTHORIZED |
| Unknown audit (retrieve) | 404, {"msg": "audit not found"} |
404, not_found_error / NOT_FOUND |
audit_id that is not a UUID |
v1 does not check the format | 400, invalid_request_error / VALIDATION_ERROR |
Bad filter value, such as a date without Z or limit over 100 |
v1 does not validate it | 400, invalid_request_error / VALIDATION_ERROR, with details per failing parameter |
| Body that is not valid JSON | 400, {"message": "Invalid JSON format"} |
400, invalid_request_error / HTTP_ERROR |
Flag update without flags, or without audit_ids and filter |
400, {"error": "flags array is required"} or {"error": "Either audit_ids or filter is required"} |
400, invalid_request_error / VALIDATION_ERROR |
Flag update body without Content-Type: application/json |
v1 also accepts a form-encoded body | 400, invalid_request_error / INVALID_CONTENT_TYPE |
| Unknown audit, or another organization's audit, in a flag update | 500 {"error": "Unexpected error occurred"}; no audit changes |
PATCH: 404, not_found_error / NOT_FOUND. batch_update: 200 with a per-item NOT_FOUND; v2 updates the other items |
| Too many requests | (none) | 429, rate_limit_error / RATE_LIMITED, with a Retry-After header |
| Server error | 500 {"error": "Unexpected error occurred"} |
500, api_error / INTERNAL_ERROR |
Every v2 error body carries request_id, which equals the x-request-id response header; send it to Brellium support when you report a problem. Branch on error.type and error.code, which are stable; message can change. For every code and the retry rules, see Rate limits and errors.
Next
Retrieve an audit by id
Retrieve one audit, check status before trusting score and flags, handle 404 and 400.
List a document's audits
Filter audits by the documents, external_id, status and set_id parameters.
Keep audits in sync with updated_after
Backfill once, then poll updated_after windows that overlap and upsert by id.
Update audit flags
Set the flags of one audit or of many in a batch update, without dropping the flags each audit already has.
Link an audit's people to recipients and users
Resolve an audit's people[] to email recipients and users by person_id.
Page through and filter lists
Cursor pagination, batch get by id, and each resource's filters.
Migrate from v1
Move an integration from the v1 API to v2, one resource at a time.