Migrate coding evaluation calls from v1

v1 has two coding evaluation endpoints: POST /v1/coding, a list driven by a JSON filter body, and GET /v1/coding/:coding_eval_id. In v2 both are GET requests under https://api.brellium.com/v2/coding_evaluations. A coding_eval_id names the same record in both versions, so ids you stored from v1 work in v2.

The sample ids are the ones List and retrieve coding evaluations uses: coding evaluation 6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7, its audit a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d and its document 0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b. Values in the examples are made up.

Migration checklist

Copy this list into a ticket. Each item is one change to the code that calls the v1 coding evaluation endpoints.

  • Send requests to https://api.brellium.com/v2/... (v1 stays at /v1/...), with a v2 access token (Authenticate with OAuth2 client credentials). Mint one token per key and cache it. POST /v2/oauth/token allows a burst of 20 requests per client_id, then one every 10 seconds; the 21st returns 429 with Retry-After.
  • POST /v1/coding with a JSON filter body becomes GET /v2/coding_evaluations with query parameters and no request body.
  • v2 filters on status (one value), document_id (one id), audit_id, creation time and update time. The v1 filters on codes, medical decision making (MDM) levels, scores, timeBased, alignment, upcoded, downcoded and the session date are gone. Fetch the coding evaluations and apply those filters in your own code.
  • v1 returns up to 1,000 coding evaluations per page with page/pageSize. v2 returns at most limit coding evaluations per page (default 20, maximum 100). Send the last coding_eval_id of each page as starting_after, and stop when has_more is false.
  • v1 returns a bare array. v2 wraps the list as { "object": "list", "data": [...], "has_more": ... }.
  • Rename timestamp to created_at, em_eval to em_evaluation and icd_eval to icd_evaluation.
  • The switches verbose, includeDisplayInfo, includePeople, includeResponses and includeQuestionIds are gone. Every response has every field on every status, and a field with no value is null or [].
  • v1 responses is gone. For the answers to the questions, fetch the audit named by audit_id with GET /v2/audits/{audit_id}.
  • v1 people_emails is gone. Each person in people has a person_id, or null when the name isn't linked to a person. For a non-null id, GET /v2/email_recipients/{person_id} returns the email recipient with its email, or 404 NOT_FOUND when the person is not an email recipient.
  • Every error response is one JSON envelope: { "error": { "type", "code", "message", "param"?, "details"?, "request_id" } }. Branch on type and code, which are stable; message and details can change. request_id equals the x-request-id response header.

POST /v1/coding to GET /v2/coding_evaluations

Request

Both requests below return the completed coding evaluations of one document that changed since September 24.

- POST https://<your v1 host>/v1/coding
- Content-Type: application/json
-
- {
-   "documents": ["0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b"],
-   "status": ["complete"],
-   "lastUpdatedStartDate": "2026-09-24T00:00:00.000Z",
-   "verbose": "true",
-   "pageSize": 1000
- }
+ GET https://api.brellium.com/v2/coding_evaluations
+     ?document_id=0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b&status=complete
+     &updated_after=2026-09-24T00:00:00Z&limit=100
curl --get "https://api.brellium.com/v2/coding_evaluations" \
  --data-urlencode "document_id=0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b" \
  --data-urlencode "status=complete" \
  --data-urlencode "updated_after=2026-09-24T00:00:00Z" \
  --data-urlencode "limit=100" \
  --header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN"
const params = new URLSearchParams({
  document_id: "0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b",
  status: "complete",
  updated_after: "2026-09-24T00:00:00Z",
  limit: "100",
});

