Migrate question set calls from v1

v1 has one question set endpoint, GET /v1/question-sets, which returns every question set of your organization in one array. In v2, two endpoints replace it: GET /v2/question_sets lists the sets one page at a time, and GET /v2/question_sets/{set_id} returns one set.

A direct port returns only 20 question sets. v1 returns all your sets in one response. v2 returns 20 per page by default (at most 100), and has_more is true when more follow. Loop until has_more is false, or fetch the one set you need by its set_id.

Migration checklist

Copy this list into a ticket. Each item is one change to the code that calls GET /v1/question-sets.

  • Send requests to https://api.brellium.com/v2/... (v1 stays at /v1/...), with a v2 access token (Authenticate with OAuth2 client credentials).
  • Change the path from /v1/question-sets (hyphen) to /v2/question_sets (underscore). The method stays GET.
  • To fetch one set, for example the set an audit names in its set_id, call /v2/question_sets/{set_id} instead of searching the full v1 list.
  • To fetch several known sets, repeat set_id in the list call: ?set_id=<id>&set_id=<id>.
  • The list response is no longer a bare array of every set. Read the sets from data, and request the next page while has_more is true.
  • Check any code that depends on the position of a set in the list. v2 returns the newest set first; v1 has no defined order.
  • Rename the set's name field to set_name. Every other v1 field keeps its name.
  • Treat null and "" as "no value" in question fields. v1 sends an empty string ("") for a missing question_type, severity, remediation or affirmation. v2 sends null, and remediation and affirmation can also be "".
  • Make sure your code accepts the new fields: object, set_code, type, thresholds, created_at, updated_at on the set, and object on each question.
  • Handle the v2 error format. Every error response is one JSON envelope: { "error": { "type", "code", "message", "param"?, "details"?, "request_id" } }. Handle 404 from GET /v2/question_sets/{set_id}. On 429 (too many requests), wait the number of seconds in the Retry-After header.

List all question sets

Request

v1 takes no parameters and ignores any you send. v2 takes cursor parameters and two filters.

- GET https://<your v1 host>/v1/question-sets
+ GET https://api.brellium.com/v2/question_sets?limit=100
  Authorization: Bearer $BRELLIUM_ACCESS_TOKEN
v2 query parameter Default What it does
limit 20 Question sets per page, 1 to 100. Any other value returns 400 VALIDATION_ERROR.
starting_after none The set_id of the last set on the previous page; returns the next page.
ending_before none The set_id of the first set on the current page; returns the previous page.
include_total false true adds total_count, the number of sets that match.
set_id none Return only these sets. For more than one, repeat it or separate the ids with commas. Each id must be a UUID.
type none Return only sets whose type equals this value exactly (case-sensitive). Sets with type: null never match.

Response

v1 returns a bare array of every question set that is not deleted, each with its questions:

[
  {
    "set_id": "3c2b1a09-8f7e-4d6c-b5a4-938271605f4e",
    "name": "Progress Note Compliance",
    "questions": [
      {
        "question_id": "7d6c5b4a-3928-4170-a695-847362514f3e",
        "query": "Is the session note signed by the rendering provider?",
        "question_type": "default",
        "severity": "Critical",
        "affirmation": "",
        "remediation": "Ask the rendering provider to sign the note."
      },
      {
        "question_id": "6c5b4a39-2817-4069-9584-736251403f2e",
        "query": "Does the note document the service start and end times?",
        "question_type": "default",
        "severity": "Normal",
        "affirmation": "Service times are documented.",
        "remediation": ""
      },
      {
        "question_id": "5b4a3928-1706-4f58-8473-625140392e1d",
        "query": "Which program does this note belong to?",
        "question_type": "informational",
        "severity": "None",
        "affirmation": "",
        "remediation": ""
      }
    ]
  }
]

v2 returns the list envelope. Each item in data is a full question set, the same object GET /v2/question_sets/{set_id} returns:

