API Reference

Oracle Simulation API

Generate a complete synthetic respondent-level dataset: submit a survey and quota groups, and the Oracle simulates each respondent answering every question.

Overview

The Simulation API generates a complete synthetic respondent-level dataset. You define who the respondents are (quota groups — target-group cells with explicit counts) and what they answer (a full survey), and the Oracle generates a row per respondent with their demographics and their individual answer to every question. The answer data is returned to you directly — there are no redirect links or external survey tools to reconcile.

Because a run can involve thousands of respondents across up to 30 questions, simulation is an asynchronous job. The lifecycle is:

  1. Create — submit the survey and quota groups; receive a job id.
  2. Track — poll the job, or receive a webhook when it finishes.
  3. Retrieve — pull the respondent-level results.

Base URL: https://app.simsurveys.com/api/v1

Authentication

All requests are authenticated with a Bearer token. Create and manage API keys from the API Dashboard.

Authorization: Bearer sk_live_abc123...
  • Keys are 72 characters long and begin with sk_live_.
  • Keys are shown once at creation and cannot be retrieved again.
  • The key must carry the oracle:simulate scope — select it when creating the key in the dashboard (or contact us to enable Oracle access). A missing, invalid, or expired key returns 401; a key that lacks the scope returns 403.
  • Keep keys server-side. An sk_live_ key is a sensitive credential — never embed it in browser or mobile-client code. The API is called server-to-server and does not support cross-origin (CORS) browser requests.
  • There is no sandbox or test mode — every simulation is billable. Validate integrations against small jobs.

Rate Limiting

Each API key has a configurable rate limit (default 60 requests per minute, range 1–1,000). Every response includes X-RateLimit-Limit, X-RateLimit-Remaining, and X-RateLimit-Reset headers (X-RateLimit-Reset is a Unix epoch-seconds timestamp). Creating a simulation counts as a single request, regardless of respondent count.

When the limit is exceeded, the API returns 429 Too Many Requests with a Retry-After header (in seconds) and the standard typed error envelope:

{
  "error": {
    "type": "rate_limit_error",
    "message": "Rate limit exceeded. Try again later."
  }
}

Job Lifecycle

A simulation job moves through the following statuses:

StatusMeaning
queuedAccepted and waiting to start
processingRespondents are being generated
completedFinished — results are available
failedThe job could not complete; see the error object

Jobs run to completion — there is no cancel operation once a job is created. Simulation is atomic: if any quota group fails to generate, the entire job transitions to failed and no partial results are returned. Once completed, a job's results are retained indefinitely and remain retrievable.

Create a Simulation

POST /api/v1/oracle/simulate

Creates a simulation job and returns immediately with a job id and status: "queued".

Idempotency: because a job is billable, pass an Idempotency-Key header (any unique string) to make retries safe. A replay with the same key returns 200 with the original job at its current status, not a duplicate. Reusing a key with a different request body returns 409 with a conflict_error. A key is bound only once a job is created (2xx) — a request that fails validation does not consume it, so you can retry with a corrected body. Keys are honored for 24 hours.

Request Body

FieldTypeRequiredDescription
model_type string Yes One of: consumer, hcp, social, patient
quota_groups array Yes One or more demographic cells, each with an explicit respondent count. Total sample size N = the sum of all counts (up to 5,000)
survey object Yes The survey instrument. Contains questions (1–30 items)
context string No Study context applied to the whole run. Consumer and patient models only.
webhook_url string No HTTPS URL notified when the job reaches completed or failed. See Webhooks. If omitted, poll for status instead.

Quota Group Object (quota_groups[])

FieldTypeRequiredDescription
count integer Yes Number of respondents to generate for this cell (≥ 1)
demographics object Yes* The demographic profile for this cell (free-form key-value pairs; see the Demographics reference). Consumer, social, and patient models.
medical_specialty string Yes* The cell's specialty — one of the 20 supported specialties. HCP model only (replaces demographics).
targeting_criteria array No Question–answer pairs that condition this cut on prior responses (see Targeting criteria). All models.