const res = await fetch(
  `https://api.brellium.com/v2/coding_evaluations?${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 codingEvaluations = await client.codingEvaluations.list({
  document_id: "0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b",
  status: "complete",
  updated_after: "2026-09-24T00:00:00Z",
  limit: 100,
});
console.log(codingEvaluations);
import os

from brellium_api_client import BrelliumClient

client = BrelliumClient(o_auth2=os.environ["BRELLIUM_ACCESS_TOKEN"])

coding_evaluations = client.coding_evaluations.list(
    document_id="0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b",
    status="complete",
    updated_after="2026-09-24T00:00:00Z",
    limit=100,
)
print(coding_evaluations.to_json())

Request fields

The table lists every v1 filter body field, in the order of the v1 documentation. v1 reads a boolean switch only as the string "true". Unless a row says otherwise, the v2 parameter takes one value; sending it twice returns 400 VALIDATION_ERROR.

v1 body field v2 query parameter Notes
verbose (none) v2 always returns the fields verbose adds in v1. They are reviewed_by, the score and flags of the evaluation and management (E/M) and International Classification of Diseases (ICD) results, problems, data, risk, ICD criteria and documents. The v2 response never has responses or people_emails; see Response fields.
status (array, matches any) status One exact value. For several, send one request per value.
documents (array, matches any) document_id One document_id. For several, send one request per document.
sessionStartDate, sessionEndDate (none) v1 filters on the coding evaluation's date of service. v2 has no field or filter for that date. Filter in your own code on the date item in display_info, if your organization shows one.
evalStartDate, evalEndDate created_after, created_before Both filter on the creation time and are inclusive, as in v1. Send dates as RFC 3339 timestamps in UTC ending in Z; an offset such as +02:00 or a date alone returns 400 VALIDATION_ERROR.
lastUpdatedStartDate, lastUpdatedEndDate updated_after, updated_before Both filter on the update time and are inclusive, as in v1. They take the same date format as the row above.
emScoreLowerBound, emScoreUpperBound (none) Filter in your own code on em_evaluation.score. v1 takes percent (0 to 100); v2 score is 0 to 1, so divide your v1 bound by 100. v1 ignores a bound of 0.
cptCodes (none) Filter in your own code on em_evaluation.submitted_base_code and submitted_add_on_codes.
recommendedCptCodes (none) Filter in your own code on em_evaluation.recommended_base_code and recommended_add_on_codes.
problemsLevel, dataLevel, riskLevel, mdmLevel (none) Filter in your own code on the em_evaluation field of the same name in snake_case (problems_level, ...).
timeBased (none) Filter in your own code on em_evaluation.time_based.
baseCodeAligned, addOnCodesAligned (none) Filter in your own code on em_evaluation.base_code_aligned and add_on_codes_aligned.
upcoded, downcoded (none) Filter in your own code on em_evaluation.upcoded and downcoded.
icdScoreLowerBound, icdScoreUpperBound (none) Filter in your own code on icd_evaluation.score, which runs from 0 to 1 (v1 bounds are percent).
icdCodes (none) Filter in your own code on icd_evaluation.icd_codes[].icd_code.
hccCodes (none) Filter in your own code on icd_evaluation.icd_codes[].hcc_code.
page (default 1) (none) v2 has no page numbers; use starting_after.
pageSize (default and maximum 1000) limit 1 to 100, default 20. limit=1000 returns 400 VALIDATION_ERROR.
afterCreatedAt + afterCodingEvalId starting_after v1 needs both values of the last item; v2 needs only its coding_eval_id.
includeDisplayInfo (none) display_info is always present.
includePeople (none) people is always present.
includeResponses (none) v1 POST /v1/coding ignores this field (verbose controls responses). v2 has no responses; fetch the audit.
includeQuestionIds (none) Question ids are on the audit's responses[].question_id.
(none) audit_id v2 adds this filter, which returns only the coding evaluations of one audit.
(none) coding_eval_id v2 adds this parameter for a batch get. Repeat it, or separate the ids with commas, to fetch specific coding evaluations in one request: ?coding_eval_id=<id>&coding_eval_id=<id> or ?coding_eval_id=<id>,<id>.
(none) ending_before, include_total v2 adds both. ending_before pages backwards, and include_total=true adds total_count to the response. For the pagination rules, see Page through and filter lists.

In both versions, a coding evaluation must match every filter you send. Without filters, both list every coding evaluation of your organization, newest first, a page at a time.

Replace a dropped filter

Fetch with the filters v2 keeps, then apply the rest in your own code. Only complete coding evaluations can have an E/M or ICD result, so filter on status=complete first. This sample replaces v1 {"upcoded": "true", "cptCodes": ["99215"], "emScoreUpperBound": 50}:

type EmEvaluation = {
  score: number | null;
  upcoded: boolean | null;
  submitted_base_code: string | null;
  submitted_add_on_codes: string[];
};
type CodingEvaluation = {
  coding_eval_id: string;
  em_evaluation: EmEvaluation | null;
};

// Call on each item of `data` from
// GET /v2/coding_evaluations?status=complete&limit=100
function matchesV1Filter(codingEvaluation: CodingEvaluation): boolean {
  const em = codingEvaluation.em_evaluation;
  if (!em) return false; // no E/M result to filter on
  const submitted = [em.submitted_base_code, ...em.submitted_add_on_codes];
  return (
    em.upcoded === true && // v1 "upcoded": "true"
    submitted.includes("99215") && // v1 "cptCodes": ["99215"]
    em.score !== null &&
    em.score <= 0.5 // v1 "emScoreUpperBound": 50 is a percent
  );
}

Run the filter on every page of the pagination loop in List and retrieve coding evaluations. A narrow v1 filter becomes a wider fetch, so add created_after or updated_after to keep it small.

Response

The v1 response depends on verbose. Without it, v1 returns a completed coding evaluation like this and leaves out every field with no value:

[
  {
    "coding_eval_id": "6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7",
    "status": "complete",
    "timestamp": "2026-09-24T02:34:54.489Z",
    "em_eval": {
      "em_eval_id": "0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d",
      "base_code_aligned": false,
      "add_on_codes_aligned": true,
      "problems_level": "moderate",
      "data_level": "low",
      "risk_level": "moderate",
      "mdm_level": "moderate",
      "time_based": false,
      "upcoded": true,
      "downcoded": false,
      "submitted_base_code": "99215",
      "submitted_add_on_codes": [],
      "recommended_base_code": "99214",
      "recommended_add_on_codes": []
    },
    "icd_eval": {
      "icd_eval_id": "3d4e5f60-7182-4c9d-8e1f-2a3b4c5d6e7f",
      "icd_codes": [{ "icd_code": "F41.1" }]
    },
    "document_id": "0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b"
  }
]

v2 returns the same coding evaluation with every field. A list item has the same shape as the response of GET /v2/coding_evaluations/{coding_eval_id}:

{
  "object": "list",
  "data": [
    {
      "coding_eval_id": "6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7",
      "object": "coding_evaluation",
      "audit_id": "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
      "status": "complete",
      "reviewed_by": null,
      "em_evaluation": {
        "em_eval_id": "0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d",
        "object": "em_evaluation",
        "coding_status": "codable",
        "uncodable_reason": null,
        "score": 0.5,
        "flags": ["Upcoded"],
        "base_code_aligned": false,
        "add_on_codes_aligned": true,
        "problems_level": "moderate",
        "data_level": "low",
        "risk_level": "moderate",
        "mdm_level": "moderate",
        "time_based": false,
        "encounter_time_minutes": null,
        "upcoded": true,
        "downcoded": false,
        "submitted_base_code": "99215",
        "submitted_add_on_codes": [],
        "recommended_base_code": "99214",
        "recommended_add_on_codes": [],
        "problems": [
          {
            "problem_id": "1b2c3d4e-5f60-4a7b-8c9d-0e1f2a3b4c5d",
            "category": "Stable, Chronic Illnesses",
            "description": "Generalized anxiety disorder, stable"
          }
        ],
        "data": [],
        "risk": [
          {
            "risk_id": "2c3d4e5f-6071-4b8c-9d0e-1f2a3b4c5d6e",
            "category": "Prescription Drug Management",
            "description": "Sertraline dose continued"
          }
        ]
      },
      "icd_evaluation": {
        "icd_eval_id": "3d4e5f60-7182-4c9d-8e1f-2a3b4c5d6e7f",
        "object": "icd_evaluation",
        "score": 1,
        "flags": [],
        "hcc_model_name": null,
        "icd_codes": [
          {
            "icd_code": "F41.1",
            "hcc_code": null,
            "criteria": [
              {
                "icd_criteria_id": "4e5f6071-8293-4da0-9f2a-3b4c5d6e7f80",
                "criteria_name": "Assess",
                "criteria_context": "Assessment lists generalized anxiety disorder.",
                "is_criteria_met": true
              }
            ]
          }
        ]
      },
      "documents": ["0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b"],
      "display_info": [
        {
          "key": "date_of_service",
          "title": "Date of service",
          "value": "2026-09-24T02:34:54.489Z",
          "format": "date"
        }
      ],
      "people": [
        {
          "person_id": "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
          "role": "provider",
          "role_title": "Provider",
          "name": "Jordan Rivera"
        }
      ],
      "created_at": "2026-09-24T02:34:54.489Z",
      "updated_at": "2026-09-24T02:37:12.105Z"
    }
  ],
  "has_more": false
}

Response fields

v2 returns the same object for both v1 endpoints. In the v1 column, "list: verbose" means v1 POST /v1/coding returns the field only with "verbose": "true", and "get: always" means GET /v1/coding/:coding_eval_id returns it without a switch. For a field with no value, v1 leaves the field out and v2 sends null or [].

Coding evaluation

v1 field v2 field Notes
(bare array, list only) object, data, has_more The list envelope; include_total=true adds total_count.
coding_eval_id coding_eval_id Same value.
(none) object Always "coding_evaluation".
(none) audit_id The id of the audit the coding evaluation belongs to, or null when it has none. Fetch the audit for the answers to the questions.
status status Same values. Branch on complete; treat any other value as unfinished.
timestamp created_at Same moment (both are the creation time), RFC 3339 in UTC.
(none) updated_at The last change. updated_after and updated_before filter on it.
reviewed_by (list: verbose; get: always; left out when empty) reviewed_by Always present, null when nobody reviewed it.
em_eval em_evaluation Field by field in the E/M table below.
icd_eval icd_evaluation Field by field in the ICD table below.
responses (list: verbose; get: includeResponses=true) dropped Fetch GET /v2/audits/{audit_id} and read its responses. The audit uses the v2 names (query is question, context is evidence, ...); for the full list, see Migrate audit calls from v1.
documents (list: verbose; get: always) documents Always present, with the document_id of every document the coding evaluation covers, in the order they were added.
document_id (list only) documents v1 returns the first linked document that matches your documents filter, or the first linked document. In v2, the document you filter on is your document_id parameter, and every linked document is in documents.
display_info (includeDisplayInfo) display_info Always present. Each item is { key, title, value, format }; v1 order and filter_type are dropped (the array is in display order). v2 returns date and time values as RFC 3339 in UTC and leaves other values unchanged. People are in people, as in v1.
people (includePeople) people Always present. Each item is { person_id, role, role_title, name }. v1 key becomes role, title becomes role_title and value becomes name, and v1 order, format and filter_type are dropped. person_id is new. It is the id shared by the email recipient and user for that person, or null when the name isn't linked to a person.
people_emails (list: verbose; get: always) dropped v1 lists the people linked to the coding evaluation's first document as { title, name, email }. In v2, fetch GET /v2/email_recipients/{person_id} for each people[].person_id that is not null. It returns the email recipient with its email, or 404 NOT_FOUND for a person who is not an email recipient, such as a patient.

v1 hides almost everything until a coding evaluation is complete. When status is not complete, or the coding evaluation has neither an E/M nor an ICD result to show, v1 returns only coding_eval_id, status and timestamp. The v2 response has every field on every status; until status is complete, em_evaluation and icd_evaluation are null. Code that treats "has em_eval" as "is finished" must check status === "complete" instead.

E/M result

v1 em_eval has two shapes: a codable one without coding_status, and an uncodable one with at most five fields. v2 em_evaluation always has every field; coding_status tells the two apart.

v1 em_eval field v2 em_evaluation field Notes
em_eval_id em_eval_id Unchanged.
(none) object Always "em_evaluation".
coding_status (uncodable only) coding_status Always present: "codable" or "uncodable".
uncodable_reason (uncodable only) uncodable_reason v1 returns only no_problem_identified or visit_type_indeterminate and leaves out any other reason. v2 returns every reason, such as insufficient_documentation, and null when codable or when no reason was recorded.
score (list: verbose) score v1 sends 0 when there is no score; v2 sends null. v2 also sends null when uncodable.
flags (list: verbose) flags Display names. v2 sorts them alphabetically.
base_code_aligned base_code_aligned v1 leaves it out when unknown; v2 sends null.
add_on_codes_aligned add_on_codes_aligned Same as the row above.
problems_level, data_level, risk_level, mdm_level same names straightforward, low, moderate or high; null when unknown or uncodable (v1 leaves them out).
time_based time_based null when uncodable.
encounter_time_minutes encounter_time_minutes null when not recorded (v1 leaves it out). Present on uncodable E/M results, as in v1.
upcoded, downcoded same names null when uncodable.
submitted_base_code submitted_base_code null when none (v1 leaves it out).
submitted_add_on_codes submitted_add_on_codes v2 sorts by code.
recommended_base_code recommended_base_code v1 sends "" when there is no recommendation; v2 sends null.
recommended_add_on_codes recommended_add_on_codes v2 sorts by code.
problems (list: verbose) problems Items keep problem_id, category, description. v2 sorts by category, then description.
data (list: verbose) data Items keep data_id, category, description. Same sort.
risk (list: verbose) risk Items keep risk_id, category, description. Same sort.

The diff below compares an uncodable E/M result in v1 and v2:

  "em_eval": {                                  // v2: "em_evaluation"
    "em_eval_id": "0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d",
+   "object": "em_evaluation",
    "coding_status": "uncodable",
+   // v1 leaves this reason out; v2 returns every reason
+   "uncodable_reason": "insufficient_documentation",
    "flags": ["Insufficient Documentation"],
    "encounter_time_minutes": 45
+   // v2 adds every other field, as null or [] for the arrays
  }

ICD result

v1 icd_eval field v2 icd_evaluation field Notes
icd_eval_id icd_eval_id Unchanged.
(none) object Always "icd_evaluation".
score (list: verbose) score v1 sends 0 when there is no score; v2 sends null.
flags (list: verbose) flags v2 sorts them alphabetically.
icd_codes[].hcc_model_name, only on codes with a hierarchical condition category (HCC) mapping hcc_model_name v2 moves it up one level and returns one value for the whole ICD result, or null when no model applies.
icd_codes[].icd_code icd_codes[].icd_code v2 lists codes in the order they were added.
icd_codes[].hcc_code (left out when no mapping) icd_codes[].hcc_code Always present; null when the code has no mapping.
icd_codes[].criteria (list: verbose) icd_codes[].criteria Items keep icd_criteria_id, criteria_name, criteria_context, is_criteria_met. v2 lists them in evaluation order.

Behavior changes that break a direct port

  • A v1 loop that stops when a page has fewer than 1,000 items stops after the first v2 page. Loop until has_more is false.
  • v1 afterCreatedAt must be the exact timestamp string; v2 starting_after takes only the last coding_eval_id.
  • v1 status and documents are arrays, while v2 status and document_id take one value each. Send one request per value of a v1 array.
  • v1 code that tests "score" in em_eval or em_eval.recommended_base_code === "" gets a different answer in v2. Test for null.
  • A v1 score of 0 can mean "no score"; in v2 that is null, and 0 is a real score.
  • Lists can take a few seconds to include a new or changed coding evaluation.

GET /v1/coding/:coding_eval_id to GET /v2/coding_evaluations/{coding_eval_id}

Request

- GET https://<your v1 host>/v1/coding/6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7
-     ?includeDisplayInfo=true&includePeople=true&includeResponses=true
+ GET https://api.brellium.com/v2/coding_evaluations/6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7
curl \
  "https://api.brellium.com/v2/coding_evaluations/6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7" \
  --header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN"
const res = await fetch(
  "https://api.brellium.com/v2/coding_evaluations/6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7",
  { 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 codingEvaluation = await client.codingEvaluations.retrieve(
  "6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7",
);
console.log(codingEvaluation);
import os

from brellium_api_client import BrelliumClient

client = BrelliumClient(o_auth2=os.environ["BRELLIUM_ACCESS_TOKEN"])

coding_evaluation = client.coding_evaluations.retrieve(
    "6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7"
)
print(coding_evaluation.to_json())
v1 query parameter v2 Notes
:coding_eval_id (path) {coding_eval_id} (path) Same id. An id that is not a UUID returns 400 VALIDATION_ERROR.
includeDisplayInfo=true (none) display_info is always present.
includePeople=true (none) people is always present.
includeResponses=true (none) v2 has no responses; fetch the audit with GET /v2/audits/{audit_id}.
includeQuestionIds=true (none) Question ids are on the audit's responses[].question_id.

To fetch several known ids in one request, use a batch get on the list: GET /v2/coding_evaluations?coding_eval_id=<id>&coding_eval_id=<id>. A query string over 2,048 bytes is rejected with a bare 403 and no JSON body, so split larger batches across requests.

Response

v1 returns this with all three switches on:

{
  "coding_eval_id": "6f1c2b3a-4d5e-4f60-8a71-92b3c4d5e6f7",
  "status": "complete",
  "timestamp": "2026-09-24T02:34:54.489Z",
  "em_eval": {
    "em_eval_id": "0a1b2c3d-4e5f-4a6b-8c7d-8e9f0a1b2c3d",
    "score": 0.5,
    "flags": ["Upcoded"],
    "base_code_aligned": false,
    "add_on_codes_aligned": true,
    "problems_level": "moderate",
    "data_level": "low",
    "risk_level": "moderate",
    "mdm_level": "moderate",
    "time_based": false,
    "upcoded": true,
    "downcoded": false,
    "submitted_base_code": "99215",
    "submitted_add_on_codes": [],
    "recommended_base_code": "99214",
    "recommended_add_on_codes": [],
    "problems": [
      {
        "problem_id": "1b2c3d4e-5f60-4a7b-8c9d-0e1f2a3b4c5d",
        "category": "Stable, Chronic Illnesses",
        "description": "Generalized anxiety disorder, stable"
      }
    ],
    "data": [],
    "risk": [
      {
        "risk_id": "2c3d4e5f-6071-4b8c-9d0e-1f2a3b4c5d6e",
        "category": "Prescription Drug Management",
        "description": "Sertraline dose continued"
      }
    ]
  },
  "icd_eval": {
    "icd_eval_id": "3d4e5f60-7182-4c9d-8e1f-2a3b4c5d6e7f",
    "score": 1,
    "flags": [],
    "icd_codes": [
      {
        "icd_code": "F41.1",
        "criteria": [
          {
            "icd_criteria_id": "4e5f6071-8293-4da0-9f2a-3b4c5d6e7f80",
            "criteria_name": "Assess",
            "criteria_context": "Assessment lists generalized anxiety disorder.",
            "is_criteria_met": true
          }
        ]
      }
    ]
  },
  "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."
    }
  ],
  "documents": ["0f8e7d6c-5b4a-4938-8271-605f4e3d2c1b"],
  "display_info": [
    {
      "key": "date_of_service",
      "order": 1,
      "title": "Date of service",
      "value": "2026-09-24T02:34:54.489Z",
      "format": "date",
      "filter_type": "date"
    }
  ],
  "people": [
    {
      "key": "provider",
      "order": 2,
      "title": "Provider",
      "value": "Jordan Rivera",
      "format": "text",
      "filter_type": "people"
    }
  ],
  "people_emails": [
    {
      "title": "staff",
      "name": "Jordan Rivera",
      "email": "jordan.rivera@clinic.example"
    }
  ]
}

v2 returns one item of the v2 list response above, without the envelope. The Response fields tables map every field. For v1 responses and people_emails, fetch the audit and each email recipient:

# The answers to the questions: from the linked audit
curl "https://api.brellium.com/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d" \
  --header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN"

# The email of each person with a person_id
curl "https://api.brellium.com/v2/email_recipients/2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d" \
  --header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN"
const headers = { Authorization: `Bearer ${process.env.BRELLIUM_ACCESS_TOKEN}` };

for (const path of [
  // The answers to the questions: from the linked audit
  "/v2/audits/a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d",
  // The email of each person with a person_id
  "/v2/email_recipients/2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
]) {
  const res = await fetch(`https://api.brellium.com${path}`, { headers });
  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 });

