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 API key as a bearer token on every authenticated request:

Authorization: Bearer YOUR_API_KEY

The API key is issued once at registration and is not recoverable. Store it securely. WeCureUs stores only a cryptographic hash of your key.

Base URL

https://query-engine-service-lbjiulf7yq-uc.a.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

Register as a researcher and receive an API key. No authentication required. Registration is processed automatically.

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)
  • api_key (string) — shown once only
curl -X POST https://query-engine-service-lbjiulf7yq-uc.a.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-lbjiulf7yq-uc.a.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)
  • question_id (string, required)
  • 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)
  • 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 (radiology, labs, or ancestry), dimension (one of the seven record dimensions; see /v1/aggregate/records), and a coded value. 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.

Response

  • module_id, question_id (string)
  • cohort_size (integer) — distinct participants who answered
  • 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

  • m1_introduction_profile
  • m2_cognitive_symptoms
  • m3_fatigue
  • m4_diagnosis_journey

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-lbjiulf7yq-uc.a.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-lbjiulf7yq-uc.a.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-lbjiulf7yq-uc.a.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-lbjiulf7yq-uc.a.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-lbjiulf7yq-uc.a.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-lbjiulf7yq-uc.a.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"
      }
    ]
  }'

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

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-lbjiulf7yq-uc.a.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 (the YOUR RECORDS track: radiology reports, lab results, 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 seven listed below
  • record_type (string, optional) — radiology, labs, or ancestry; 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.

Available dimensions

  • radiology_lesion_location — MS lesion locations (controlled vocabulary)
  • radiology_enhancement — contrast enhancement present
  • radiology_trajectory — overall trajectory
  • radiology_lesion_count — lesion count, banded when exact values are sparse
  • lab_test — which lab tests (LOINC-coded) participants have contributed
  • ancestry_component — ancestry components, including ancient steppe ancestry
  • ancestry_result_type — modern-ethnicity vs ancient-admixture estimate

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-lbjiulf7yq-uc.a.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-lbjiulf7yq-uc.a.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 API 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.