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_KEYGet 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.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
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 dataagreed_to_dua(boolean, required) — must betrue; you are accepting the Data Use Agreement
Response
researcher_id(string)status(string). It ispendingon 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 beginsos_). One Shots and Twofers are queried through this same endpoint.question_id(string, required) — a One Shot usesq1; a Twofer usesq1(lead) orq2(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 onbirth_yearanddiagnosis_year), or a{"value": "<code>", "match_type": "..."}postal geographic filter (only onpostal_code; see below)cohort_exclude(object, optional) — the same shape ascohort(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 withcohortand 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 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(any of the eight record types:radiology,labs,ancestry,medication,supplement,procedure,dietary_protocol, orassistive_device), adimension(any record dimension; see/v1/aggregate/records), and a codedvalue. Thedimensionmay be a named-entity key (medication_drug,supplement_item,procedure_type,dietary_protocol, ordevice_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 arecord_typeand aconditionsarray. Every positive condition in a group must hold on the same contributed record (true within-record intersection), somedication_drug=ocrelizumabANDmedication_effectiveness=very_effectivemeans the same medication record was both, i.e. Ocrevus was rated effective (not just taken). Each condition is{ dimension, value?, bands?, negate? }: usevaluefor 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_dorancestry_yamnaya_percentage; band keys match the banded distribution and come from the catalog), andnegate: trueto exclude participants who have any record matching that condition (for examplemedication_drugis notocrelizumab). Groups are combined with AND. k-anonymity applies to the narrowed cohort.
Response
module_id,question_id(string)cohort_size(integer) — distinct participants who answeredpopulation_total(integer or null),population_total_suppressed(boolean): the cohort-filtered population, the denominator for the response rate.cohort_sizeofpopulation_totalis the share of the cohort that answered this question.population_totalisnullandpopulation_total_suppressedistruewhen 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) 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
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 ofbirth_year,diagnosis_year,age_at_diagnosis,years_since_diagnosiscohort(object, optional) — same cohort filter shape as the responses endpointrecords_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 belowrecord_type(string, optional) —radiology,labs,ancestry,medication,supplement,procedure,dietary_protocol, orassistive_device; 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.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 withrecord_type,dimension, andvalue. Thedimensionmust be a named-entity key (medication_drug,supplement_item,procedure_type,dietary_protocol, ordevice_type) of the same record type as the aggregateddimension; the distribution andcohort_sizeare 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 a400. Combining a filter withprocedure_countryorprocedure_cost_bandis permitted: when you aggregateprocedure_country(optionally with an entity filter such asprocedure_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_bandis 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 fromrecords_filtersabove: 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 medicationsupplement_item— a specific contributed supplementprocedure_type— a specific contributed proceduredietary_protocol— a specific contributed dietdevice_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— success400— invalid request; the response body contains adetailstring describing the problem401— missing or invalid Research Access 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.
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 (onbirth_yearanddiagnosis_year), or a{"value", "match_type"}postal geographic filter (onpostal_code).cohort_exclude(object, optional): the same shape ascohort, 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, socohort_sizeofpopulation_totalis the joint response rate.cells(array of{ value_a, value_b, count }) ornull: the surviving cells, each with at least k=5 distinct participants.nullwhen 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"] }
}'