* Each group carries the targeting fields appropriate to the chosen model_typedemographics for consumer/social/patient, medical_specialty for HCP.

Targeting criteria

Beyond demographics, you can constrain a quota group by prior answers. Each entry in targeting_criteria is a { question, answer } pair, and the respondents generated for that cut are people who gave those answers. Multiple pairs are combined with AND — the cell is the intersection of its demographics (or specialty) and every criterion. These are conditioning inputs, separate from survey.questions, and do not count against the 30-question survey limit. They are not part of the codebook and not coded — the question and answer are label text, echoed verbatim on each respondent row.

// quota group with targeting criteria

{
  "count": 150,
  "demographics": { "age": "35-44", "gender": "Female" },
  "targeting_criteria": [
    { "question": "Do you own a car?", "answer": "Yes" },
    { "question": "How often do you shop online?", "answer": "Weekly" }
  ]
}

Each respondent generated for a cut echoes its criteria in a targeting_criteria field on the row, alongside demographics and responses:

{
  "respondent_id": "resp_00001",
  "demographics": { "age": "35-44", "gender": "Female" },
  "targeting_criteria": [
    { "question": "Do you own a car?", "answer": "Yes" },
    { "question": "How often do you shop online?", "answer": "Weekly" }
  ],
  "responses": { "q1": 1 }
}

Patient model — conditioning on a diagnosis: for the patient model, a convenience shortcut lets you condition on a condition without writing a full criterion — include a demographics key named exactly "Patient Condition" (title case, with a space) whose value is one of the supported conditions, e.g. { "count": 100, "demographics": { "age": "55-64", "Patient Condition": "Type 2 Diabetes" } }. It is equivalent to a single targeting criterion. The key is case- and spelling-sensitive (a mistyped key like patient_condition is silently treated as an ordinary demographic label), and on the patient model the value is enforced — an unsupported condition returns a validation_error.

Question Object (survey.questions[])

FieldTypeRequiredApplies toDescription
idstringYesall Client-defined identifier, unique within the survey; used as the key for this question's answer in each respondent's responses. Duplicate ids are rejected with a validation_error.
typestringYesall One of: single_select, multi_select, grid, numeric, ranking, text
textstringYesall The question wording (max 5,000 characters)
optionsobject[]Yessingle_select, multi_select, ranking, numeric 2–20 answer options, each { "label": string, "code": string|number }. For numeric, each label is a value or range (e.g. "0", "1-2", "24").
rowsobject[]Yesgrid The row items being rated, each { "label", "code" } (2–20)
columnsobject[]Yesgrid The scale/columns each row is rated against, each { "label", "code" } (2–20)
max_selectionsintegerNomulti_select Maximum options a respondent may select. Defaults to the number of options (no cap). Respondents always select at least one.

Labels & codes. Every question, option, grid row, and grid column carries a client-supplied code (its data value) alongside its human-readable label. The question id is the variable code and text is the variable label; each option/row/column's label is what the respondent sees and its code is what the data stores. Respondent answers are returned as codes, and the submitted survey is echoed on the completed job as the codebook, so the dataset is self-describing.

code is required on every option/row/column (omitting one returns a validation_error) and may be an integer or a string — e.g. 1, "01", or "97" for a DK/refused convention. A code must be unique within its own list — among a question's options, among a grid's rows, and among its columns (a row and a column may share a code, and codes may be reused across questions). Grid rows codes in particular must be unique among rows, since they become the keys of the grid answer object. Duplicate codes within a list are rejected with a validation_error. The codebook echoes each code exactly as submitted (types are preserved), so lookups round-trip — e.g. an option { "label": "Don't know", "code": "97" } comes back in a respondent's answer as the string "97", not the number 97. One caveat: a grid answer uses the row code as a JSON object key, so it is serialized as a string — coerce it when matching against the numeric codebook code, or use string row codes.

Example Request

