WeCureUs

API Reference

The WeCureUs query engine is a research-accessible HTTP API. The query builder is a convenience layer over the same endpoints documented here; you can call them directly for programmatic and reproducible analysis. Every result is a population statistic. No individual record is accessible through any endpoint. The full legal terms are in the Data Use Agreement.

Authentication

Researchers authenticate with their Research Access Key as a bearer token on every authenticated request:

Authorization: Bearer YOUR_API_KEY

Get your Research Access Key from the one-time reveal in your browser at your first sign-in, or by rotating it from your profile page. It is shown once, is not recoverable, and is never sent by email. Store it securely. WeCureUs stores only a cryptographic hash of your key. To get access, first register and wait for approval, then sign in.

Base URL

https://query-engine-service-541883600362.us-central1.run.app

This is the current development URL. It will change when the wecureus.com domain routing is configured; update your base URL at that time.

POST /v1/researchers/register

POST /v1/researchers/register

Apply to register as a researcher. No authentication required. Registration creates a pending application and does not issue an API key. An operator reviews the application, and if it is approved you are emailed a sign-in link. Your Research Access Key is shown once in your browser when you first sign in.

Request body

  • name (string, required)
  • institution (string, optional — send your affiliation, or omit for an independent registration)
  • stated_purpose (string, required) — a description of your intended use of the data
  • agreed_to_dua (boolean, required) — must be true; you are accepting the Data Use Agreement

Response

  • researcher_id (string)
  • status (string). It is pending on a new application. No Research Access Key is returned here. Your key is shown once in your browser at your first sign-in after approval.
curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/researchers/register \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Dr. Jane Smith",
    "institution": "Example University",
    "stated_purpose": "Studying fatigue patterns in MS.",
    "agreed_to_dua": true
  }'

GET /v1/researchers/me

GET /v1/researchers/me

Return the registration record for the authenticated key. Authentication required.

Response

  • researcher_id (string)
  • name (string)
  • institution (string)
  • registered_at (ISO 8601 timestamp)
  • status (string)
curl https://query-engine-service-541883600362.us-central1.run.app/v1/researchers/me \
  -H "Authorization: Bearer YOUR_API_KEY"

POST /v1/aggregate/responses

POST /v1/aggregate/responses

Run an aggregate query against one question in one module. Authentication required.

Request body

  • module_id (string, required) — a survey module id, or a One Shot / Twofer id (each begins os_). One Shots and Twofers are queried through this same endpoint.
  • question_id (string, required) — a One Shot uses q1; a Twofer uses q1 (lead) or q2 (the gated follow-up).
  • cohort (object, optional) — the cohort filter: profile-cache dimension keys mapped to a value. A value may be a string/number (exact), an array (match any of), a {"min": <int>, "max": <int>} range (only on birth_year and diagnosis_year), or a {"value": "<code>", "match_type": "..."} postal geographic filter (only on postal_code; see below)
  • cohort_exclude (object, optional) — the same shape as cohort (a cohort dimension mapped to a value or an array of values), but it EXCLUDES matching participants: the cohort is narrowed to participants who did NOT match. It is ANDed with cohort and every other filter, validated the same way, and subject to the same k-anonymity. A participant with no recorded value for the dimension is treated as not matching the excluded value, and is kept. Applies to responses queries.
  • cross_module_filters (array, optional) — each item is an object with module_id, question_id, and response_value (a string, or an array of strings for a multi-select question). Multiple filters are combined with AND; the cohort is narrowed to participants whose latest answer to each referenced question matches.
  • records_filters (array, optional) — narrow by contributed records (Direction B). Each item is an object with record_type (any of the eight record types: radiology, labs, ancestry, medication, supplement, procedure, dietary_protocol, or assistive_device), a dimension (any record dimension; see /v1/aggregate/records), and a coded value. The dimension may be a named-entity key (medication_drug, supplement_item, procedure_type, dietary_protocol, or device_type) to narrow the cohort to participants who contributed a specific named treatment or record, for example participants who take a specific medication. The cohort is narrowed to participants who contributed at least one matching record. Free-text fields are not dimensions and are rejected. Multiple filters are combined with AND. k-anonymity applies to the intersection cohort.
  • records_group_filters (array, optional) — same-record multi-attribute cohort narrowers. Each item is an object with a record_type and a conditions array. Every positive condition in a group must hold on the same contributed record (true within-record intersection), so medication_drug=ocrelizumab AND medication_effectiveness=very_effective means the same medication record was both, i.e. Ocrevus was rated effective (not just taken). Each condition is { dimension, value?, bands?, negate? }: use value for a coded/count dimension, bands (an array of one or more band keys such as ["20-30", "40+"], OR-combined so the value is in any listed band) for a float dimension (the banded lab-value and ancestry-percentage dimensions, e.g. lab_vitamin_d or ancestry_yamnaya_percentage; band keys match the banded distribution and come from the catalog), and negate: true to exclude participants who have any record matching that condition (for example medication_drug is not ocrelizumab). Groups are combined with AND. k-anonymity applies to the narrowed cohort.