{
  "object": "list",
  "data": [
    {
      "set_id": "3c2b1a09-8f7e-4d6c-b5a4-938271605f4e",
      "object": "question_set",
      "set_name": "Progress Note Compliance",
      "set_code": "PNC-1",
      "type": "treatment_plan",
      "thresholds": [
        { "flag_id": "5e4d3c2b-1a09-4f8e-9d7c-6b5a49382716", "score": 0.85 }
      ],
      "questions": [
        {
          "question_id": "7d6c5b4a-3928-4170-a695-847362514f3e",
          "object": "question",
          "query": "Is the session note signed by the rendering provider?",
          "question_type": "default",
          "severity": "Critical",
          "remediation": "Ask the rendering provider to sign the note.",
          "affirmation": null
        },
        {
          "question_id": "6c5b4a39-2817-4069-9584-736251403f2e",
          "object": "question",
          "query": "Does the note document the service start and end times?",
          "question_type": "default",
          "severity": "Normal",
          "remediation": null,
          "affirmation": "Service times are documented."
        },
        {
          "question_id": "5b4a3928-1706-4f58-8473-625140392e1d",
          "object": "question",
          "query": "Which program does this note belong to?",
          "question_type": "informational",
          "severity": "None",
          "remediation": null,
          "affirmation": null
        }
      ],
      "created_at": "2026-09-01T12:00:00.000Z",
      "updated_at": "2026-09-24T02:37:12.105Z"
    }
  ],
  "has_more": false
}

Field by field

The list envelope has these fields:

v1 v2 Notes
the top-level array data One page of sets, not all of them
(none) object Always "list"
(none) has_more true when more sets follow; send the last set_id as starting_after
(none) total_count Present only with include_total=true

Each question set has these fields:

v1 field v2 field Notes
set_id set_id Unchanged; the same id an audit carries in set_id
(none) object Always "question_set"
name set_name Renamed; same value
(none) set_code Your organization's short code for the set, not guaranteed unique; "" when none
(none) type The set's category, such as treatment_plan; null for most sets; the type filter matches it
(none) thresholds Score cutoffs, as objects that usually have flag_id and score; some sets use other fields; [] when none
questions questions In the set's configured order (see below); deleted questions are left out, as in v1
(none) created_at RFC 3339 in UTC
(none) updated_at RFC 3339 in UTC

Each question in questions has these fields:

v1 field v2 field Notes
question_id question_id Unchanged; matches an audit's responses[].question_id
(none) object Always "question"
query query Unchanged
question_type question_type v1 "" when unset; v2 null
severity severity v1 "" when unset; v2 null; free text, and your organization can define its own values
remediation remediation v1 "" when unset; v2 null or ""
affirmation affirmation v1 "" when unset; v2 null or ""

v1 sends the question fields in the order question_id, query, question_type, severity, affirmation, remediation; v2 sends remediation before affirmation. A JSON parser ignores field order.