curl -X POST https://app.simsurveys.com/api/v1/oracle/simulate \
  -H "Authorization: Bearer sk_live_your_key_here" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: 7f3a9c1e-2b4d-4a10-9c8e-1f2a3b4c5d6e" \
  -d '{
    "model_type": "consumer",
    "quota_groups": [
      { "count": 150, "demographics": { "age": "25-34", "gender": "Female" } },
      { "count": 150, "demographics": { "age": "35-44", "gender": "Male" } }
    ],
    "survey": {
      "questions": [
        { "id": "q1", "type": "single_select", "text": "Which brand do you prefer?",
          "options": [ {"label": "Nike", "code": 1}, {"label": "Adidas", "code": 2}, {"label": "New Balance", "code": 3}, {"label": "Puma", "code": 4} ] },
        { "id": "q2", "type": "multi_select", "text": "What matters when buying?", "max_selections": 2,
          "options": [ {"label": "Price", "code": 1}, {"label": "Comfort", "code": 2}, {"label": "Style", "code": 3}, {"label": "Brand", "code": 4} ] },
        { "id": "q3", "type": "grid", "text": "Rate each factor.",
          "rows": [ {"label": "Durability", "code": 1}, {"label": "Style", "code": 2} ],
          "columns": [ {"label": "Not important", "code": 1}, {"label": "Somewhat", "code": 2}, {"label": "Very important", "code": 3} ] },
        { "id": "q4", "type": "numeric", "text": "Pairs bought last year?",
          "options": [ {"label": "0", "code": 1}, {"label": "1-2", "code": 2}, {"label": "3-5", "code": 3}, {"label": "6+", "code": 4} ] },
        { "id": "q5", "type": "ranking", "text": "Rank these brands.",
          "options": [ {"label": "Nike", "code": 1}, {"label": "Adidas", "code": 2}, {"label": "New Balance", "code": 3}, {"label": "Puma", "code": 4} ] },
        { "id": "q6", "type": "text", "text": "Describe your last purchase." }
      ]
    }
  }'

Response (202 Accepted)

{
  "id": "sim_a1b2c3d4e5f6a1b2c3d4e5f6",
  "object": "oracle.simulation",
  "status": "queued",
  "model": "consumer",
  "respondent_count": 300,
  "created": 1770940800
}

Retrieve a Simulation

GET /api/v1/oracle/simulate/{id}

Returns the job's current status. Poll this endpoint until status is completed or failed; on completed, read respondents. Large datasets are paginated.

Query Parameters

ParameterTypeDefaultDescription
limitinteger100Respondents per page (1–1,000)
offsetinteger0Number of respondents to skip

Response (while queued or processing)

Before the job finishes, the response carries status metadata only. The survey codebook, respondents, and pagination fields are omitted until status is completed (and then appear on every page of the result set):

{
  "id": "sim_a1b2c3d4e5f6a1b2c3d4e5f6",
  "object": "oracle.simulation",
  "status": "processing",
  "model": "consumer",
  "respondent_count": 300,
  "created": 1770940800
}

Response (completed)

The completed job echoes the submitted survey (the codebook — labels and codes) and returns each respondent's answers as codes. The survey is included on every page.

