Instrument questions
Status:
stableCategory:patternsAdded:2026-04-20Last reviewed:2026-04-21
Summary
The shared contract for every GET /api/:category/:resource/questions
endpoint. Every instrument in the catalog exposes a /questions
operation that returns the instrument's item list, the answer choices
per item, and the set of items the paired /score operation requires.
Per-instrument docs (instruments-<resource>.md) list what is unique
to that instrument: item count, scoring method, band cutoffs, worked
example. They defer to this document for everything else.
Endpoint shape
| Field | Value |
|---|---|
| Method | GET |
| Path template | /api/:category/:resource/questions |
| Authentication | API key (required) |
| Request body | none |
| Response body | application/json |
| Idempotent | Yes |
| Rate-limited | Yes |
:category and :resource must match a registered pair in the
catalog. See GET /api/catalog/resources
for the complete list of concrete URLs.
Purpose
- Render a questionnaire UI. The items array carries the item IDs, prompt text, and the answer choices the UI must offer.
- Validate input client-side before
/score. Userequired_itemsto confirm the user has answered every required item — if you send an incomplete body to/score, Trinity rejects with400 ValidationErrornaming the missing ids. Better to catch that in the client. - Sync the instrument version your client renders against. Every
response carries the rubric's
version. Cache renders keyed on that version; refresh when it changes.
Authentication
Send a valid API key in the apikey header. See
authentication.md.
Request
Headers
| Header | Required | Description |
|---|---|---|
apikey | Yes | Consumer API key. |
X-Request-ID | No | Optional correlation ID. Echoed in the X-Request-ID response header. |
Path parameters
| Parameter | Type | Description |
|---|---|---|
category | string | Catalog category (for example pain, function, psychosocial). |
resource | string | Catalog resource inside that category (for example orebro, oswestry). |
The (category, resource) pair must be registered in the catalog.
An unknown pair returns 404.
Query / body parameters
None. GET ignores any body; query strings are not consulted.
Response
Success — 200 OK
{
"instrument_id": "pain_scale",
"version": "1.0.0",
"items": [
{
"id": "item_1",
"text": "Pain intensity right now (0 = no pain, 10 = worst imaginable)",
"answer_choices": [
{ "value": 0, "label": "0" },
{ "value": 1, "label": "1" },
{ "value": 2, "label": "2" },
{ "value": 3, "label": "3" },
{ "value": 4, "label": "4" },
{ "value": 5, "label": "5" },
{ "value": 6, "label": "6" },
{ "value": 7, "label": "7" },
{ "value": 8, "label": "8" },
{ "value": 9, "label": "9" },
{ "value": 10, "label": "10" }
]
}
],
"required_items": ["item_1"]
}
The X-Request-ID response header carries the correlation ID for
this call. The response body itself does not echo a request_id;
/questions is a pure lookup.
Response fields
| Field | Type | Always present | Description |
|---|---|---|---|
instrument_id | string | Yes | Canonical resource name (matches the URL's :resource). |
version | string (SemVer) | Yes | Rubric version for this instrument. Bumps when items, answer choices, or required-items change. |
items | array | Yes | Ordered list of items. Array position is not significant for scoring — items are keyed by their id. Rendering order is a UI choice. |
items[].id | string | Yes | Item identifier. Match when constructing the /score request body's responses map. |
items[].text | string | Yes | Display text for the item. Terse paraphrase today; replace with validated clinical wording before real-user-facing deployments. |
items[].answer_choices | array | Yes | Allowed answers. Send the value to /score; display label to the user. Ordering matters for UI presentation. |
items[].answer_choices[].value | integer | Yes | The numeric value to send in the /score body. Values are instrument-specific (for example 0–10 for NPRS, 0–3 for PHQ-9, 1–4 for TSK-11). See the per-instrument doc. |
items[].answer_choices[].label | string | Yes | Human-readable label for the choice. Render this to the user. |
required_items | array of string | Yes | Item IDs that must be present in /score request bodies. Missing any of these causes 400 ValidationError on /score. |
Status codes
| Code | Meaning | When |
|---|---|---|
200 | OK | Questions returned. |
401 | Unauthenticated | Missing or invalid API key. |
404 | NotFound | (category, resource) pair is not registered in the catalog. |
429 | RateLimited | Per-consumer rate limit exceeded. |
503 | ServiceUnavailable | Rubric for this instrument is not loaded. Transient; retry with backoff. |
Examples
The language-specific invocations below assume the call() helper (or
its equivalent) from quickstart.md. Per-instrument
docs (for example instruments-orebro.md,
instruments-depression_screen.md)
carry their own worked-example pairs; this section shows the shared
pattern using pain_scale as the smallest concrete instrument.
1. Canonical request and response
Send:
GET /api/pain/pain_scale/questions HTTP/1.1
Host: engine.tripod-health.com
apikey: $ENGINE_API_KEY
X-Request-ID: docs-pain_scale-questions-canonical
Receive (200 OK):
{
"instrument_id": "pain_scale",
"version": "1.0.0",
"items": [
{
"id": "item_1",
"text": "Pain intensity right now (0 = no pain, 10 = worst imaginable)",
"answer_choices": [
{ "value": 0, "label": "0" },
{ "value": 1, "label": "1" },
{ "value": 2, "label": "2" },
{ "value": 3, "label": "3" },
{ "value": 4, "label": "4" },
{ "value": 5, "label": "5" },
{ "value": 6, "label": "6" },
{ "value": 7, "label": "7" },
{ "value": 8, "label": "8" },
{ "value": 9, "label": "9" },
{ "value": 10, "label": "10" }
]
}
],
"required_items": ["item_1"]
}
2. Calling the endpoint in your language
curl
# Tiny (single-item) instrument
curl -sS "https://engine.tripod-health.com/api/pain/pain_scale/questions" \
-H "apikey: $ENGINE_API_KEY"
# Multi-item instrument
curl -sS "https://engine.tripod-health.com/api/psychosocial/depression_screen/questions" \
-H "apikey: $ENGINE_API_KEY"
Python (requests) — render a form
# Uses the call() helper from quickstart.md.
rubric = call("GET", "/api/psychosocial/depression_screen/questions")
print(f"{rubric['instrument_id']} v{rubric['version']}")
for item in rubric["items"]:
print(f" {item['id']}: {item['text']}")
for choice in item["answer_choices"]:
print(f" {choice['value']}. {choice['label']}")
Node.js (native fetch) — cache by version
// Uses the call() helper from quickstart.md.
const cache = new Map();
async function loadInstrument(category, resource) {
const key = `${category}/${resource}`;
const cached = cache.get(key);
const rubric = await call("GET", `/api/${category}/${resource}/questions`);
if (cached && cached.version === rubric.version) return cached;
cache.set(key, rubric);
return rubric;
}
const rubric = await loadInstrument("pain", "orebro");
TypeScript
// Uses the typed call<T>() helper from quickstart.md.
interface AnswerChoice { value: number; label: string }
interface RubricItem {
id: string;
text: string;
answer_choices: AnswerChoice[];
}
interface QuestionsResponse {
instrument_id: string;
version: string;
items: RubricItem[];
required_items: string[];
}
const rubric = await call<QuestionsResponse>(
"GET",
"/api/pain/orebro/questions",
);
Go
// Uses the call() helper from quickstart.md.
rubric, err := call("GET", "/api/pain/pain_scale/questions", nil, 3)
if err != nil {
panic(err)
}
items := rubric["items"].([]any)
for _, i := range items {
item := i.(map[string]any)
fmt.Printf("%v: %v\n", item["id"], item["text"])
}
Java
// Uses the call() helper from quickstart.md.
// Response is a nested JSON object; real callers should deserialize
// with Jackson/Gson for typed traversal.
String rubric = call("GET", "/api/pain/pain_scale/questions", null, 3);
System.out.println("version: " + extract(rubric, "version"));
C# (.NET HttpClient)
// Uses the Call() helper from quickstart.md.
var rubric = await Call(HttpMethod.Get, "/api/pain/pain_scale/questions");
var version = rubric.GetProperty("version").GetString();
Console.WriteLine($"{rubric.GetProperty("instrument_id").GetString()} v{version}");
foreach (var item in rubric.GetProperty("items").EnumerateArray())
{
var id = item.GetProperty("id").GetString();
var text = item.GetProperty("text").GetString();
Console.WriteLine($" {id}: {text}");
}
PowerShell
# Uses the Invoke-Engine helper from quickstart.md.
$rubric = Invoke-Engine -Method Get -Path "/api/pain/pain_scale/questions"
"$($rubric.instrument_id) v$($rubric.version)"
foreach ($item in $rubric.items) {
" $($item.id): $($item.text)"
}
3. Error scenarios
Unknown resource
Send:
curl -sS "https://engine.tripod-health.com/api/pain/frobnitz/questions" \
-H "apikey: $ENGINE_API_KEY" \
-H "X-Request-ID: docs-questions-unknown" \
-w "\nHTTP %{http_code}\n"
Receive (404):
{
"timestamp": "2026-04-21T23:10:00.000Z",
"error": "NotFound",
"message": "Unknown resource: pain/frobnitz",
"request_id": "docs-questions-unknown"
}
Handing the response into /score
The response body for /score uses the item IDs from this endpoint
as keys in its responses map. Values are the numeric value fields
from answer_choices, not the label:
{
"responses": {
"item_1": 4,
"item_2": 2
}
}
Every ID in required_items must appear in the responses map, or
/score responds 400 ValidationError with a missing_items array
naming which ones are missing. See
patterns-score.md.
Item text caveat
The item text returned today is a terse paraphrase of each published
instrument's content — enough to verify item identity and choice
ranges in integration, but not validated clinical wording. Before
showing these strings to a real user, replace them with the
officially-licensed wording for the instrument (and confirm any
licensing requirements for public administration). Trinity treats
the value integers as the authoritative contract, not the
displayed text.
Intended use
- Populating a questionnaire UI. One call per instrument gives you everything needed to render the form.
- Validating a client-side draft before submission. Walk
required_itemsand ensure each has a value before POSTing to/score. - Version-pinning a cached form. Cache the rubric keyed on
instrument_id + version; refetch when you see a newversionon a subsequent pull or on a scoring response'scatalog_versionchange.
Mistaken use
- Do not treat
items[]order as authoritative for scoring. Scoring keys onid, not position. A UI can reorder items safely; Trinity still scores the same way. - Do not send
answer_choices[].labelto/score. Send thevalueinteger. Labels are for display; they are not part of the scoring contract. - Do not assume every instrument's
answer_choicesrange is the same. Ranges vary:0–10(Örebro, NPRS, recovery_expectation),0–3(PHQ-9, GAD-7),0–5(ODI, NDI),1–5(QuickDASH),1–4(TSK-11),0/1(OSPRO-YF, ORT). Always readanswer_choicesfrom this endpoint, never hardcode ranges. - Do not call this endpoint on every page render. The response is stable for the lifetime of an instrument version. Cache it.
Debugging
- Capture the
X-Request-IDresponse header. 404 NotFound— the(category, resource)pair is not registered. Cross-referenceGET /api/catalog/resourcesto see what the current deploy exposes. Typos inresource(for examplephq9instead ofdepression_screen) are the usual cause.503 ServiceUnavailable— the instrument is registered in the catalog but the rubric was not loaded. Transient; retry with backoff. Persistent 503s on one instrument indicate a deploy-side problem worth reporting with therequest_id.answer_choicesdo not match what you expected. Confirm theversionon the response. Rubric content changes bumpversion; comparing yours against the live response reveals whether your client was rendering against a stale copy.- Subsequent
/scorefails withmissing_items. Compare the keys in yourresponsesmap againstrequired_itemshere. A common slip is sendingitem_01when the rubric expectsitem_1.
Rate limits
See rate-limits.md.
Related
- patterns-score.md — the paired scoring operation.
- Per-instrument docs (
instruments-<resource>.md) — each one documents the specific item IDs, answer-choice ranges, required items, and scoring semantics for that instrument. GET /api/catalog/resources— list of every(category, resource)pair currently exposed.
Change history
| Date | Change |
|---|---|
| 2026-04-20 | Initial publication. |