Behavior changes that break a direct port

  • v1 returns every question set. v2 returns at most limit sets per page (default 20, maximum 100): loop with starting_after until has_more is false. For the pagination loop, see Page through and filter lists. The samples below fetch the first page and the page after it, and print the two list envelopes.

    # First page, then the next page after the last set_id of the first page
    curl "https://api.brellium.com/v2/question_sets?limit=100" \
      --header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN"
    curl --get "https://api.brellium.com/v2/question_sets" \
      --data-urlencode "limit=100" \
      --data-urlencode "starting_after=3c2b1a09-8f7e-4d6c-b5a4-938271605f4e" \
      --header "Authorization: Bearer $BRELLIUM_ACCESS_TOKEN"
    
    const headers = { Authorization: `Bearer ${process.env.BRELLIUM_ACCESS_TOKEN}` };
    
    // First page, then the next page after the last set_id of the first page
    for (const params of [
      new URLSearchParams({ limit: "100" }),
      new URLSearchParams({
        limit: "100",
        starting_after: "3c2b1a09-8f7e-4d6c-b5a4-938271605f4e",
      }),
    ]) {
      const res = await fetch(`https://api.brellium.com/v2/question_sets?${params}`, {
        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 });
    
    // First page, then the next page after the last set_id of the first page
    console.log(await client.questionSets.list({ limit: 100 }));
    console.log(
      await client.questionSets.list({
        limit: 100,
        starting_after: "3c2b1a09-8f7e-4d6c-b5a4-938271605f4e",
      }),
    );
    
    import os
    
    from brellium_api_client import BrelliumClient
    
    client = BrelliumClient(o_auth2=os.environ["BRELLIUM_ACCESS_TOKEN"])
    
    # First page, then the next page after the last set_id of the first page
    print(client.question_sets.list(limit=100).to_json())
    print(
        client.question_sets.list(
            limit=100, starting_after="3c2b1a09-8f7e-4d6c-b5a4-938271605f4e"
        ).to_json()
    )
    
  • v1 applies no sort to question sets, so their order is not defined. v2 sorts them by created_at, newest first, then by set_id.

  • Within a set, v1 does not sort the questions either. v2 returns them in the set's configured order; questions without a position come last, sorted by question_id.

  • Code that tests severity === "" (or similar) on a v1 question misses the v2 null, so test for both.

  • v2 lists can take a few seconds to include a new or changed question set.

  • Deleted question sets are left out in both versions.

Retrieve a question set

v1 has no endpoint that returns one question set, so you fetch the full list and search it for the set_id. In v2, GET /v2/question_sets/{set_id} returns the set directly.

- GET https://<your v1 host>/v1/question-sets
-   (then find the item whose set_id is 3c2b1a09-8f7e-4d6c-b5a4-938271605f4e)
+ GET https://api.brellium.com/v2/question_sets/3c2b1a09-8f7e-4d6c-b5a4-938271605f4e
  Authorization: Bearer $BRELLIUM_ACCESS_TOKEN

The response is one question set with the same fields as an item of the list data, so the field-by-field tables above apply unchanged.

{
  "set_id": "3c2b1a09-8f7e-4d6c-b5a4-938271605f4e",
  "object": "question_set",
  "set_name": "Progress Note Compliance",
  "set_code": "PNC-1",
  "type": "treatment_plan",
  "thresholds": [
    { "flag_id": "5e4d3c2b-1a09-4f8e-9d7c-6b5a49382716", "score": 0.85 }
  ],
  "questions": [ /* the three questions, as in the list response */ ],
  "created_at": "2026-09-01T12:00:00.000Z",
  "updated_at": "2026-09-24T02:37:12.105Z"
}

Retrieving one question set differs from searching the v1 list in three ways:

  • The API returns 404 NOT_FOUND (Question set not found) when your organization has no question set with this id; another organization's id and a deleted set return 404 too. In v1 the set is missing from the array.
  • A set_id that is not a UUID returns 400 VALIDATION_ERROR.
  • A new or changed question set can take a few seconds to appear at GET /v2/question_sets/{set_id}; until then you can get a 404 or the previous version.

For code that joins a question set to an audit's responses and caches it by set_id, see Retrieve an audit's question set.

Batch get question sets by id

v1 has no filter: you search the full list. In v2, repeat set_id in the list call. The response is the list envelope. An id that matches no question set in your organization is left out, with no error.

- GET https://<your v1 host>/v1/question-sets
-   (then keep the items whose set_id is in your list)
+ GET https://api.brellium.com/v2/question_sets
+     ?set_id=3c2b1a09-8f7e-4d6c-b5a4-938271605f4e
+     &set_id=4d3c2b1a-9e8f-4a7b-8c6d-5e4f3a2b1c0d&limit=100
  Authorization: Bearer $BRELLIUM_ACCESS_TOKEN

The page size still applies: with more than 20 ids, 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 46 set_id values fit in one request; split larger batches. For batch get by id, see Page through and filter lists.

Error changes

Case v1 v2
Missing, invalid or expired token 401, plain text Unauthorized 401, authentication_error / UNAUTHORIZED
Set unknown, deleted or from another organization (retrieve by id) not applicable; the set is missing from the array 404, not_found_error / NOT_FOUND
Bad set_id, limit or other parameter not applicable; v1 ignores parameters 400, invalid_request_error / VALIDATION_ERROR, with details per failing parameter
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