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_KEYThe 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.appThis 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 dataagreed_to_dua(boolean, required) — must betrue; 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 onbirth_yearanddiagnosis_year), or a{"value": "<code>", "match_type": "..."}postal geographic filter (only onpostal_code; see below)cross_module_filters(array, optional) — each item is an object withmodule_id,question_id, andresponse_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 withrecord_type(radiology,labs, orancestry),dimension(one of the seven record dimensions; see/v1/aggregate/records), and a codedvalue. 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 answeredvalue_distribution(object: option key → distinct participant count) ornullwhen suppressedsuppressed(boolean)suppression_reason(string or null) — one ofbelow_k_anonymity_threshold,policy_suppress,range_below_k_anonymity_threshold(a year range that did not meet the threshold; the range is never widened), orpostal_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 thresholdcohort_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_profilem2_cognitive_symptomsm3_fatiguem4_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 ofbirth_year,diagnosis_year,age_at_diagnosis,years_since_diagnosiscohort(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 belowrecord_type(string, optional) —radiology,labs, orancestry; inferred from the dimension when omitted, and validated for consistency when presentcohort(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 andcohort_sizeare 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 presentradiology_trajectory— overall trajectoryradiology_lesion_count— lesion count, banded when exact values are sparselab_test— which lab tests (LOINC-coded) participants have contributedancestry_component— ancestry components, including ancient steppe ancestryancestry_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— success400— invalid request; the response body contains adetailstring describing the problem401— missing or invalid API key500— server error
Notes on results
- Every result is a population statistic. No individual record is accessible.
cohort_sizeis the count of distinct participants who answered the question.- For
multi_selectquestions, option counts can sum to more thancohort_sizebecause one participant can select multiple options. - When
suppressedistrue,value_distributionisnull— the cohort fell below the k-anonymity threshold. - When
generalization_appliedistrue, the results are at a coarser precision than the raw data (for example, birth years reported as five-year bands). - The
_suppressedkey insidevalue_distributionrepresents options whose individual counts each fell below the threshold, combined into one bucket.