Open Scale Definition (OSD) — Format Specification
Complete technical reference for the JSON-based format used to encode psychological and behavioral measurement scales. This document is sufficient to implement a compliant scale from scratch.
1. Overview
OSD (Open Scale Definition) is a JSON-based format for encoding psychological and behavioral
measurement scales in a structured, machine-readable form. A scale is defined by a
definition file ({CODE}.json) that captures structure,
items, dimensions, and scoring rules, and one or more translation files
({CODE}.{lang}.json) that supply all display strings in a given language.
For distribution and external hosting, these are combined into a single
OSD bundle ({CODE}.osd).
The format is designed to be platform-neutral: the same definition files are read by PEBL (the desktop runner), the web-based scale runner, conversion tools that export to REDCap / LimeSurvey / Qualtrics, and any custom implementation that follows this specification.
Key design principles:
- Separation of structure from language: the
.jsondefinition never contains display strings directly; all text passes through translation keys. - Flat translation files: the
.{lang}.jsonfile is a simple key/value object, easy to edit in any text editor or import into a translation management system. - Explicit scoring: reverse-coding and dimension membership are declared in the definition, not inferred from item order.
- Forward-compatible: unknown keys are silently ignored, so new runners can add fields without breaking existing definitions.
- Single-file distribution: the
.osdbundle combines the definition and all translations into one file for easy hosting — point the web runner directly at it with one URL.
2. File Naming & Directory Structure
Each scale lives in its own subdirectory under scales/. The directory name must
match the scale code exactly.
scales/
{CODE}/
{CODE}.osd <-- scale definition + translations (single JSON file)
screenshot.png <-- (optional) screenshot shown in the catalogue
README.md <-- (optional) description and usage notes
LICENSE.txt <-- (optional) license evidence
CODE Conventions
- Uppercase letters, digits, and hyphens only. No spaces.
- Use the established abbreviation for well-known scales:
PHQ9,GAD7,SF36,BDI2. - If no abbreviation exists, invent a short descriptive code and document it in
scale_info.abbreviation. - Examples of valid codes:
PHQ9,MOSSLP,IPIP-Big5-50,EAS3.
Language Codes
Translation keys within the translations object use
ISO 639-1 two-letter language codes: en,
de, fr, es, nl, etc.
An English translation ("en") is required. Additional languages are optional
and are embedded in the same .osd file.
The .osd Format
A .osd file is a plain JSON file that packages the scale definition and all
translations into a single document. It is the standard format used throughout OpenScales:
in the repository, the web runner, the PEBL desktop launcher, and PEBLHub. A researcher
hosting a scale on their own website needs only one file, and the web runner can be pointed
at it with a single URL.
Bundle structure
{
"osd_version": "1.0",
"definition": {
/* complete contents of {CODE}.json */
},
"translations": {
"en": { /* complete contents of {CODE}.en.json */ },
"de": { /* complete contents of {CODE}.de.json */ },
"fr": { /* ... */ }
}
}
| Key | Type | Description |
|---|---|---|
osd_version | string | Format version. Currently "1.0". Parsers should warn on unknown versions but still attempt to read the file. |
definition | object | The full scale definition — identical in content to {CODE}.json. |
translations | object | Map of language code → flat string dictionary. Each value is identical in content to the corresponding {CODE}.{lang}.json file. May be empty {} if no translations are available yet. |
Using a .osd file with the web runner
Because a .osd file is plain JSON, it can be fetched from any URL that
serves it with CORS headers — including OSF, GitHub raw, GitLab raw, and ordinary
web hosting. No special server configuration is required.
Direct URL — share a single link that runs the scale straight from your hosted file:
<!-- OSF -->
scale-runner.html?osd=https://osf.io/abc123/download&lang=en
<!-- GitHub raw -->
scale-runner.html?osd=https://raw.githubusercontent.com/user/repo/main/GAD7.osd
<!-- GitLab raw -->
scale-runner.html?osd=https://gitlab.com/user/repo/-/raw/main/GAD7.osd
<!-- Personal/lab website -->
scale-runner.html?osd=https://lab.example.org/scales/GAD7.osd&lang=fr
JavaScript API:
ScaleRunner.mount(container, {
osdURL: 'https://osf.io/abc123/download',
language: 'de', // falls back to 'en' if 'de' not present in bundle
participant: participantId,
onComplete: handleResult,
});
Using a .osd file with PEBL
The PEBL launcher reads .osd files directly and unpacks them to create a
valid PEBL experiment. Once unpacked, you can use the standard PEBL schemes for
translations (language files) and parameter-setting (parameter files). Place the
.osd file in your PEBL scales/ directory and select it
from the launcher's ScaleBuilder interface.
The .osd file is also available for download directly from each scale's page
on this website. The MIME type for serving .osd files is
application/json; no special server configuration is required.
3. Top-Level Keys of the Definition Object
These keys appear inside the "definition" object of the .osd file.
| Key | Type | Required | Description |
|---|---|---|---|
scale_info |
object | Yes | Metadata about the scale: name, code, citation, license, etc. |
likert_options |
object or null | Yes | Default response scale shared by all likert-type items. Set to
null if no items use the likert type. |
parameters |
object | No | Named runtime parameters that can be overridden via URL or launcher config. |
dimensions |
array | Yes | Scored subscales. Each element defines one dimension; the array may be empty for unscored instruments. |
items |
array | Yes | All administered items in presentation order, including instruction screens. |
scoring |
object | Yes | Scoring rules keyed by dimension id. May be an empty object if there are no scored dimensions. |
report |
object | No | HTML report configuration: template, sections to include, header, footer citations. |
data_output |
object | No | Output file naming templates and column lists for CSV output. |
default_required |
boolean | No | Overrides the type-based required defaults for all items in the scale.
Per-item required fields take precedence over this setting.
See Section 8a. |
4. scale_info Object
Metadata that identifies the scale and satisfies attribution and licensing requirements.
| Key | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Full human-readable name of the scale, e.g. "Patient Health Questionnaire-9". |
code |
string | Yes | Machine code; must match the directory name and filename prefix exactly, e.g. "PHQ9". |
abbreviation |
string | No | Common short name displayed in badges and compact views, e.g. "PHQ-9". |
description |
string | Yes | 1–3 sentence plain-text description of the scale, what it measures, and the score range. |
citation |
string | Yes | Full APA-style citation for the original publication, including DOI where available. |
license |
string | Yes | SPDX identifier or plain-text license name, e.g. "Public Domain",
"CC BY 4.0", "CC BY-NC 4.0". |
version |
string | No | Version of this OSD definition file (not the scale itself). Defaults to "1.0". |
url |
string | No | DOI or canonical URL for the scale, e.g. "https://doi.org/10.1046/j.1525-1497.2001.016009606.x". |
domain |
string | No | Domain hint for the manifest and catalogue UI (e.g. "depression",
"physical activity"). Overrides keyword-based auto-detection. |
debrief_key |
string | No | Translation key for the message shown to the participant on completion. Defaults
to "debrief". |
Example
"scale_info": {
"name": "Patient Health Questionnaire-9",
"code": "PHQ9",
"abbreviation": "PHQ-9",
"description": "9-item depression screening tool measuring symptom severity over the past 2 weeks. Scores range from 0 to 27.",
"citation": "Kroenke, K., Spitzer, R. L., & Williams, J. B. (2001). The PHQ-9: Validity of a brief depression severity measure. Journal of General Internal Medicine, 16(9), 606-613. https://doi.org/10.1046/j.1525-1497.2001.016009606.x",
"license": "Public Domain",
"version": "1.0",
"url": "https://doi.org/10.1046/j.1525-1497.2001.016009606.x"
}
5. likert_options Object
Defines the single shared response scale applied to every item whose type is
"likert". If the scale has no likert-type items (all items are multi,
short, number, etc.), set likert_options to
null.
| Key | Type | Required | Description |
|---|---|---|---|
points |
integer | Yes | Number of response options, e.g. 4 for a 4-point scale. |
min |
integer | Yes | Numeric value of the first (leftmost) option. Typically 0 or 1. |
max |
integer | Yes | Numeric value of the last (rightmost) option. Must equal min + points - 1. |
labels |
array of strings | Yes | Translation keys for the option labels, one per point. Length must equal
points. Each key must be defined in the translation file. |
question_head |
string | No | Translation key for a shared question stem shown above all likert items,
e.g. "question_head" resolving to
"Over the last 2 weeks, how often…". |
Example — PHQ-9 (4-point, 0-based)
"likert_options": {
"points": 4,
"min": 0,
"max": 3,
"labels": ["likert_1", "likert_2", "likert_3", "likert_4"],
"question_head": "question_head"
}
likert_options is null, every scored
item must be of a type other than likert (typically multi or
number), and option values must be set explicitly in each item's
options array. See the IPAQ definition for a real example.
6. parameters Object
An optional object that declares named runtime parameters. Parameters can be overridden
at launch time via URL query strings (?param_NAME=value) or via the launcher
configuration dialog, without editing the definition file. Each key is the parameter name;
the value is a parameter descriptor object.
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | Yes | One of "boolean", "integer", "number",
"string", "choice". Used for validation and UI
widget selection. |
default |
any | Yes | Value used when the parameter is not overridden at runtime. |
description |
string | No | Human-readable description for documentation and the launcher UI. |
options |
array | For "choice" type |
Array of allowed string values. Only used when type is
"choice". |
Reserved Parameter Names
Runners give these parameter names special treatment in addition to normal parameter
substitution. Scales that require specific behavior should declare
these in their parameters block with the appropriate default.
| Name | Type | Default | Effect |
|---|---|---|---|
shuffle_questions |
boolean | false |
Randomize item order within randomization groups (see random_group
in Section 8a). When declared in the OSD, the declared default overrides the
runner's built-in default. |
show_header |
boolean | true |
Whether to display the scale title above the questionnaire. Set to
false for scales where revealing the title could bias responses
(e.g., burnout or susceptibility scales administered in a battery). |
Example
"parameters": {
"shuffle_questions": {
"type": "boolean",
"default": false,
"description": "Randomize item presentation order"
},
"show_header": {
"type": "boolean",
"default": false,
"description": "Hide scale title to avoid response bias"
},
"system_name": {
"type": "string",
"default": "the system",
"description": "Name of the system being evaluated (inserted into item text)"
},
"scale_version": {
"type": "choice",
"default": "full",
"options": ["full", "short", "screening"],
"description": "Which version of the scale to administer"
}
}
Parameters are overridden at runtime via URL query strings
(?param_shuffle_questions=1) or via launcher configuration.
Parameters are referenced by name in the enabled_param field of a
dimension (see Section 7). Parameter values can also be substituted into item
text using {param_name} syntax in translation strings.
7. dimensions Array
Each element describes one scored subscale. The array determines what subscale scores are
computed and reported. Items may belong to zero, one, or more dimensions; dimension
membership is declared in the scoring object, not here.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Machine key used as the key in the scoring object and in CSV
column names. Must be unique within the scale. |
name |
string | Yes | Human-readable name displayed in the report and catalogue. |
abbreviation |
string | No | Short label for compact display, e.g. "PF" for Physical Functioning. |
description |
string | No | Plain-text description including score range and direction, e.g. "Range 0–27; higher = greater severity." |
enabled_param |
string or null | No | If set to a parameter name, this dimension is only scored when that parameter
is truthy at runtime. Set to null (or omit) to always score. |
Example — SF-36 multi-dimension excerpt
"dimensions": [
{
"id": "pf",
"name": "Physical Functioning",
"abbreviation": "PF",
"description": "Extent to which health limits physical activities. Range: 10–30 (higher = less limitation).",
"enabled_param": null
},
{
"id": "ewb",
"name": "Emotional Well-Being",
"abbreviation": "EWB",
"description": "Extent of anxiety and depression. Range: 5–30 (higher = better mental health).",
"enabled_param": null
}
]
8. items Array
The ordered list of every item presented to the participant, including instruction screens, image displays, and scored questions. Items are shown in array order unless branching conditions redirect flow.
8a. Common Fields (All Item Types)
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique identifier for this item within the scale. Used as the key in
scoring.item_coding and as the CSV column name for the response. |
text_key |
string | Yes | Translation key whose value is the question or instruction text displayed to the participant. Must be defined in the translation file. |
type |
string | Yes | Item type. See the type reference table (Section 8b) for valid values. |
dimension |
string or null | No | Dimension id this item contributes to, for informational/display purposes.
Scoring membership is authoritative in the scoring object. |
visible_when |
object or null | No | Skip logic condition. Item is shown only when the condition evaluates to true. See Section 8d for syntax. |
random_group |
integer | varies | Randomization group for shuffle behavior when shuffle_questions
is active. 0 = fixed position; 1+ = shuffle within
that numbered group (scoped to the item's section). Defaults: inst
items and items with visible_when default to 0
(fixed); all others default to 1 (shuffled). |
required |
boolean | varies by type | Whether the item must be answered before advancing. Overrides type-based
defaults and the scale-level default_required field. Scored
types (likert, vas, multi, etc.)
default to required; text-entry types (short, long)
default to optional. |
8b. Item Type Reference
| Type | Description | Key Extra Fields |
|---|---|---|
inst |
Instruction or transition screen. Text is displayed but no response is recorded.
Not included in scoring. Images can be embedded via <img>
in the translation string. |
(none beyond common fields) |
section |
Section boundary marker. Begins a new logical grouping of items. All items
following the marker (up to the next section marker) belong to
this section. See Section 8e. |
title_key (optional), revisable (boolean, default
true), randomize (object, optional),
visible_when (skips entire section) |
likert |
Single-select using the global likert_options response scale. The
most common type for traditional questionnaire items. |
(none beyond common fields) |
multi |
Single-select with a custom set of labeled options. Use this when items need different response options from the global likert scale. | options: array of {"text_key": "…", "value": N} |
multicheck |
Multi-select (checkbox) with custom options. The recorded value is a comma-separated list of selected values. | options: array of {"text_key": "…", "value": N} |
short |
Single-line free-text entry. Supports optional validation
constraints (see Section 8f). |
maxlength (optional); validation object (optional) |
long |
Multi-line free-text entry (textarea). Supports optional validation. |
maxlength, rows, cols (all optional) |
number |
Numeric entry with optional range and step constraints. | min, max, step (all optional) |
date |
Date picker entry. Recorded as an ISO 8601 date string. | (none) |
vas |
Visual analog scale — a continuous horizontal slider. | min, max, min_label, max_label,
step |
grid |
Matrix question: a set of sub-rows each rated on the global
likert_options scale. |
rows: array of row translation keys;
columns: array of column translation keys |
rank |
Rank-ordering task. Participant drags options into a preferred order. | options: array of {"text_key": "…", "value": N} |
dropdown |
Single-select from a dropdown list. Useful for long option lists. | options: array of {"text_key": "…", "value": N} |
constant_sum |
Participant allocates a fixed total of points across several options. | options: array of {"text_key": "…", "value": N};
total: integer total to distribute |
semantic_differential |
Bipolar adjective pair scale; participant rates on a continuum between two poles. | min_label, max_label, min, max |
8c. options for multi / multicheck / dropdown / rank
Each element of the options array is an object with two required fields:
| Field | Type | Description |
|---|---|---|
text_key |
string | Translation key for the option label. Must be defined in the translation file. |
value |
integer or float | Numeric value recorded when this option is selected. Used directly in scoring. |
multi items: The item_coding: -1
flag in the scoring object does not apply to multi
(or multicheck / dropdown) items — only to likert
items. To reverse-code a multi item, pre-reverse the value fields
in the options array. For example, if "Strongly Agree" would normally score 1
on a forward item, set "value": 5 for the reverse item and use
item_coding: 1 in the scoring block. See the SF-36 definition (items
sf36_q11b, sf36_q11d) for real examples.
8d. visible_when (Skip Logic)
An optional condition object that controls whether this item is shown.
The item appears only when the condition evaluates to true; it is skipped (and its
response recorded as "NA") when the condition is false. A missing or
null visible_when means always show.
Simple condition (previous item's response)
{
"id": "followup1",
"text_key": "followup1",
"type": "likert",
"visible_when": {
"item": "screen1",
"operator": "equals",
"value": 1
}
}
Condition on a parameter value
{
"visible_when": {
"parameter": "show_followup",
"operator": "equals",
"value": true
}
}
Compound conditions
Use "all" (logical AND) or "any" (logical OR) to combine conditions:
{
"visible_when": {
"all": [
{ "item": "q1", "operator": "equals", "value": "Yes" },
{ "parameter": "show_followup", "operator": "equals", "value": true }
]
}
}
Operators
| Operator | Description |
|---|---|
"equals" | Exact match |
"not_equals" | Not equal |
"greater_than" | Numeric greater than |
"less_than" | Numeric less than |
"in" | Value is in array (provide "value": [...]) |
"not_in" | Value is not in array |
"is_answered" | Item has been answered (no value needed) |
"is_not_answered" | Item has not been answered |
8e. Sections
A section item creates a logical boundary within the items list. All items
after a section marker (up to the next marker) belong to that section.
Sections control randomization scope, skip logic, and optional back-navigation behavior.
{
"items": [
{ "id": "inst1", "type": "inst", "text_key": "intro_text" },
{
"id": "sec_demographics",
"type": "section",
"title_key": "demographics_title"
},
{ "id": "q1", "type": "short", "text_key": "q1" },
{ "id": "q2", "type": "multi", "text_key": "q2", "options": [...] },
{
"id": "sec_main",
"type": "section",
"title_key": "main_title",
"randomize": { "method": "shuffle", "fixed": ["q3"] }
},
{ "id": "q3", "type": "likert", "text_key": "q3" },
{ "id": "q4", "type": "likert", "text_key": "q4" }
]
}
| Field | Required | Description |
|---|---|---|
id |
Yes | Unique identifier for this section. |
type |
Yes | Must be "section". |
title_key |
No | Translation key for an optional section heading displayed to the participant. |
revisable |
No | Default true. When false, responses in this section
are final — runners must not show a Back button for items in this section.
Omit when true (the default). |
visible_when |
No | Skips the entire section (and all its items) when the condition is false. Evaluated once when the section marker is reached. |
randomize |
No | Randomize item order within this section. Currently supports
{"method": "shuffle", "fixed": ["item_id", ...]}.
The fixed list pins items in their defined positions (equivalent
to setting random_group: 0). |
Implicit first section: Items appearing before the first
section marker are in an implicit unnamed section (always shown,
revisable by default). To control its properties, place a section
marker as the very first item.
8f. Input Validation
For short and long items, a validation object
specifies constraints checked before advancing. Multiple constraints may coexist;
each has a paired *_error key naming the translation string for the error
message shown when that constraint fails.
{
"id": "age",
"type": "short",
"text_key": "age_question",
"validation": {
"number_min": 0,
"number_min_error": "age_too_low",
"number_max": 150,
"number_max_error": "age_too_high"
}
}
{
"id": "bio",
"type": "long",
"text_key": "bio_question",
"validation": {
"min_words": 10,
"min_words_error": "bio_too_short",
"max_length": 500,
"max_length_error": "bio_too_long"
}
}
{
"id": "hobbies",
"type": "multicheck",
"text_key": "hobbies_question",
"options": [...],
"validation": {
"min_selected": 1,
"min_selected_error": "hobbies_min",
"max_selected": 3,
"max_selected_error": "hobbies_max"
}
}
| Constraint | Applies to | Description |
|---|---|---|
min_length | short, long | Minimum character count |
max_length | short, long | Maximum character count |
min_words | short, long | Minimum word count |
max_words | short, long | Maximum word count |
number_min | short | Minimum numeric value (also restricts input to digits) |
number_max | short | Maximum numeric value |
pattern | short | Regular expression — response must match |
min_selected | multicheck | Minimum options checked |
max_selected | multicheck | Maximum options checked |
Item Examples
// A standard likert item
{ "id": "phq1", "text_key": "phq1", "type": "likert" }
// A multi item with custom options (pre-reversed for scoring)
{
"id": "sf36_q11b",
"text_key": "sf36_q11b",
"type": "multi",
"options": [
{ "text_key": "sf36_hp1", "value": 5 },
{ "text_key": "sf36_hp2", "value": 4 },
{ "text_key": "sf36_hp3", "value": 3 },
{ "text_key": "sf36_hp4", "value": 2 },
{ "text_key": "sf36_hp5", "value": 1 }
]
}
// An instruction screen (not scored)
{ "id": "sf36_inst1", "text_key": "sf36_inst1", "type": "inst" }
// A number item with constraints
{
"id": "age",
"text_key": "age_question",
"type": "number",
"min": 0,
"max": 120,
"step": 1
}
// An item shown only when a prior item equals 1
{
"id": "followup1",
"text_key": "followup1",
"type": "likert",
"visible_when": { "item": "screen1", "operator": "equals", "value": 1 }
}
// A section marker with shuffle (keeps q3 in place)
{
"id": "sec_main",
"type": "section",
"title_key": "main_title",
"randomize": { "method": "shuffle", "fixed": ["q3"] }
}
9. scoring Object
Keyed by dimension id. Each value is a scoring definition object specifying
which items contribute to the dimension score and how they are aggregated.
| Field | Type | Required | Description |
|---|---|---|---|
method |
string | Yes | One of: "sum_coded", "mean_coded",
"weighted_sum", "sum_correct",
"max", "min". See the method table below. |
items |
array of strings | See note | Item id values to include. Only items listed in
item_coding with a non-zero value are included in the score.
Required unless scores is used instead. |
scores |
array of strings | No | Dimension IDs whose already-computed scores are used as inputs, instead of
or alongside items. Enables higher-order scores (e.g., summing
subscale scores). item_coding applies to score references just
as to item references. Runners must evaluate blocks in dependency order. |
item_coding |
object | Yes | Maps each item/score id to 1 (forward), -1
(reverse), or 0 (exclude). For likert items,
-1 reverses using min + max − response. For
multi items, -1 has no effect — pre-reverse
the option values instead. Items absent from item_coding
are excluded. |
weights |
object | For weighted_sum |
Per-item weight values applied to each item's contribution. |
correct_answers |
object | For sum_correct |
Maps each item id to an array of acceptable answers (case-insensitive matching
recommended). E.g. {"q1": ["5", "five"]}. |
description |
string | No | Human-readable description of the score range, direction, and interpretation. Shown in the catalogue and report. |
norms |
object or null | No | Interpretation thresholds. See Section 9a. Set to null to omit. |
Scoring Methods
| Method | Description |
|---|---|
sum_coded | Sum of coded item/score contributions |
mean_coded | Mean of coded item/score contributions |
weighted_sum | Weighted sum; requires a weights object |
sum_correct | Count of correct answers; requires a correct_answers object |
max | Maximum value across inputs (e.g., worst symptom across alternative items) |
min | Minimum value across inputs |
Scoring Calculation
For a likert item with item_coding: 1: contribution = response value.
For a likert item with item_coding: -1: contribution =
likert_options.min + likert_options.max − response.
For sum_coded: dimension score = sum of all item contributions.
For mean_coded: dimension score = mean of all item contributions.
For max / min: dimension score = maximum/minimum response
across all forward-coded inputs (useful for scoring symptom domains as the worst
of several alternative items, e.g. the QIDS-SR sleep subscale).
Using scores for higher-order aggregation
Some scales compute a total score by summing subscale scores rather than raw items.
Use the scores field to reference already-computed dimension scores:
"scoring": {
"sleep": {
"method": "max",
"items": ["item_falling_asleep", "item_sleep_night", "item_waking_up", "item_sleeping_too_much"],
"item_coding": {
"item_falling_asleep": 1, "item_sleep_night": 1,
"item_waking_up": 1, "item_sleeping_too_much": 1
}
},
"QIDS_total": {
"method": "sum_coded",
"scores": ["sleep", "sad_mood", "appetite_weight", "concentration",
"self_view", "death_suicide", "interest", "energy", "psychomotor"],
"item_coding": {
"sleep": 1, "sad_mood": 1, "appetite_weight": 1, "concentration": 1,
"self_view": 1, "death_suicide": 1, "interest": 1, "energy": 1, "psychomotor": 1
},
"description": "Sum of 9 symptom domain scores (0–27)"
}
}
9a. norms Object
An optional object containing an array of threshold objects used by the runner and report template to display interpretation labels (e.g. "Minimal", "Mild", "Moderate") alongside the score.
"norms": {
"thresholds": [
{ "min": 0, "max": 4, "label": "Minimal" },
{ "min": 5, "max": 9, "label": "Mild" },
{ "min": 10, "max": 14, "label": "Moderate" },
{ "min": 15, "max": 19, "label": "Moderately Severe" },
{ "min": 20, "max": 27, "label": "Severe" }
]
}
Each threshold's min and max are inclusive. Thresholds should be
listed in ascending order and must cover the full possible score range without gaps or
overlaps. The label is plain text (not a translation key) so it appears
directly in reports.
Scoring Example — PHQ-9
"scoring": {
"depression": {
"method": "sum_coded",
"items": ["phq1","phq2","phq3","phq4","phq5","phq6","phq7","phq8","phq9"],
"item_coding": {
"phq1": 1, "phq2": 1, "phq3": 1, "phq4": 1, "phq5": 1,
"phq6": 1, "phq7": 1, "phq8": 1, "phq9": 1
},
"description": "Sum of all items (0–27). Severity: 0-4 minimal, 5-9 mild, 10-14 moderate, 15-19 moderately severe, 20-27 severe.",
"norms": {
"thresholds": [
{ "min": 0, "max": 4, "label": "Minimal" },
{ "min": 5, "max": 9, "label": "Mild" },
{ "min": 10, "max": 14, "label": "Moderate" },
{ "min": 15, "max": 19, "label": "Moderately Severe" },
{ "min": 20, "max": 27, "label": "Severe" }
]
}
}
}
Scoring Example — SF-36 (mixed forward/reverse coding)
"ef": {
"method": "sum_coded",
"items": ["sf36_q9a", "sf36_q9e", "sf36_q9g", "sf36_q9i"],
"description": "Energy/Fatigue: sum of 4 items (range 4–24; higher = more energy).",
"item_coding": {
"sf36_q9a": -1,
"sf36_q9e": -1,
"sf36_q9g": 1,
"sf36_q9i": 1
}
}
10. report Object
Configures the HTML report generated after scale completion and available for download from the data dashboard.
| Field | Type | Description |
|---|---|---|
template |
string | Report template name. Currently only "standard" is supported. |
include |
array of strings | Sections to include in the report. Valid values: "timestamp",
"completion_time", "dimension_scores".
Add or remove values to control report content. |
header |
string | Plain text (or a translation key) shown at the top of the report, typically describing what the report shows and any caveats about score interpretation. |
footer_refs |
array of strings | Citation strings shown at the bottom of the report. HTML is allowed (e.g. links to DOIs). |
Example
"report": {
"template": "standard",
"include": ["timestamp", "completion_time", "dimension_scores"],
"header": "PHQ-9 Depression Severity Report. Scores range from 0 to 27.",
"footer_refs": [
"Kroenke, K., Spitzer, R. L., & Williams, J. B. (2001). The PHQ-9. Journal of General Internal Medicine, 16(9), 606-613."
]
}
11. data_output Object
Controls how output data files are named and what columns they contain. All fields are
optional; omitting data_output uses runner defaults.
| Field | Type | Description |
|---|---|---|
individual_file |
string | Filename template for per-participant CSV output.
{subnum} is replaced with the participant identifier at runtime.
Example: "PHQ9-{subnum}.csv". |
pooled_file |
string | Filename for the pooled (all-participants) CSV that is appended to on each run.
Example: "PHQ9-pooled.csv". |
report_file |
string | Filename template for the HTML report.
Example: "PHQ9-report-{subnum}.html". |
columns |
string | Comma-separated list of column names for the individual-participant CSV.
Common columns: subnum, order, timestamp,
question_id, text_key, dimension,
coding, response, rt. |
pooled_columns |
string | Comma-separated list of column names for the pooled CSV. Often a subset of
columns. Set to an empty string to use runner defaults. |
Example
"data_output": {
"individual_file": "PHQ9-{subnum}.csv",
"pooled_file": "PHQ9-pooled.csv",
"report_file": "PHQ9-report-{subnum}.html",
"columns": "subnum,order,timestamp,question_id,text_key,dimension,coding,response,rt",
"pooled_columns": ""
}
12. Translations (translations Object)
The translations object sits at the root level of the .osd file
(alongside osd_version and definition). It is a flat JSON object
per language, mapping string keys to translated display strings. Every key referenced
in the definition must have an entry for the chosen language.
Rules
- Each language is keyed by its ISO 639-1 code:
"en","de", etc. - All values are strings. HTML tags are allowed:
<b>,<em>,<i>,<br>,<a href="…">. - Every
text_keyinitems, every key inlikert_options.labels,likert_options.question_head, and everytext_keyinoptionsarrays must be present. - The key
"debrief"(or the key named inscale_info.debrief_key) must be present. Its value is the message shown to the participant on completion. - English (
"en") is required. Additional languages are optional but encouraged. - Keys not referenced by the definition are ignored.
Example — PHQ-9 English translations
{
"likert_1": "not at all",
"likert_2": "several days",
"likert_3": "more than half the days",
"likert_4": "nearly every day",
"question_head": "Over the <b>last 2 weeks</b>, how often have you been bothered by any of the following problems?",
"phq1": "Little interest or pleasure in doing things",
"phq2": "Feeling down, depressed, or hopeless",
"phq3": "Trouble falling or staying asleep, or sleeping too much",
"phq4": "Feeling tired or having little energy",
"phq5": "Poor appetite or overeating",
"phq6": "Feeling bad about yourself — or that you are a failure or have let yourself or your family down",
"phq7": "Trouble concentrating on things, such as reading the newspaper or watching television",
"phq8": "Moving or speaking so slowly that other people could have noticed? Or the opposite — being so fidgety or restless that you have been moving around a lot more than usual",
"phq9": "Thoughts that you would be better off dead or of hurting yourself in some way",
"debrief": "Thank you for completing this questionnaire."
}
13. Complete Minimal Example
A fully working OSD definition for a fictional 3-item scale: the Example Anxiety Screen (EAS3). This demonstrates the minimum viable structure: 3 likert items, 1 dimension, sum-coded scoring, and norms.
scales/EAS3/EAS3.osd
{
"osd_version": "1.0",
"definition": {
"scale_info": {
"name": "Example Anxiety Screen",
"code": "EAS3",
"abbreviation": "EAS-3",
"description": "A fictional 3-item anxiety screening scale for demonstration purposes.",
"citation": "Author, A. B. (2024). The EAS-3: A demonstration scale. Example Journal, 1(1), 1-5.",
"license": "CC0",
"version": "1.0"
},
"implementation": {
"author": "Shane T. Mueller",
"organization": "OpenScales Project",
"date": "2026-04-01",
"license": "CC BY 4.0",
"license_url": "https://creativecommons.org/licenses/by/4.0/"
},
"likert_options": {
"points": 3,
"min": 0,
"max": 2,
"labels": ["eas_never", "eas_sometimes", "eas_often"],
"question_head": "eas_head"
},
"dimensions": [
{
"id": "anxiety",
"name": "Anxiety Severity",
"description": "Sum of all 3 items. Range 0–6; higher = greater anxiety."
}
],
"items": [
{ "id": "eas_inst", "text_key": "eas_inst", "type": "inst" },
{ "id": "eas1", "text_key": "eas1", "type": "likert" },
{ "id": "eas2", "text_key": "eas2", "type": "likert" },
{ "id": "eas3", "text_key": "eas3", "type": "likert" }
],
"scoring": {
"anxiety": {
"method": "sum_coded",
"items": ["eas1", "eas2", "eas3"],
"item_coding": { "eas1": 1, "eas2": 1, "eas3": 1 },
"description": "Sum of all items (range 0–6).",
"norms": {
"thresholds": [
{ "min": 0, "max": 2, "label": "Minimal" },
{ "min": 3, "max": 4, "label": "Mild" },
{ "min": 5, "max": 6, "label": "Moderate to Severe" }
]
}
}
}
},
"translations": {
"en": {
"eas_head": "Over the <b>past week</b>, how often have you experienced the following?",
"eas_never": "never",
"eas_sometimes": "sometimes",
"eas_often": "often",
"eas_inst": "This short questionnaire asks about feelings of anxiety you may have experienced recently. There are no right or wrong answers.",
"eas1": "Felt nervous, anxious, or on edge",
"eas2": "Been unable to stop or control worrying",
"eas3": "Felt a sense of dread about what might happen",
"debrief": "Thank you for completing the Example Anxiety Screen."
}
}
}
14. Versioning & Compatibility Notes
-
scale_info.versiontracks the version of the individual scale definition file (e.g. when item wording or scoring is corrected), not the OSD format version. - The OSD format itself is at version 1.x. Future versions will maintain backwards compatibility: new optional keys may be added, but existing required keys will not be removed or renamed in a 1.x release.
-
Unknown top-level keys are silently ignored by compliant parsers.
This means implementations can add custom extension keys (e.g.
"_notes") without breaking other runners. -
Minimum required keys for a valid definition:
scale_info(withnameandcode),likert_options(ornull),dimensions,items, andscoring. -
Validation tool: Run
python3 tools/validate_scale.py scales/YOURCODE/to check that all required keys are present, alltext_keyvalues have translation entries, and all scoring item references resolve to valid itemids.
Format Version History
| Version | Date | Changes |
|---|---|---|
| 1.0 | 2026-02-15 | Initial specification |
| 1.0.1 | 2026-02-17 | C9 rewritten: flat multi-constraint validation format; added
min_words/max_words; per-constraint
{field}_error keys |
| 1.0.2 | 2026-02-17 | C5 rewritten: sections replace pages; "type": "section" inline
marker model; random_group clarified as section-scoped;
randomize_pages renamed randomize_sections |
| 1.0.3 | 2026-02-18 | "questions" array key renamed to "items"
(questions retained as alias); C4a added: media embedding via
<img> in item text; section type added;
S1 condition key "question" renamed to "item" |
| 1.0.4 | 2026-02-18 | C4a: remote media sourcing policy — images block remote URLs by default;
remote="true" attribute opts in per tag |
| 1.0.5 | 2026-02-19 | C5: revisable and randomize promoted to named
section fields; S4: per-section randomize takes priority over
scale-level shuffle_questions for that section |
| 1.0.6 | 2026-02-22 | C3: added max and min scoring methods; added
scores field for hierarchical scoring (subscale → total);
added evaluation-order requirement; added QIDS example |
| 1.0.7 | 2026-02-25 | C3: added sd scoring method; added transform field —
optional sequence of affine steps applied to the raw score after the scoring method |
| 1.0.8 | 2026-02-26 | C2: added question_head per-item override;
multi/multicheck options accept plain strings as shorthand;
option value may be a number |
| 1.0.9 | 2026-03-01 | C2: added likert_reverse boolean — displays response buttons in
descending order; stored value is unchanged |
| 1.0.10 | 2026-03-03 | C3: added weighted_mean scoring method; weights field
applies to both weighted_sum and weighted_mean;
C2: grid adaptive rendering note for narrow screens |
| 1.0.11 | 2026-03-19 | C3: added answer_categories for named answer-category sets,
enabling multiple sum_correct dimensions against different answer sets |
| 1.0.12 | 2026-04-01 | C1: added implementation object — metadata about who created the
.osd file and licensing for the digital implementation (distinct from scale content
license); fields: author, organization, date,
license, license_url, notes |