// The answers to the questions: from the linked audit
const audit = await client.audits.retrieve("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d");
console.log(audit);

// The email of each person with a person_id
const emailRecipient = await client.emailRecipients.retrieve(
  "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d",
);
console.log(emailRecipient);
import os

from brellium_api_client import BrelliumClient

client = BrelliumClient(o_auth2=os.environ["BRELLIUM_ACCESS_TOKEN"])

# The answers to the questions: from the linked audit
audit = client.audits.retrieve("a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d")
print(audit.to_json())

# The email of each person with a person_id
email_recipient = client.email_recipients.retrieve(
    "2a1b0c9d-8e7f-4a6b-9c5d-4e3f2a1b0c9d"
)
print(email_recipient.to_json())

Behavior changes that break a direct port

  • An unfinished coding evaluation has em_evaluation and icd_evaluation set to null, where v1 leaves the fields out. See the warning under Response fields.
  • v1 people_emails comes from the people linked to the first document. v2 people comes from the names shown on the coding evaluation, with a person_id where the name is linked to a person. The two lists can differ.
  • A new or changed coding evaluation can take a few seconds to appear at GET /v2/coding_evaluations/{coding_eval_id}, and until then a new one returns 404 NOT_FOUND. Retry once after a few seconds.

Error changes

v1 sends errors as plain text, {"msg": ...}, {"message": ...} or {"error": ...}. v2 sends one JSON envelope for every error.

Case v1 v2
Missing, invalid or expired token 401, plain text Unauthorized 401, authentication_error / UNAUTHORIZED
Unknown coding_eval_id 404, {"msg": "Coding evaluation not found"} 404, not_found_error / NOT_FOUND, message Coding evaluation not found
coding_eval_id is not a UUID 500, {"error": "Unexpected error occurred"} 400, invalid_request_error / VALIDATION_ERROR, details names coding_eval_id
Only one of afterCreatedAt and afterCodingEvalId, or a reformatted afterCreatedAt 400, {"error": "afterCreatedAt and afterCodingEvalId must be provided together, ..."} v2 has no such parameters; a starting_after that is not a UUID returns 400, invalid_request_error / VALIDATION_ERROR
A filter value v1 cannot read, such as "evalStartDate": "yesterday" 500, {"error": "Unexpected error occurred"} 400, invalid_request_error / VALIDATION_ERROR for a bad value, a repeated single-value parameter, a date with an offset or limit above 100
Body is not valid JSON 400, {"message": "Invalid JSON format"} These GET requests have no body
Too many requests (none) 429, rate_limit_error / RATE_LIMITED, with Retry-After
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