{
  "id": "sim_a1b2c3d4e5f6a1b2c3d4e5f6",
  "object": "oracle.simulation",
  "status": "completed",
  "model": "consumer",
  "respondent_count": 300,
  "created": 1770940800,
  "completed": 1770940815,
  "survey": { "questions": [
    { "id": "q1", "type": "single_select", "text": "Which brand do you prefer?",
      "options": [ {"label": "Nike", "code": 1}, {"label": "Adidas", "code": 2}, {"label": "New Balance", "code": 3}, {"label": "Puma", "code": 4} ] },
    { "id": "q2", "type": "multi_select", "text": "What matters when buying?", "max_selections": 2,
      "options": [ {"label": "Price", "code": 1}, {"label": "Comfort", "code": 2}, {"label": "Style", "code": 3}, {"label": "Brand", "code": 4} ] },
    { "id": "q3", "type": "grid", "text": "Rate each factor.",
      "rows": [ {"label": "Durability", "code": 1}, {"label": "Style", "code": 2} ],
      "columns": [ {"label": "Not important", "code": 1}, {"label": "Somewhat", "code": 2}, {"label": "Very important", "code": 3} ] },
    { "id": "q4", "type": "numeric", "text": "Pairs bought last year?",
      "options": [ {"label": "0", "code": 1}, {"label": "1-2", "code": 2}, {"label": "3-5", "code": 3}, {"label": "6+", "code": 4} ] },
    { "id": "q5", "type": "ranking", "text": "Rank these brands.",
      "options": [ {"label": "Nike", "code": 1}, {"label": "Adidas", "code": 2}, {"label": "New Balance", "code": 3}, {"label": "Puma", "code": 4} ] },
    { "id": "q6", "type": "text", "text": "Describe your last purchase." }
  ] },
  "respondents": [
    {
      "respondent_id": "resp_00001",
      "demographics": { "age": "25-34", "gender": "Female" },
      "responses": {
        "q1": 1,
        "q2": [2, 1],
        "q3": { "1": 3, "2": 2 },
        "q4": 2,
        "q5": [1, 3, 2, 4],
        "q6": "I bought running shoes online during a sale."
      }
    }
  ],
  "pagination": { "limit": 100, "offset": 0, "total": 300 }
}
  • The example shows one respondent for brevity; a page returns up to limit rows.
  • respondents and pagination are present only when status is completed.
  • respondent_count is the requested sample size (the sum of quota-group counts). Because generation is atomic, on a completed job it always equals pagination.total. Page against pagination.total; when offset ≥ total, respondents is an empty array.
  • Each response is keyed by the question id and its value is the code of the chosen option(s) — decode via the survey codebook. text answers are returned verbatim (uncoded). The model field echoes the request's model_type.
  • The survey codebook (with labels and codes) is echoed on every page so each page is self-describing.
  • If a group defined targeting_criteria, each of its respondent rows also carries a targeting_criteria field echoing them.
  • created and completed are Unix timestamps in seconds.
  • Completed results are retained indefinitely and can be re-fetched at any time.

Response for HCP and patient jobs

For the hcp model, each respondent row carries medical_specialty in place of demographics. The consumer, social, and patient models all use demographics (the consumer example above); for patient, the input demographic keys are echoed back on each row, including the special "Patient Condition" key when supplied.

{
  "respondent_id": "resp_00001",
  "medical_specialty": "Cardiology & Cardiovascular",
  "responses": { "q1": 1 }
}

List Simulations

GET /api/v1/oracle/simulate

Lists your simulation jobs, most recent first. Because results are retained indefinitely and there is no other lookup, this is how you recover a job id that wasn't persisted. Paginated; optionally filter by status.

Query Parameters

ParameterTypeDefaultDescription
statusstringFilter by status: queued, processing, completed, or failed
limitinteger20Jobs per page (1–100)
offsetinteger0Number of jobs to skip

Response

{
  "object": "list",
  "data": [
    {
      "id": "sim_a1b2c3d4e5f6a1b2c3d4e5f6",
      "object": "oracle.simulation",
      "status": "completed",
      "model": "consumer",
      "respondent_count": 300,
      "created": 1770940800
    }
  ],
  "pagination": { "limit": 20, "offset": 0, "total": 42 }
}

List items are job summaries (no respondents); fetch full results from the retrieve endpoint. When offset ≥ total, data is an empty array. Every request — create, retrieve, and list — counts against your key's rate limit.

Webhooks

If you supply an HTTPS webhook_url when creating a job, the Oracle sends a POST to that URL when the job reaches a terminal status — so you don't have to poll. The payload identifies the job and its outcome; always fetch the data itself from the retrieve endpoint.

POST // to your webhook_url

{
  "id": "evt_9f8e7d6c5b4a3210",
  "object": "oracle.simulation.event",
  "type": "simulation.completed",
  "simulation_id": "sim_a1b2c3d4e5f6a1b2c3d4e5f6",
  "status": "completed",
  "respondent_count": 300,
  "occurred_at": 1770940815
}