Response

  • module_id, question_id (string)
  • cohort_size (integer) — distinct participants who answered
  • population_total (integer or null), population_total_suppressed (boolean): the cohort-filtered population, the denominator for the response rate.cohort_size of population_total is the share of the cohort that answered this question. population_total is null and population_total_suppressed is true when the group that did not answer was itself below the threshold (so the exact population cannot be shown). These fields appear on the responses, records, and demographics responses alike.
  • value_distribution (object: option key → distinct participant count) or null when suppressed
  • suppressed (boolean)
  • suppression_reason (string or null) — one of below_k_anonymity_threshold, policy_suppress, range_below_k_anonymity_threshold (a year range that did not meet the threshold; the range is never widened), or postal_cohort_below_k_anonymity_threshold (a postal geographic cohort that did not meet the threshold)
  • k_anonymity_threshold (integer)
  • generalization_applied (boolean), generalization_level (integer or null), generalization_level_label (string or null) — set when the answer buckets were coarsened to meet the threshold
  • cohort_filter_generalized (boolean), cohort_filter_level (string or null), cohort_filter_description (string or null) — describe a cohort-filter adjustment: an exact-year filter that was widened to a nearby band to meet the threshold (cohort_filter_generalized: true), or the geographic level a postal filter was applied at (cohort_filter_generalized: false)

Available modules

All 26 launch modules are live and queryable, along with the current One Shots and Twofers. The authoritative, current catalog of module ids, question ids, response types, cohort dimensions, and record dimensions is available at GET /api/catalog (API-key authenticated, using the same key as your queries). The response returns the modules (each with an id, a title, and its questions, where each question carries a question_id, a response_type, prompt text, and option labels), the One Shots and Twofers, the cohort filter dimensions, and the record dimensions.

Available cohort dimensions

  • ms_subtype, diagnosis_year, birth_year, biological_sex, gender_identity, postal_code, dmt_status

Supported question types

single_select, boolean, year, numeric_integer, multi_select. Free-text questions are never aggregatable.

Basic query

curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/responses \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "module_id": "m1_introduction_profile",
    "question_id": "q6"
  }'

Cohort-filtered query

curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/responses \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "module_id": "m3_fatigue",
    "question_id": "q1.1",
    "cohort": {
      "ms_subtype": "rrms",
      "birth_year": [1975, 1976, 1977]
    }
  }'

Year range query (birth_year / diagnosis_year)

A {"min", "max"} range matches every participant whose year falls within the inclusive bounds. Valid only on birth_year and diagnosis_year. If the range cohort does not meet the threshold the result is suppressed with range_below_k_anonymity_threshold; the range is never automatically widened past the bounds you specified.

curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/responses \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "module_id": "m3_fatigue",
    "question_id": "q1.1",
    "cohort": {
      "birth_year": { "min": 1960, "max": 1980 },
      "diagnosis_year": { "min": 2005, "max": 2015 }
    }
  }'

Postal geographic query

A postal filter takes a value (any postal code) and a match_type: exact (default), prefix (first 3 characters), state (US state or Canadian province derived from the code), region, or country. A plain string value is treated as exact. State- and region-level matching are available for US and Canadian postal codes only; other formats return 400. The applied level is reported back in cohort_filter_description.

