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 staysGET. - 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_idin 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 whilehas_moreistrue. - 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
namefield toset_name. Every other v1 field keeps its name. - Treat
nulland""as "no value" in question fields. v1 sends an empty string ("") for a missingquestion_type,severity,remediationoraffirmation. v2 sendsnull, andremediationandaffirmationcan also be"". - Make sure your code accepts the new fields:
object,set_code,type,thresholds,created_at,updated_aton the set, andobjecton each question. - Handle the v2 error format. Every error response is one JSON envelope:
{ "error": { "type", "code", "message", "param"?, "details"?, "request_id" } }. Handle404fromGET /v2/question_sets/{set_id}. On429(too many requests), wait the number of seconds in theRetry-Afterheader.
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
limitsets per page (default 20, maximum 100): loop withstarting_afteruntilhas_moreisfalse. 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 byset_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 v2null, 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 return404too. In v1 the set is missing from the array. - A
set_idthat is not a UUID returns400 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 a404or 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
Retrieve an audit's question set
Follow set_id to the question set and join responses to questions.
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.