The type is simulation.completed or simulation.failed, matching the job status. occurred_at is the Unix-seconds time the event was emitted (distinct from the job's own created).

Verifying signatures

Every delivery is signed so you can confirm it genuinely came from Simsurveys. Two headers are sent:

HeaderValue
X-Simsurveys-TimestampUnix seconds when the request was sent
X-Simsurveys-SignatureHex HMAC-SHA256 of {timestamp}.{raw_body}, keyed with your webhook signing secret

The signing secret is a single account-level secret, found in your dashboard (distinct from your sk_live_ API key). To verify: recompute the lowercase-hex HMAC over timestamp + "." + raw_request_body using that secret, and compare with a constant-time equality against the (lowercase) header value. Reject the request if it doesn't match, or if the timestamp is outside a 5-minute window (replay protection).

import hmac, hashlib, time

def verify(raw_body, timestamp, signature, secret):
    if abs(time.time() - int(timestamp)) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{timestamp}.{raw_body}".encode(), hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature.lower())

Treat the webhook as a hint, not the source of truth. After verifying, re-GET the job to read the authoritative status and results.

A simulation.failed event carries the same fields with status: "failed":

{
  "id": "evt_1a2b3c4d5e6f7890",
  "object": "oracle.simulation.event",
  "type": "simulation.failed",
  "simulation_id": "sim_a1b2c3d4e5f6a1b2c3d4e5f6",
  "status": "failed",
  "respondent_count": 300,
  "occurred_at": 1770940815
}

Delivery & retries

  • Respond with any 2xx to acknowledge. Non-2xx or timeout (>10s) triggers retries with exponential backoff for up to 24 hours.
  • Delivery is best-effort: after 24 hours of failed retries the event is dropped. Always keep polling available as the source of truth.
  • Delivery is at-least-once — the same event may arrive more than once. Deduplicate on the event id.
  • Event payloads follow the same versioning contract as API responses — ignore unrecognized fields.

Answer Encoding by Question Type

Each respondent's responses object maps a question id to its answer. Answers are returned as codes (from the survey codebook), not labels. The value's shape depends on the question type:

TypeValue shapeExample
single_selectthe code of the chosen option1
multi_selectarray of the chosen option codes[2, 1]
gridobject — { rowCode: columnCode } per row{ "1": 3, "2": 2 }
numericthe code of the selected value/range2
rankingarray of option codes, best → worst[1, 3, 2, 4]
textstring — open-ended response (uncoded)"I shop online during sales."
  • single_select returns the code of exactly one of the question's options.
  • multi_select returns between 1 and max_selections option codes (never empty); order is not significant.
  • ranking is exhaustive — it returns all of the question's option codes, in ranked order.
  • numeric returns the code of the selected bucket — not the numeric value or range it labels. Codes need not equal the values (e.g. bucket "1-2" has code 2 here), so always decode via the codebook.
  • grid returns every row's code as a key, each mapped to exactly one column code. Row codes are emitted as JSON string keys even when numeric — coerce when matching them against the codebook.
  • text returns a free-form string and is the only uncoded type.

Code Examples

A full run: create the job, poll until it completes, then page through the respondent-level results.

Python

import requests, time

API_KEY = "sk_live_your_key_here"
BASE_URL = "https://app.simsurveys.com/api/v1"
HEADERS = {"Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json"}

# 1. Create the simulation
job = requests.post(
    f"{BASE_URL}/oracle/simulate",
    headers=HEADERS,
    json={
        "model_type": "consumer",
        "quota_groups": [
            {"count": 150, "demographics": {"age": "25-34", "gender": "Female"}},
            {"count": 150, "demographics": {"age": "35-44", "gender": "Male"}},
        ],
        "survey": {"questions": [
            {"id": "q1", "type": "single_select",
             "text": "Which brand do you prefer?",
             "options": [{"label": "Nike", "code": 1}, {"label": "Adidas", "code": 2}]},
        ]},
    },
).json()

sim_id = job["id"]

# 2. Poll until complete
while job["status"] in ("queued", "processing"):
    time.sleep(2)
    job = requests.get(f"{BASE_URL}/oracle/simulate/{sim_id}", headers=HEADERS).json()

if job["status"] == "failed":
    raise RuntimeError(job["error"]["message"])

# 3. Page through respondent-level results (pagination.total is authoritative)
respondents, offset = [], 0
while True:
    page = requests.get(
        f"{BASE_URL}/oracle/simulate/{sim_id}",
        headers=HEADERS,
        params={"limit": 1000, "offset": offset},
    ).json()
    respondents += page["respondents"]
    offset += len(page["respondents"])
    if offset >= page["pagination"]["total"]:
        break

print(f"Retrieved {len(respondents)} respondents")

JavaScript / Node.js

const API_KEY = "sk_live_your_key_here";
const BASE_URL = "https://app.simsurveys.com/api/v1";
const HEADERS = { Authorization: `Bearer ${API_KEY}`, "Content-Type": "application/json" };
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// 1. Create the simulation
let job = await (await fetch(`${BASE_URL}/oracle/simulate`, {
  method: "POST",
  headers: HEADERS,
  body: JSON.stringify({
    model_type: "consumer",
    quota_groups: [
      { count: 150, demographics: { age: "25-34", gender: "Female" } },
      { count: 150, demographics: { age: "35-44", gender: "Male" } },
    ],
    survey: { questions: [
      { id: "q1", type: "single_select",
        text: "Which brand do you prefer?",
        options: [{ label: "Nike", code: 1 }, { label: "Adidas", code: 2 }] },
    ] },
  }),
})).json();

const simId = job.id;

// 2. Poll until complete
while (["queued", "processing"].includes(job.status)) {
  await sleep(2000);
  job = await (await fetch(`${BASE_URL}/oracle/simulate/${simId}`, { headers: HEADERS })).json();
}
if (job.status === "failed") throw new Error(job.error.message);

// 3. Page through respondent-level results (pagination.total is authoritative)
const respondents = [];
let offset = 0, total = Infinity;
while (offset < total) {
  const page = await (await fetch(
    `${BASE_URL}/oracle/simulate/${simId}?limit=1000&offset=${offset}`,
    { headers: HEADERS },
  )).json();
  respondents.push(...page.respondents);
  offset += page.respondents.length;
  total = page.pagination.total;
}
console.log(`Retrieved ${respondents.length} respondents`);

Errors

Every error response — at any status code — uses the same typed error envelope. Request-time errors (bad body, invalid quotas, too many questions) are returned synchronously from the POST:

{
  "error": {
    "type": "validation_error",
    "message": "survey.questions must contain between 1 and 30 items."
  }
}
TypeHTTP StatusDescription
validation_error400Invalid request body, quota groups, or survey
authentication_error401Missing, invalid, or expired API key
permission_error403Key lacks the oracle:simulate scope
not_found_error404Unknown simulation id
conflict_error409Idempotency-Key reused with a different request body
rate_limit_error429Rate limit exceeded
internal_error500Unexpected server error

A job that fails after being accepted returns status: "failed" with an error object on the GET response. This is a job-level error: the HTTP status is still 200 (the request succeeded; the job did not). It carries the standard job fields plus error, and the error.type is a job-level value (currently generation_error) — a separate, open set from the transport error types above, so handle it independently of HTTP status. respondent_count stays the requested count on a failed job (no rows are produced).

{
  "id": "sim_a1b2c3d4e5f6a1b2c3d4e5f6",
  "object": "oracle.simulation",
  "status": "failed",
  "model": "consumer",
  "respondent_count": 300,
  "created": 1770940800,
  "error": { "type": "generation_error", "message": "A quota group failed to generate; the job was aborted." }
}

HTTP Status Codes

StatusMeaning
202Accepted — simulation job created
200Success — job status/results returned
400Bad request — invalid body, quotas, or survey
401Unauthorized — missing, invalid, or expired API key
403Forbidden — key lacks the oracle:simulate scope
404Not found — unknown simulation id
409Conflict — Idempotency-Key reused with a different request body
429Too many requests — rate limit exceeded
500Internal server error

Limits & Constraints

ConstraintValue
Questions per survey1–30
Answer options per question2–20
Max question length5,000 characters
Respondents per jobUp to 5,000 (sum of all quota-group counts)
Quota groups per jobAt least 1; each count ≥ 1; combined total ≤ 5,000
Grid rows / columns2–20 each
Results pagination100 per page (max 1,000)
Max request body size1 MB
Result retentionIndefinite — completed results never expire
Idempotency-Key window24 hours
Rate limitJob creation counts as one request against your key's limit

Exceeding any of these limits returns a 400 validation_error describing the violated constraint.

Demographics Reference

A quota group's demographics object (consumer, social, and patient models) is free-form: each key-value pair is passed through to the model verbatim. The API validates only that keys and values are strings — it does not enforce a fixed set. The table below is the recommended vocabulary the models were tuned on; using these keys and values gives the most reliable results.

KeyRecommended values
genderMale, Female
age18-24, 25-34, 35-44, 45-54, 55-64, 65-74, 75+
incomeUnder $10k, $10k-15k, $15k-20k, $20k-25k, $25k-30k, $30k-35k, $35k-40k, $40k-45k, $45k-50k, $50k-60k, $60k-75k, $75k-100k, $100k-125k, $125k-150k, $150k-200k, $200k+
locationUrban, Suburban, Rural
race_ethnicityWhite, Black/African American, American Indian/Alaska Native, Asian, Native Hawaiian/Pacific Islander, Other, Multiracial, Hispanic/Latino
educationSome High School or Less, High School Graduate, Some College, College Graduate, Post-Graduate Degree
employmentNot in Labor Force, Employed, Unemployed
childrenNo, Yes
politicalDemocrat, Republican, Libertarian, Green Party, Other
marital_statusMarried, Not Married

For the patient model only, the special key "Patient Condition" conditions the cell on a diagnosis; its value must be one of the supported Patient Conditions (see Create).

Supported Specialties

For the hcp model, a quota group's medical_specialty must exactly match one of these 20 values:

Allied Health & Other Medical ProfessionalsNeurology & Neurosurgery
Anesthesiology & Pain ManagementNursing & Advanced Practice Providers
Cardiology & CardiovascularOncology & Hematology
Dermatology & Skin ConditionsPathology & Laboratory Medicine
Emergency & Critical CarePediatrics & Child Health
Endocrinology & DiabetesPrimary Care & General Practice
Eye & Vision CarePulmonology & Respiratory Medicine
Gastroenterology & Digestive HealthRadiology & Medical Imaging
Internal Medicine SubspecialtiesSurgical Specialties
Mental Health & PsychiatryWomen's Health & Reproductive Medicine

Patient Conditions

For the patient model, the "Patient Condition" value (see Targeting criteria) must exactly match one of these 25 values; an unsupported value returns a validation_error:

Type 2 DiabetesMigraine
Obesity / Weight ManagementAlzheimer's Disease / Dementia
Breast CancerMajor Depressive Disorder
Lung CancerAnxiety Disorders
Colorectal CancerSchizophrenia / Bipolar Disorder
Prostate CancerAsthma
Blood Cancers (Leukemia/Lymphoma/Myeloma)COPD
Rheumatoid ArthritisAtopic Dermatitis / Eczema
Psoriasis / Psoriatic ArthritisChronic Pain / Neuropathic Pain
Inflammatory Bowel Disease (Crohn's/UC)Osteoarthritis
Heart FailureRare Disease
Hypertension / HyperlipidemiaOther
Multiple Sclerosis

Versioning

The current API version is v1, reflected in the URL path (/api/v1). Backwards-incompatible changes ship under a new version prefix; additive changes (new optional request fields, new response fields) may appear within v1, so clients should ignore unrecognized response fields. Unrecognized request fields — and fields that are recognized but not applicable to the chosen model_type (e.g. context or demographics sent to the hcp model, or medical_specialty sent to a non-HCP model) — are rejected with a validation_error.

Start building with the Simulation API.

Generate full synthetic respondent-level datasets programmatically. Create an account and generate your first API key in minutes.