# region level: every participant in the same US region as 97031
curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/responses \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "module_id": "m3_fatigue",
    "question_id": "q1.1",
    "cohort": {
      "postal_code": { "value": "97031", "match_type": "region" }
    }
  }'

# state level (Canadian FSA derives the province)
#   "postal_code": { "value": "V6B 1A1", "match_type": "state" }
# prefix level (first 3 characters)
#   "postal_code": { "value": "970", "match_type": "prefix" }
# country level
#   "postal_code": { "value": "97031", "match_type": "country" }
# exact level (plain string, backwards-compatible)
#   "postal_code": "97031"

Cross-module filtered query

Distribution of one question only among participants who gave a specific answer in another module:

curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/responses \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "module_id": "m3_fatigue",
    "question_id": "q1.1",
    "cross_module_filters": [
      {
        "module_id": "m2_cognitive_symptoms",
        "question_id": "q1.1",
        "response_value": "many_a_day"
      }
    ]
  }'

Records-filtered query (Direction B)

Distribution of one question only among participants who contributed a record with a specific coded value — e.g. fatigue severity among participants with cervical spine lesions in their radiology records:

curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/responses \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "module_id": "m3_fatigue",
    "question_id": "q1.1",
    "records_filters": [
      {
        "record_type": "radiology",
        "dimension": "radiology_lesion_location",
        "value": "Cervical Spinal Cord"
      }
    ]
  }'

Same-record group query (records_group_filters)

Distribution of one question among participants whose same medication record is both ocrelizumab and very effective (Ocrevus rated effective), excluding anyone who also takes natalizumab, and whose vitamin D is in the 20-30 or 40+ band:

curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/responses \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "module_id": "m3_fatigue",
    "question_id": "q0",
    "records_group_filters": [
      {
        "record_type": "medication",
        "conditions": [
          { "dimension": "medication_drug", "value": "ocrelizumab" },
          { "dimension": "medication_effectiveness", "value": "very_effective" },
          { "dimension": "medication_drug", "value": "natalizumab", "negate": true }
        ]
      },
      {
        "record_type": "labs",
        "conditions": [
          { "dimension": "lab_vitamin_d", "bands": ["20-30", "40+"] }
        ]
      }
    ]
  }'

POST /v1/aggregate/demographics

POST /v1/aggregate/demographics

Banded distributions over numeric demographic dimensions derived from the introduction module. Authentication required.

Request body

  • dimension (string, required) — one of birth_year, diagnosis_year, age_at_diagnosis, years_since_diagnosis
  • cohort (object, optional) — same cohort filter shape as the responses endpoint
  • records_group_filters (array, optional): same-record contributed-record cohort narrower, the same shape as on /v1/aggregate/responses. Lets a contributed-record value (for example a lab result such as EBV EBNA IgG positive) be used as a cohort filter. Population-defining and protected by the same minimum-cohort threshold.

Response

The same structure as /v1/aggregate/responses: cohort_size, value_distribution (band label → count), suppressed, k_anonymity_threshold, and the generalization fields.

curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/demographics \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dimension": "age_at_diagnosis",
    "cohort": { "ms_subtype": "rrms" }
  }'

POST /v1/aggregate/records

POST /v1/aggregate/records

Aggregate distributions over participant-contributed records across all eight record types: medications, supplements, procedures, diet, devices, radiology reports, lab results, and ancestry. Authentication required. Every count is a number of distinct participants, never a number of records, so a participant who contributed several records is counted once per value.

Request body

  • dimension (string, required) — one of the record dimensions listed below
  • record_type (string, optional) — radiology, labs, ancestry, medication, supplement, procedure, dietary_protocol, or assistive_device; inferred from the dimension when omitted, and validated for consistency when present
  • cohort (object, optional) — the same seven cohort dimensions as the responses endpoint, but exact values only (string / number / array). Year ranges and postal geographic matching are not supported on this endpoint.
  • cross_module_filters (array, optional) — narrow the records pool by module responses (Direction A). Same shape as on /v1/aggregate/responses: module_id, question_id, response_value. The distribution and cohort_size are then computed over the intersection of participants who contributed the record AND match every filter. k-anonymity applies to that intersection.
  • records_filters (array, optional) — named-entity outcome filtering (L3): scope the outcome to a specific named entity, for example the effectiveness reported for ocrelizumab rather than across all medications. Each item is an object with record_type, dimension, and value. The dimension must be a named-entity key (medication_drug, supplement_item, procedure_type, dietary_protocol, or device_type) of the same record type as the aggregated dimension; the distribution and cohort_size are then computed over that entity’s own records, and a below-five entity cohort is suppressed. A filter dimension that is not a named-entity key, or a record-type mismatch, returns a 400. Combining a filter with procedure_country or procedure_cost_band is permitted: when you aggregate procedure_country (optionally with an entity filter such as procedure_type), the engine returns exact countries when every country cohort meets k=5, and automatically coarsens the whole distribution to region if any country is below k=5 (and suppresses if a region is still below k=5). procedure_cost_band is likewise queryable, on its own or combined with an entity filter, under standard k-anonymity.
  • records_group_filters (array, optional): same-record contributed-record cohort narrower, the same shape as on /v1/aggregate/responses. This differs from records_filters above: it is a population-defining cohort filter (which participants enter the cohort), so the filter record type does not have to match the aggregated dimension. Use a contributed-record value (for example a lab result such as EBV EBNA IgG positive) as a cohort filter for a records distribution. Numeric dimensions filter by a fixed band, never a raw value. Protected by the same minimum-cohort threshold.

Available dimensions

There are roughly 74 record dimensions across all eight record types: radiology, labs, ancestry, medication, supplement, procedure, diet (dietary_protocol), and devices (assistive_device). The list is large, so the authoritative current set is in GET /api/catalog. The named-entity dimensions researchers use most, each naming a specific contributed treatment or record, are:

  • medication_drug — a specific contributed medication
  • supplement_item — a specific contributed supplement
  • procedure_type — a specific contributed procedure
  • dietary_protocol — a specific contributed diet
  • device_type — a specific contributed assistive device

Each named-entity dimension is drawn from a controlled vocabulary, so its values are the recognized entities in that vocabulary: medication_drug covers 104 medications, supplement_item 18 supplements, dietary_protocol 43 diets, device_type 53 assistive devices, and procedure_type 9 procedures, alongside a free-entry path for anything not yet in the vocabulary. The query builder’s value pickers list the full vocabulary for each; the authoritative current set is in GET /api/catalog.

The original radiology, labs, and ancestry dimensions remain available: radiology_lesion_location, radiology_enhancement, radiology_trajectory, radiology_lesion_count (lesion count, banded when exact values are sparse), lab_test, ancestry_component (including ancient steppe ancestry), and ancestry_result_type.

Lab result dimensions. The qualitative results of the key MS-related serology and CSF tests are queryable as positive / negative / equivocal distributions: lab_oligoclonal_bands (CSF oligoclonal bands, a core MS diagnostic marker), lab_ebv_vca_igg and lab_ebv_ebna_igg (Epstein-Barr serostatus), and lab_ana. These read a controlled result canonicalized when the record is contributed, never free text.

lab_vitamin_d, lab_igg_index (CSF IgG index), lab_kappa_lambda_ratio (CSF kappa/lambda free light chain ratio), and ancestry_yamnaya_percentage (and the other ancestry percentages) are banded numeric dimensions. Raw values and means are never surfaced. Results are always returned as banded distributions, and k=5 is enforced on every band, so a band with fewer than five participants is widened or suppressed, never shown.

procedure_cost_band and procedure_country are queryable. procedure_country uses automatic country-to-region generalization: exact countries are returned when every country cohort meets k=5; if any country is below k=5 the whole distribution coarsens to region; if a region is still below k=5 it is suppressed. This is what makes the procedure_country and procedure_type-by-country cross-tabs safe to offer.

Response

The same structure as the other aggregate endpoints: cohort_size, value_distribution, suppressed, suppression_reason, k_anonymity_threshold, plus dimension and record_type. The radiology_lesion_count dimension may carry the generalization fields when exact counts are banded. Categorical dimensions can be multi-valued per participant, so their counts can exceed the cohort size.

curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/records \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dimension": "radiology_lesion_location",
    "cohort": { "ms_subtype": "rrms" }
  }'

Cross-module filtered records query (Direction A)

Lesion-location distribution only among participants who also reported a specific module answer — e.g. among participants reporting daily fatigue in M3:

curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/records \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "dimension": "radiology_lesion_location",
    "cross_module_filters": [
      {
        "module_id": "m3_fatigue",
        "question_id": "q1.1",
        "response_value": "many_a_day"
      }
    ]
  }'

Response codes

  • 200 — success
  • 400 — invalid request; the response body contains a detail string describing the problem
  • 401 — missing or invalid Research Access Key
  • 500 — server error

Notes on results

  • Every result is a population statistic. No individual record is accessible.
  • cohort_size is the count of distinct participants who answered the question.
  • For multi_select questions, option counts can sum to more than cohort_size because one participant can select multiple options.
  • When suppressed is true, value_distribution is null — the cohort fell below the k-anonymity threshold.
  • When generalization_applied is true, the results are at a coarser precision than the raw data (for example, birth years reported as five-year bands).
  • The _suppressed key inside value_distribution represents options whose individual counts each fell below the threshold, combined into one bucket.

POST /v1/aggregate/crosstab

POST /v1/aggregate/crosstab

A two-dimensional cross-tabulation: the joint distribution over two dimensions at once, as the count of distinct participants in each (value_a, value_b) cell. Authentication required. This build supports categorical axes only: a single_select, boolean, or multi_select module question, a scalar, label-list, or qualitative-result contributed-record dimension, or a categorical demographic dimension (ms_subtype, biological_sex, gender_identity, dmt_status, or postal_region). Numeric and banded dimensions (the year demographics, lesion and scan counts, banded lab values, and ancestry percentages) are not yet available as axes. The two axes may not be two dimensions of the same record type.

Request body

  • axis_a, axis_b (object, required): each is { "kind": "module", "module_id", "question_id" }, { "kind": "records", "dimension" }, or { "kind": "demographics", "dimension" } (a categorical demographic). The two axes must be different dimensions, and may not be two dimensions of the same record type.
  • cohort (object, optional): the full cohort grammar, identical to the responses endpoint. Each dimension may be an exact value, an array (match any of), a {"min", "max"} year range (on birth_year and diagnosis_year), or a {"value", "match_type"} postal geographic filter (on postal_code).
  • cohort_exclude (object, optional): the same shape as cohort, but it excludes matching participants, exactly as on the responses endpoint.
  • cross_module_filters, records_filters, records_group_filters (array, optional): define the population exactly as on the other aggregate endpoints (Direction A, Direction B, and the same-record group narrower). A contributed-record value, such as a lab result like EBV EBNA IgG positive, can therefore be used as a cross-tabulation cohort filter.

Response

  • axis_a, axis_b (string): the resolved axis descriptors.
  • cohort_size (integer): distinct participants who have a value on both axes.
  • population_total, population_total_suppressed: the cohort-filtered population and its withheld flag, as elsewhere, so cohort_size of population_total is the joint response rate.
  • cells (array of { value_a, value_b, count }) or null: the surviving cells, each with at least k=5 distinct participants. null when the whole result is withheld (cohort below five).
  • suppressed_cell_count, suppressed_participant_count (integer): how many cells, and how many participants across them, were withheld. Suppressed cells and their values are never returned.
  • suppressed (boolean), suppression_reason (string or null), k_anonymity_threshold (integer).
  • cohort_filter_level, cohort_filter_description (string or null): when a non-exact postal cohort filter is applied, the geographic level it matched at and a human-readable description.

Suppression is stricter than a single distribution: every cell below k=5 is suppressed (primary), and complementary suppression then ensures no row or column is left with exactly one suppressed cell, so a suppressed cell cannot be recovered by subtracting the visible cells from a single-dimension total. No row or column margins are returned. A two-by-two table with one small cell therefore suppresses entirely; larger categorical tables keep most cells.

curl -X POST https://query-engine-service-541883600362.us-central1.run.app/v1/aggregate/crosstab \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "axis_a": { "kind": "module", "module_id": "m1_introduction_profile", "question_id": "q1" },
    "axis_b": { "kind": "records", "dimension": "lab_oligoclonal_bands" },
    "cohort": { "ms_subtype": ["rrms", "ppms", "spms"] }
  }'