{"openapi":"3.1.0","info":{"title":"Simulacra Headless API","description":"## What Is Simulacra?\n\nSimulacra is a research simulation and what-if scenario modeling\nplatform for consumer and market research teams. It augments existing\nstudies with high-fidelity synthetic data so teams can expand sample\nsizes, rebalance cohorts, explore low-incidence audiences, and build\nscenario models from the data they already trust.\n\nThe differentiator is conditioning: instead of only asking for more\nrows, you can ask what the full dataset should look like under a\nspecific desired outcome mix, such as a premium-heavy segment, a\nyounger target audience, or a high-intent buying scenario. Simulacra\nthen generates a coherent synthetic dataset around that scenario,\nsubject to feasibility under the trained model.\n\nThe Headless API exposes that workflow programmatically: upload a seed\ndataset, wait for training, generate scenario-conditioned synthetic\nrows, and download the result as Parquet, CSV, or Arrow.\n\n## Security, Compliance, And Retention\n\nThis API serves approved company tenants, not anonymous public\ntraffic. Resource routes require Auth0 machine-to-machine bearer\ntokens, operator actions are audited, and production infrastructure\nis monitored with security alerts and health probes.\n\n![ISO/IEC 27001 certified: Synthetic Data Studio and Headless API](/assets/compliance/iso27001.png)\n\nThe Headless API is covered by Simulacra's ISO/IEC 27001:2022\ncertification, within the same information security management\nsystem as the Synthetic Data Studio. Simulacra's current SOC 2\nType II and public SOC 3 reports cover the Studio; the API launched\nafter that audit period closed, and its SOC 2 coverage is planned\nfor 2027.\n\nCurrent reports, certification details, and product scope are available\nthrough the Security & Trust Center. Retained datasets, trained models,\ngenerated datasets, and download URLs follow explicit retention windows.\n\n- **Authentication:** OAuth2 client-credentials through Auth0. Treat\n  `client_secret` as a production secret and store it in your secret\n  manager.\n- **Authorization:** credentials are tenant-scoped and tied to a\n  Simulacra-approved company. Simulacra team members do not handle\n  customer `client_secret` values.\n- **Transport and storage:** API traffic uses TLS. Retained customer\n  artifacts are encrypted at rest; standard managed mode uses\n  Simulacra-managed controls, while enterprise storage mode can\n  deliver artifacts through customer-controlled storage/key paths.\n- **Default retention:** trained dataset artifacts and generated\n  outputs default to 24-hour retention. Explicit extension is bounded\n  by a 7-day maximum continuous dataset retention window. Managed\n  artifact download URLs are short-lived, with a 15-minute default.\n- **Delete semantics:** `DELETE /v1/datasets/{dataset_id}` removes\n  active dataset access and associated retrievable dataset artifacts\n  from the API surface; generation artifacts expire on their own\n  retention windows.\n- **Secrets and claims:** approved signup and rotation secrets are\n  delivered through encrypted one-time credential claims. Claim\n  tokens expire and cannot be reused after the secret is claimed.\n- **Reliability:** long-running work is represented as async jobs.\n  Preserve `X-Request-Id`, body `request_id`, `job_id`, `dataset_id`,\n  and `generation_id` in your own support logs.\n- **SLA and procurement:** these defaults are platform controls.\n  Uptime commitments, support response targets, data-processing\n  terms, and audit report access are governed by your enterprise\n  agreement with Simulacra.\n- **Public references:** [API Security](https://www.simulacra-data.com/security/api),\n  [Trust Center](https://www.simulacra-data.com/security) (product-scoped assurance reports and certification materials),\n  [Privacy](https://www.simulacra-data.com/privacy),\n  [Terms](https://www.simulacra-data.com/terms),\n  [SLA](https://www.simulacra-data.com/sla), and\n  [Subprocessors](https://www.simulacra-data.com/subprocessors).\n\n## End-To-End Setup Flow\n\nUse this as the happy path for a first integration. The endpoint\npages below are detailed references; this flow shows how their\nvalues connect.\n\n1. Request access with `POST /v1/signup`; save the returned\n   `request_id` immediately. It is disclosed only when the pending\n   row is first created. A same-email replay is acknowledged but\n   never returns an existing request identifier or state.\n2. Poll `GET /v1/signup/{request_id}` until the request is\n   approved.\n3. Exchange the approved `credential_claim_token` at\n   `POST /v1/credential-claims`; store the returned\n   `client_secret` immediately.\n4. Mint an Auth0 bearer token with the client credentials.\n5. If the questionnaire has skip logic, validate the short routing\n   declaration with `POST /v1/route-plans/validate`; no fit or\n   persistence occurs.\n6. Upload a seed dataset with `POST /v1/datasets`; save\n   `dataset_id` and poll `job_id` when the response is async.\n   Prepare the file first (one row per respondent, standardized\n   blanks, `missing_data` chosen); the public guide\n   [Preparing survey data](https://www.simulacra-data.com/resources/preparing-survey-data)\n   covers the semantic preparation rules.\n7. Inspect `GET /v1/datasets/{dataset_id}/schema`; use this\n   cleaned schema, not your original headers, to build\n   conditions.\n8. Generate synthetic rows with\n   `POST /v1/datasets/{dataset_id}/generations`; save\n   `generation_id` and poll `job_id` when needed.\n9. Fetch `GET /v1/generations/{generation_id}` until status is\n   `ready` or `partial` and `artifact_url` is present. `partial`\n   means the scenario was valid but fewer rows were feasible than\n   requested; usage-billing is counted on generated rows rather\n   than requested rows.\n10. Download the artifact. Managed-mode URLs point back to this\n   API and still require the bearer token; enterprise URLs may be\n   absolute customer-storage URLs.\n\n## What's New\n\n- 2026-09-03 — API 1.7.0:\n  - Generation request bodies now accept only the documented fields:\n    `row_count`, `output_format`, `conditions`, `wait_seconds`, and\n    `idempotency_key`. Any other field name, a repeated field, or a\n    `conditions` member other than `categorical` or `numeric` returns a\n    400 response naming the field. Remove any extra or repeated fields\n    from existing requests.\n  - Dataset uploads now return a 400 response for duplicated multipart\n    fields. Oversized JSON request bodies return a 413 response.\n  - Dataset creation and generation now return promptly when work\n    continues asynchronously. `wait_seconds` remains accepted for\n    compatibility but does not hold the request open. Poll the returned\n    `job_id` after a 202 response.\n\n- 2026-09-02 — API 1.6.25:\n  - Malformed conditions now return a 400 response instead of producing\n    an unconditioned generation.\n  - Generation requests now accept valid idempotency keys up to 128\n    characters.\n  - Pagination cursors beyond the available results now return an empty\n    page. Out-of-range `row_count` values and upload form fields that are\n    not valid UTF-8 now return a 400 response explaining what to correct.\n\n- 2026-09-02 — API 1.6.24:\n  - Malformed credential claim fields now return a 400 response that\n    explains what to correct.\n  - Invalid pagination fields now return a 400 response that explains\n    what to correct. When provided, a generation request body’s\n    `idempotency_key` must be a JSON string.\n  - `Idempotency-Key` headers are now honored on dataset uploads and\n    generation requests. If both a header and a body or form field are\n    provided, the body or form field takes precedence.\n\n- 2026-08-22 — API 1.6.0:\n  - Datasets with a wave or year column can now add a one-list\n    `model_declaration` naming numeric columns to model as\n    continuous measures. Declared columns keep their scale and type\n    in generated data, and a stable declared axis enables forward\n    conditioning: generating the next wave beyond the observed\n    data. When recent waves moved too much to project, Simulacra\n    refuses and names the columns that moved.\n  - The declaration works with either missing_data mode, combines\n    with route_plan, and is inert when absent — undeclared datasets\n    behave exactly as before. Dataset schema responses annotate\n    declared columns with model_as = continuous.\n  - See [Model Declarations](#model-declarations) for the YAML/JSON\n    format and the upload workflow.\n  - Routing and modeling-declaration validation errors now carry\n    stable machine-readable codes in the problem body: the four\n    route_plan_* codes and the two simio_model_declaration_* codes\n    are registered in the error catalog with fixed HTTP statuses\n    and dereferenceable type URLs.\n\n- 2026-08-13 — API 1.5.4:\n  - Improved support for surveys with known skip or display logic. You\n    can now add a short `route_plan` so generated data follows the\n    questionnaire's declared paths exactly, including deeply routed\n    batteries. Every upload still chooses `missing_data=complete` or\n    `missing_data=preserve`; routing is optional and is used with\n    `preserve`.\n  - Each rule says only when a question is shown and which columns it\n    opens. `POST /v1/route-plans/validate` checks those rules against your\n    upload before fitting and explains any names, values, answers, or rule\n    shapes that need attention.\n  - See [Routing Declarations](#routing-declarations) for the YAML/JSON\n    format, a runnable quickstart fixture, and the upload workflow.\n\n- 2026-07-30 — API 1.2.3:\n  - SPSS uploads that cannot be parsed now return actionable guidance\n    instead of a generic server error. Re-export from SPSS or upload\n    the equivalent data as CSV or XLSX.\n  - Cleaning now retains every nonconstant numeric column, including\n    product-static QDA measures and other substantive exact linear\n    relationships. Only missing-free numeric constants are removed.\n  - Fixed-width SPSS blank strings are normalized to missing values\n    at ingestion.\n\n- 2026-07-30 — API 1.2.2:\n  - Column-name collisions created during normalization are now\n    disambiguated rather than dropped. For example, `my var` and\n    `my_var` become `my.var...1` and `my.var...2`. Build conditions\n    from the names returned by\n    `GET /v1/datasets/{dataset_id}/schema`.\n  - You now get a clear `400` error when a `.sav` file is damaged or\n    unsupported. Re-export or replace the file, then try the upload\n    again.\n\n- 2026-07-29 — API 1.2.0:\n  - New dataset option: `POST /v1/datasets` accepts\n    `missing_data=complete|preserve` as a multipart form field.\n    `complete` remains the default: categorical NA is modeled as\n    a distinct category, while missing continuous values are\n    predicted. `preserve` learns the seed data's missingness patterns\n    and reproduces them in generated data.\n  - The mode is selected once for a dataset. Create a new dataset\n    to use a different mode.\n  - Dataset validation, cleaning, and schema reporting remain\n    included in both modes. Responses report the selected mode in\n    `cleaning_summary.missing_data`; the dataset schema endpoint\n    remains authoritative for delivered columns and types.\n\n- 2026-07-23 — API 0.1.8:\n  - Changed: generation is stochastic and the former `seed`\n    request field is retired. Omit it; supplied values return\n    `400 simio_invalid_field` without charging quota.\n\n- 2026-07-22:\n  - New endpoint: `GET /v1/usage` — your company's current quota\n    state: subscription window, committed and reserved rows,\n    remaining headroom, and your integration's single-request row\n    limit.\n  - New field: completed and partial generation responses include\n    `rows_charged` — the rows committed to your quota for that\n    generation. A partial generation charges the rows delivered,\n    not the rows requested; a processing response has no charge\n    yet. `GET /v1/usage` is the authoritative company-level\n    snapshot.\n  - Expanded: `cleaning_summary` on dataset creation and detail\n    responses now documents columns dropped and why, rows removed,\n    type changes, and the retained schema.\n    `GET /v1/datasets/{dataset_id}/schema` remains authoritative\n    for final types, levels, and ranges.\n  - Changed: cleaning always runs. The `auto_clean` request field\n    is retired — omit it. A supplied `true` is accepted for\n    compatibility; `false` returns `400 simio_invalid_field`.\n  - Changed: preparation preserves every uploaded row and every\n    nonconstant field, including sparse categorical responses.\n    Only information-free constants, exact redundant numeric\n    fields, identifiers, and unsupported datetimes may be removed.\n  - Changed: signup responses return `request_id` only on the\n    initial submission. Save it — you use it to poll status and\n    claim your credentials.\n\n## Client Integration\n\n`/openapi.json` is the canonical machine-readable contract for\ngenerated clients and API tooling. Direct HTTPS clients should\nfollow the examples below and the documented helper patterns for\nAuth0 token minting, signup polling, one-time credential claims,\nidempotent retries, async job polling, schema-first conditions,\nartifact download, and problem-code classification.\n\nKeep generated clients thin: preserve raw response fields, pass\nthrough `X-Request-Id` and problem `code` values, and put polling,\nretry, schema-resolution, and download behavior in a small helper\nlayer owned by your application.\n\n## Common Mistakes To Avoid\n\n- Do not upload response logs or unprepared platform exports.\n  Semantic preparation (what a blank means, one-hot collapses,\n  sentinel codes, `complete` vs `preserve`) is covered in\n  [Preparing survey data](https://www.simulacra-data.com/resources/preparing-survey-data).\n- Do not build conditions from original column names. Training can\n  rename columns into identifier-safe form, for example\n  `purchase_intent` may become `purchase.intent` depending on the\n  cleaning path. Always GET the schema first.\n- Preparation preserves nonconstant fields and sparse categorical\n  responses. It can still remove identifiers, strict constants, exact\n  redundant numeric fields, and unsupported datetimes. If a column is\n  not in the schema response, do not condition on it.\n- `credential_claim_token` is one-time-use. Do not close the\n  response before storing the returned `client_secret` in your\n  secret manager.\n- Use an `Idempotency-Key` on every POST retry. Retrying without\n  one can create duplicate work and duplicate usage charges.\n- Tight scenarios can produce fewer rows than requested even when\n  quota remains; Simulacra will never return more rows than\n  `row_count`. Usage-billing is counted on generated rows rather\n  than requested rows.\n- Do not put bearer tokens, client secrets, artifact URLs, or\n  customer data in chat, browser consoles, notebooks shared with\n  third parties, or application logs.\n\n## Retries, Quotas, And Billing\n\n- `202 Accepted` is normal for dataset training and generation.\n  Poll `/v1/jobs/{job_id}` every 2 seconds for quickstarts; production\n  clients should use jittered exponential backoff with a deadline.\n- Retry `POST /v1/datasets` and\n  `POST /v1/datasets/{dataset_id}/generations` only with an\n  `Idempotency-Key`. Reusing the same key makes the retry safe;\n  changing the key creates new work.\n- Retry transient `429`, `500`, `502`, `503`, and `504` responses\n  with backoff. Do not retry `400`, `401`, `402`, `403`, or `404`\n  without changing the request.\n- `402` means the request exceeds your company's active row\n  subscription or request-cap.\n- Per-request row caps are safety limits. Tight scenarios can still\n  produce fewer rows than requested even when quota remains;\n  usage-billing is counted on generated rows rather than requested\n  rows.\n\n## Error Catalog\n\nProblem responses include `type`, `title`, `status`, and `detail`.\nWhen the API can classify the failure, the body also includes a stable\n`code` such as `simio_unknown_condition_column`, and `type` points to\n`https://api.simulacra-data.com/errors/{code}`. Open that URL for\nthe cause, fix, retryability, and support guidance. Preserve\n`X-Request-Id` and response-body `request_id` values when contacting\nsupport.\n\nError codes are part of the v1 contract. New codes may be added;\nrenames or removals require a migration window.\n\n- Catalog index: `/errors`\n- Example: `/errors/simio_unknown_condition_column`\n\n## Troubleshooting\n\n- **401 Unauthorized:** mint a fresh Auth0 token and verify the\n  audience is `https://api.simulacra-data.com`. The Authorize\n  panel and the `Authorization: Bearer …` header expect the JWT\n  returned by Auth0 (begins with `eyJ…`), NOT your `client_secret`.\n  See *Authorize The Interactive Panel* above for the exchange.\n  If this happens during upload, reselect the seed file before\n  retrying.\n- **400 request body is empty:** set `Content-Type: application/json`\n  for JSON endpoints and use multipart form-data only for dataset\n  uploads.\n- **400 unknown condition column or level:** call\n  `/v1/datasets/{dataset_id}/schema` and rebuild conditions from the\n  cleaned schema. Original seed names may have been normalized.\n- **202 keeps polling:** keep polling until `completed`, `failed`,\n  `expired`, or `cancelled`; use a deadline and preserve `job_id`.\n- **partial generation:** the scenario was feasible only for a subset\n  of the requested rows. Inspect `rows_generated` before using the\n  artifact downstream.\n- **404 on copied IDs:** identifier fields are JSON strings. If your\n  client still indexes `[0]`, it may be sending a one-character ID.\n\n## Request Access\n\nAPI access is request-and-approve. Before any of the credentials in\nthe Quickstart will work you need an approved tenant.\n\n1. `POST /v1/signup` with your `company_name` and `contact_email`.\n   No login is required for this access request; leave the\n   Authorization field blank. A newly created request includes a\n   `request_id`; save it immediately because email-only replays\n   never repeat an existing request identifier.\n2. `GET /v1/signup/{request_id}` returns `pending`, `approved`, or\n   `declined`. This check is also open because credentials do not\n   exist until the request is approved.\n3. Once Simulacra approves, the status response includes a\n   `client_id` and a one-time `credential_claim_token`.\n4. `POST /v1/credential-claims` with that token returns the\n   `client_secret` exactly once, plus the Auth0 token URL and\n   audience. Store it in your secret manager immediately.\n\n```sh\nRESP=$(curl -sS -X POST \"https://api.simulacra-data.com/v1/signup\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\n    \"company_name\": \"Acme Research\",\n    \"contact_email\": \"data-science@acme.example\"\n  }')\n# 202 Accepted. A newly created request includes request_id; a\n# same-email replay returns only {\"status\":\"accepted\"}.\n# Save request_id immediately; the polling URL needs it verbatim.\n# If this command has no request_id, use the value saved from the\n# original submission or contact Simulacra support.\n# Valid format\n# is ^req_[A-Za-z0-9]{1,64}$ — no dots, dashes, or whitespace.\nREQUEST_ID=$(echo \"${RESP}\" | jq -r .request_id)\n\ncurl -sS \"https://api.simulacra-data.com/v1/signup/${REQUEST_ID}\"\n# -> pending until an operator approves; then status flips to\n#    \"approved\" and includes `client_id` plus a one-time\n#    `credential_claim_token`.\n\nAPPROVAL=$(curl -sS \"https://api.simulacra-data.com/v1/signup/${REQUEST_ID}\")\nCLAIM_TOKEN=$(echo \"${APPROVAL}\" | jq -r .credential_claim_token)\n\ncurl -sS -X POST \"https://api.simulacra-data.com/v1/credential-claims\" \\\n  -H \"content-type: application/json\" \\\n  -d \"$(jq -nc --arg token \"${CLAIM_TOKEN}\" '{claim_token: $token}')\"\n# -> returns client_id, client_secret, token_url, audience, grant_type.\n#    The claim token is one-time-use; put client_secret in your\n#    secret manager, not in source code or chat.\n```\n\nIf you are testing this from the interactive docs panel below, click\n**TRY** on `POST /v1/signup`, fill the `company_name` and\n`contact_email` fields, and **leave the Authorization field blank** —\nthis is the access-request step before credentials exist.\n\n## Authorize The Interactive Panel\n\nThe interactive docs authenticate with the **HTTP Bearer** field\ninside the **AUTHENTICATION** panel. That field expects an Auth0 JWT\naccess token, not your `client_secret`. JWTs always start with `eyJ`\nand contain two dots; your `client_secret` has no fixed prefix and\nis a single high-entropy ~64-character string with no dots. Paste\nthe wrong one and every protected call returns 401.\n\nClick **AUTHENTICATION** in the left navigation. If you already\nhave a JWT, paste it into the HTTP Bearer field. If you only have\nyour `client_id` and `client_secret`, use the *Exchange credentials\nand fill HTTP Bearer* form in that same panel. It exchanges the\ncredentials server-side and loads the JWT into HTTP Bearer for you.\nTry-It on protected endpoints then succeeds. The token is valid for\n~24 hours; rerun the form when it expires.\n\nIf you prefer the command line:\n\n```sh\nACCESS_TOKEN=$(curl -sS -X POST \"https://simulacra-data.us.auth0.com/oauth/token\" \\\n  -H \"content-type: application/json\" \\\n  -d '{\n    \"client_id\":     \"YOUR_CLIENT_ID\",\n    \"client_secret\": \"YOUR_CLIENT_SECRET\",\n    \"audience\":      \"https://api.simulacra-data.com\",\n    \"grant_type\":    \"client_credentials\"\n  }' | jq -r .access_token)\n\necho \"$ACCESS_TOKEN\"\n```\n\n### Rotating or replacing your client_secret\n\nIf you still have a working `client_secret` (or a valid bearer\ntoken minted from it), use `POST /v1/credential-rotations` — it\nrotates the secret at Auth0 and returns a one-time\n`credential_claim_token` you redeem at `POST /v1/credential-claims`\nfor the new `client_secret`. The endpoint is authenticated; the\nrate limit is three rotations per 24 hours per client.\n\nIn the interactive docs, `POST /v1/credential-rotations` gets a\nper-tab `Idempotency-Key` automatically. If the browser reloads\nor the response disappears before you redeem the claim token, retry\nthe same operation in that tab; the API returns the same\n`credential_claim_token` without rotating Auth0 again.\n\nIf you have lost the `client_secret` entirely and cannot\nauthenticate, email\n[support@simulacra-data.com](mailto:support@simulacra-data.com)\nand reference your `client_id`. Simulacra will rotate operator-side\nand deliver the new `credential_claim_token` over a secure channel.\nResubmitting the signup form does NOT re-issue a\n`credential_claim_token` once your initial credential has been\nclaimed.\n\n## Quickstart\n\nPick the language tab that matches your stack. Each script below is a\ncomplete, runnable end-to-end flow: it mints a bearer token, uploads a\ntoy seed dataset, polls until training finishes, fetches the trained\nschema, generates scenario-conditioned synthetic rows, downloads the\nartifact, and reads it back. The flow is identical across languages —\nonly the syntax changes — and the conditioning request body is the\nsame JSON structure everywhere.\n\nThe scenario examples are intentionally built from normal client-side\nobjects — pandas data frames, R data frames, Julia DataFrames, or a\nshell variable for SPSS automation. Your HTTP client serializes those\nobjects to JSON; you should not be hand-maintaining JSON files in a\nproduction integration.\n\nAlways fetch `/v1/datasets/{dataset_id}/schema` before building\nconditions. The trained schema is the customer-facing contract\nafter cleaning: names may be normalized, columns may be dropped,\nand rare categorical levels may be removed.\nThe examples below resolve condition columns from the returned\nschema before submitting the generation request.\n\nEvery upload selects `missing_data`. These examples use `complete`,\nthe default: categorical NA is modeled as a distinct category, while\nmissing continuous values are predicted. `preserve` learns the seed\ndata's missingness patterns and reproduces them in generated data.\nThe selection is fixed for that dataset.\n\nAll four scripts read these environment variables:\n\n```sh\nexport SIMIO_CLIENT_ID=\"...\"\nexport SIMIO_CLIENT_SECRET=\"...\"\nexport SIMIO_AUTH0_DOMAIN=\"simulacra-data.us.auth0.com\"\nexport SIMIO_AUTH0_AUDIENCE=\"https://api.simulacra-data.com\"\nexport SIMIO_API_BASE=\"https://api.simulacra-data.com\"\n```\n\nJump to: [Python](#quickstart-python) · [R](#quickstart-r) ·\n[Julia](#quickstart-julia) · [SPSS](#quickstart-spss)\n\n<a id=\"quickstart-python\"><\/a>\n### Python\n\nUses `requests` for HTTP, `pandas` + `numpy` for seed/scenario\nconstruction, and `pyarrow` (a `pandas` extra) for Parquet I/O.\n\n```python\nimport os, time, requests\nimport numpy as np\nimport pandas as pd\n\nAUTH0_DOMAIN   = os.environ[\"SIMIO_AUTH0_DOMAIN\"]\nAUTH0_AUDIENCE = os.environ[\"SIMIO_AUTH0_AUDIENCE\"]\nCLIENT_ID      = os.environ[\"SIMIO_CLIENT_ID\"]\nCLIENT_SECRET  = os.environ[\"SIMIO_CLIENT_SECRET\"]\nAPI_BASE       = os.environ[\"SIMIO_API_BASE\"]\n\n# 1. Mint a bearer token.\ntoken = requests.post(\n    f\"https://{AUTH0_DOMAIN}/oauth/token\",\n    json={\n        \"client_id\":     CLIENT_ID,\n        \"client_secret\": CLIENT_SECRET,\n        \"audience\":      AUTH0_AUDIENCE,\n        \"grant_type\":    \"client_credentials\",\n    },\n    timeout=30,\n).json()[\"access_token\"]\nauth = {\"Authorization\": f\"Bearer {token}\"}\n\n# 2. Build a toy seed dataset and write it to seed.csv.\nrng = np.random.default_rng(7)\nn = 800\ndf = pd.DataFrame({\n    \"age\":     rng.integers(18, 66, n),\n    \"segment\": rng.choice([\"Value\", \"Mainstream\", \"Premium\"],\n                          n, p=[0.35, 0.45, 0.20]),\n    \"channel\": rng.choice([\"Retail\", \"Online\", \"Club\"],\n                          n, p=[0.50, 0.35, 0.15]),\n    \"region\":  rng.choice([\"Northeast\", \"South\", \"Midwest\", \"West\"], n),\n})\nintent  = 42.0\nintent += (df[\"segment\"] == \"Premium\") * 18\nintent += (df[\"channel\"] == \"Online\")  *  8\nintent += ((df[\"age\"] - 35) / 3).clip(-8, 8)\nintent += rng.normal(0, 10, n)\ndf[\"purchase_intent\"] = intent.round().clip(0, 100).astype(int)\ndf.to_csv(\"seed.csv\", index=False)\n\n# Helpers: 202 Accepted is normal for training and generation.\n# This quickstart uses a simple 2s poll loop; production clients\n# should add jittered exponential backoff and a deadline appropriate\n# for their workload.\ndef poll_job(job_id, field, deadline_seconds=600):\n    deadline = time.time() + deadline_seconds\n    while time.time() < deadline:\n        body = requests.get(f\"{API_BASE}/v1/jobs/{job_id}\",\n                             headers=auth, timeout=30).json()\n        if body[\"status\"] in (\"failed\", \"expired\", \"cancelled\"):\n            raise RuntimeError(f\"job {job_id} {body['status']}: {body}\")\n        if body[\"status\"] == \"completed\":\n            value = body.get(field)\n            if value:\n                return value\n            raise RuntimeError(f\"job {job_id} completed without {field}: {body}\")\n        time.sleep(2)\n    raise TimeoutError(f\"job {job_id} did not complete before deadline\")\n\ndef wait_for_dataset(upload):\n    if upload.get(\"status\") == \"ready\" and upload.get(\"dataset_id\"):\n        return upload[\"dataset_id\"]\n    return poll_job(upload[\"job_id\"], \"dataset_id\")\n\ndef wait_for_generation(initial):\n    generation_id = initial.get(\"generation_id\")\n    if initial.get(\"status\") == \"processing\" and initial.get(\"job_id\"):\n        generation_id = poll_job(initial[\"job_id\"], \"generation_id\")\n    if not generation_id:\n        raise RuntimeError(f\"generation response lacked generation_id: {initial}\")\n    deadline = time.time() + 600\n    while time.time() < deadline:\n        meta = requests.get(\n            f\"{API_BASE}/v1/generations/{generation_id}\",\n            headers=auth, timeout=30,\n        ).json()\n        if meta.get(\"status\") in (\"ready\", \"partial\") and meta.get(\"artifact_url\"):\n            return meta\n        if meta.get(\"status\") == \"failed\":\n            raise RuntimeError(f\"generation failed: {meta}\")\n        time.sleep(2)\n    raise TimeoutError(f\"generation {generation_id} did not produce an artifact\")\n\n# 3. Upload seed + train. Returns dataset_id; when job_id is present,\n#    poll that job before fetching the dataset.\nwith open(\"seed.csv\", \"rb\") as fh:\n    upload = requests.post(\n        f\"{API_BASE}/v1/datasets\",\n        headers={**auth,\n                 \"Idempotency-Key\": f\"dataset-{int(time.time())}\"},\n        files={\"seed_file\": (\"seed.csv\", fh, \"text/csv\")},\n        data={\"display_name\": \"Quickstart seed\",\n              \"missing_data\": \"complete\"},\n        timeout=60,\n    ).json()\ndataset_id = wait_for_dataset(upload)\n\n# 4. Inspect the trained schema. Use the returned column names,\n#    levels, and ranges to build conditions; do not guess from the\n#    original seed headers.\nschema = requests.get(\n    f\"{API_BASE}/v1/datasets/{dataset_id}/schema\",\n    headers=auth, timeout=30,\n).json()\nschema_columns = schema.get(\"schema\", [])\nschema_by_name = {col[\"name\"]: col for col in schema_columns}\n\ndef resolve_column(*candidates):\n    for name in candidates:\n        if name in schema_by_name:\n            return name\n    normalized = {name.replace(\"_\", \".\").lower(): name for name in schema_by_name}\n    for name in candidates:\n        hit = normalized.get(name.replace(\"_\", \".\").lower())\n        if hit:\n            return hit\n    raise KeyError(\n        f\"None of {candidates} is in the cleaned schema. Available: {list(schema_by_name)}\"\n    )\n\ndef require_levels(column, levels):\n    available = set(schema_by_name[column].get(\"levels\") or [])\n    missing = set(levels) - available\n    if missing:\n        raise KeyError(\n            f\"Schema column {column!r} is missing levels {sorted(missing)}. \"\n            f\"Available levels: {sorted(available)}\"\n        )\n\nsegment_col = resolve_column(\"segment\")\nchannel_col = resolve_column(\"channel\")\nage_col = resolve_column(\"age\")\nintent_col = resolve_column(\"purchase_intent\", \"purchase.intent\")\nrequire_levels(segment_col, [\"Premium\", \"Mainstream\", \"Value\"])\nrequire_levels(channel_col, [\"Online\", \"Retail\", \"Club\"])\n\n# 5. Define a scenario as ordinary data frames, then convert to the\n#    API condition object. Categorical target_share values are desired\n#    outcome percentages, subject to feasibility jitter.\ncategorical_targets = pd.DataFrame([\n    {\"column\": segment_col, \"level\": \"Premium\",    \"target_share\": 0.55},\n    {\"column\": segment_col, \"level\": \"Mainstream\", \"target_share\": 0.35},\n    {\"column\": segment_col, \"level\": \"Value\",      \"target_share\": 0.10},\n    {\"column\": channel_col, \"level\": \"Online\",     \"target_share\": 0.70},\n    {\"column\": channel_col, \"level\": \"Retail\",     \"target_share\": 0.20},\n    {\"column\": channel_col, \"level\": \"Club\",       \"target_share\": 0.10},\n])\nnumeric_ranges = pd.DataFrame([\n    {\"column\": age_col,    \"min\": 25, \"max\": 44},\n    {\"column\": intent_col, \"min\": 70, \"max\": 100},\n])\n\ndef categorical_conditions(targets):\n    return {\n        column: dict(zip(group[\"level\"], group[\"target_share\"]))\n        for column, group in targets.groupby(\"column\", sort=False)\n    }\n\ndef numeric_conditions(ranges):\n    return {\n        row.column: {\"min\": row.min, \"max\": row.max}\n        for row in ranges.itertuples(index=False)\n    }\n\nscenario = {\n    \"row_count\":     5000,\n    \"output_format\": \"parquet\",\n    \"conditions\": {\n        \"categorical\": categorical_conditions(categorical_targets),\n        \"numeric\":     numeric_conditions(numeric_ranges),\n    },\n}\n\n# 6. Generate the scenario-conditioned synthetic artifact.\ngen = requests.post(\n    f\"{API_BASE}/v1/datasets/{dataset_id}/generations\",\n    headers={**auth,\n             \"content-type\":    \"application/json\",\n             \"Idempotency-Key\": f\"generation-{int(time.time())}\"},\n    json=scenario, timeout=60,\n).json()\nmeta = wait_for_generation(gen)\n\n# 7. Download the artifact. artifact_url is absolute for enterprise\n#    tenants and relative (managed-mode relay) otherwise.\nartifact_url = meta[\"artifact_url\"]\nif not artifact_url.startswith(\"http\"):\n    artifact_url = f\"{API_BASE}{artifact_url}\"\nart = requests.get(artifact_url, headers=auth, timeout=120)\nart.raise_for_status()\nwith open(\"synthetic.parquet\", \"wb\") as fh:\n    fh.write(art.content)\n\n# 8. Read the result.\nsynth = pd.read_parquet(\"synthetic.parquet\")\nprint(len(synth), \"rows\")\nprint(synth.head())\n```\n\n<a id=\"quickstart-r\"><\/a>\n### R\n\nUses `httr2` for HTTP and `arrow` for Parquet I/O.\n\n```r\nlibrary(httr2)\nlibrary(arrow)\n\nAUTH0_DOMAIN   <- Sys.getenv(\"SIMIO_AUTH0_DOMAIN\")\nAUTH0_AUDIENCE <- Sys.getenv(\"SIMIO_AUTH0_AUDIENCE\")\nCLIENT_ID      <- Sys.getenv(\"SIMIO_CLIENT_ID\")\nCLIENT_SECRET  <- Sys.getenv(\"SIMIO_CLIENT_SECRET\")\nAPI_BASE       <- Sys.getenv(\"SIMIO_API_BASE\")\n\n# 1. Mint a bearer token.\ntoken <- request(paste0(\"https://\", AUTH0_DOMAIN, \"/oauth/token\")) |>\n  req_method(\"POST\") |>\n  req_body_json(list(\n    client_id     = CLIENT_ID,\n    client_secret = CLIENT_SECRET,\n    audience      = AUTH0_AUDIENCE,\n    grant_type    = \"client_credentials\"\n  )) |>\n  req_perform() |> resp_body_json()\nTOKEN <- token$access_token\nauth <- function(req) req_auth_bearer_token(req, TOKEN)\n\n# 2. Build a toy seed dataset and write it to seed.csv.\nset.seed(7); n <- 800\nseed <- data.frame(\n  age     = sample(18:65, n, replace = TRUE),\n  segment = sample(c(\"Value\", \"Mainstream\", \"Premium\"), n,\n                   replace = TRUE, prob = c(0.35, 0.45, 0.20)),\n  channel = sample(c(\"Retail\", \"Online\", \"Club\"), n,\n                   replace = TRUE, prob = c(0.50, 0.35, 0.15)),\n  region  = sample(c(\"Northeast\", \"South\", \"Midwest\", \"West\"), n,\n                   replace = TRUE)\n)\nintent <- 42 +\n  (seed$segment == \"Premium\") * 18 +\n  (seed$channel == \"Online\")  *  8 +\n  pmin(pmax((seed$age - 35) / 3, -8), 8) +\n  rnorm(n, 0, 10)\nseed$purchase_intent <- pmax(pmin(round(intent), 100), 0)\nwrite.csv(seed, \"seed.csv\", row.names = FALSE)\n\n# Helper: a 202 response is normal for async work. Poll the job\n# until it is completed. Production clients should add jittered\n# exponential backoff and a deadline appropriate for their workload.\npoll_job <- function(job_id, field) {\n  deadline <- Sys.time() + 600\n  repeat {\n    if (Sys.time() > deadline) stop(\"job \", job_id, \" timed out\")\n    body <- request(paste0(API_BASE, \"/v1/jobs/\", job_id)) |>\n      auth() |> req_perform() |> resp_body_json()\n    if (body$status %in% c(\"failed\", \"expired\", \"cancelled\"))\n      stop(\"job \", job_id, \" \", body$status)\n    if (identical(body$status, \"completed\")) {\n      value <- body[[field]]\n      if (!is.null(value) && nzchar(value)) return(value)\n      stop(\"job \", job_id, \" completed without \", field)\n    }\n    Sys.sleep(2)\n  }\n}\n\nwait_for_generation <- function(initial) {\n  generation_id <- initial$generation_id\n  if (identical(initial$status, \"processing\") && !is.null(initial$job_id))\n    generation_id <- poll_job(initial$job_id, \"generation_id\")\n  if (is.null(generation_id) || !nzchar(generation_id))\n    stop(\"generation response lacked generation_id\")\n  deadline <- Sys.time() + 600\n  repeat {\n    if (Sys.time() > deadline) stop(\"generation \", generation_id, \" timed out\")\n    meta <- request(paste0(API_BASE, \"/v1/generations/\", generation_id)) |>\n      auth() |> req_perform() |> resp_body_json()\n    if (meta$status %in% c(\"ready\", \"partial\") &&\n        !is.null(meta$artifact_url) && nzchar(meta$artifact_url))\n      return(meta)\n    if (identical(meta$status, \"failed\")) stop(\"generation failed\")\n    Sys.sleep(2)\n  }\n}\n\n# 3. Upload seed + train.\nupload <- request(paste0(API_BASE, \"/v1/datasets\")) |>\n  req_method(\"POST\") |> auth() |>\n  req_headers(`Idempotency-Key` =\n                paste0(\"dataset-\", as.integer(Sys.time()))) |>\n  req_body_multipart(\n    seed_file    = curl::form_file(\"seed.csv\", type = \"text/csv\"),\n    display_name = \"Quickstart seed\",\n    missing_data = \"complete\"\n  ) |>\n  req_perform() |> resp_body_json()\ndataset_id <- if (identical(upload$status, \"ready\") &&\n                  !is.null(upload$dataset_id) && nzchar(upload$dataset_id))\n  upload$dataset_id else poll_job(upload$job_id, \"dataset_id\")\n\n# 4. Inspect the trained schema.\nschema <- request(paste0(API_BASE, \"/v1/datasets/\", dataset_id,\n                         \"/schema\")) |>\n  auth() |> req_perform() |> resp_body_json()\nschema_cols <- schema$schema\nschema_names <- vapply(schema_cols, `[[`, character(1), \"name\")\nresolve_column <- function(...) {\n  candidates <- c(...)\n  hit <- candidates[candidates %in% schema_names]\n  if (length(hit)) return(hit[[1]])\n  normalized <- setNames(schema_names, tolower(gsub(\"_\", \".\", schema_names)))\n  for (candidate in candidates) {\n    key <- tolower(gsub(\"_\", \".\", candidate))\n    hit <- normalized[[key]]\n    if (!is.null(hit) && !is.na(hit)) return(hit)\n  }\n  stop(\"None of \", paste(candidates, collapse = \", \"),\n       \" is in the cleaned schema. Available: \",\n       paste(schema_names, collapse = \", \"))\n}\nrequire_levels <- function(column, levels) {\n  col <- schema_cols[[match(column, schema_names)]]\n  available <- if (is.null(col$levels)) character() else unlist(col$levels)\n  missing <- setdiff(levels, available)\n  if (length(missing)) stop(\"Schema column \", column,\n                           \" is missing levels: \",\n                           paste(missing, collapse = \", \"))\n}\nsegment_col <- resolve_column(\"segment\")\nchannel_col <- resolve_column(\"channel\")\nage_col     <- resolve_column(\"age\")\nintent_col  <- resolve_column(\"purchase_intent\", \"purchase.intent\")\nrequire_levels(segment_col, c(\"Premium\", \"Mainstream\", \"Value\"))\nrequire_levels(channel_col, c(\"Online\", \"Retail\", \"Club\"))\n\n# 5. Define a scenario as ordinary data frames, then convert to the\n#    API condition object. target_share values are desired outcome\n#    percentages, subject to feasibility jitter.\ncategorical_targets <- data.frame(\n  column = c(segment_col, segment_col, segment_col,\n             channel_col, channel_col, channel_col),\n  level = c(\"Premium\", \"Mainstream\", \"Value\",\n            \"Online\", \"Retail\", \"Club\"),\n  target_share = c(0.55, 0.35, 0.10, 0.70, 0.20, 0.10)\n)\nnumeric_ranges <- data.frame(\n  column = c(age_col, intent_col),\n  min    = c(25, 70),\n  max    = c(44, 100)\n)\n\ncategorical_conditions <- lapply(\n  split(categorical_targets, categorical_targets$column),\n  function(x) as.list(setNames(x$target_share, x$level))\n)\nnumeric_conditions <- setNames(\n  lapply(seq_len(nrow(numeric_ranges)), function(i) {\n    list(min = numeric_ranges$min[[i]], max = numeric_ranges$max[[i]])\n  }),\n  numeric_ranges$column\n)\n\nscenario <- list(\n  row_count     = 5000L,\n  output_format = \"parquet\",\n  conditions = list(\n    categorical = categorical_conditions,\n    numeric     = numeric_conditions\n  )\n)\n\n# 6. Generate the scenario-conditioned synthetic artifact.\ngen <- request(paste0(API_BASE, \"/v1/datasets/\", dataset_id,\n                      \"/generations\")) |>\n  req_method(\"POST\") |> auth() |>\n  req_headers(`Idempotency-Key` =\n                paste0(\"generation-\", as.integer(Sys.time()))) |>\n  req_body_json(scenario) |>\n  req_perform() |> resp_body_json()\nmeta <- wait_for_generation(gen)\n\n# 7. Download the artifact.\nartifact_url <- if (startsWith(meta$artifact_url, \"http\"))\n  meta$artifact_url else paste0(API_BASE, meta$artifact_url)\nrequest(artifact_url) |> auth() |>\n  req_perform(path = \"synthetic.parquet\")\n\n# 8. Read the result.\nsynth <- read_parquet(\"synthetic.parquet\")\ncat(nrow(synth), \"rows\\n\")\nhead(synth)\n```\n\n<a id=\"quickstart-julia\"><\/a>\n### Julia\n\nUses `HTTP.jl` + `JSON3` for HTTP, `DataFrames` + `CSV` + `StatsBase`\nfor the seed, and `Parquet2` for the result.\n\n```julia\nusing HTTP, JSON3, DataFrames, CSV, Random, StatsBase, Parquet2\n\nAUTH0_DOMAIN   = ENV[\"SIMIO_AUTH0_DOMAIN\"]\nAUTH0_AUDIENCE = ENV[\"SIMIO_AUTH0_AUDIENCE\"]\nCLIENT_ID      = ENV[\"SIMIO_CLIENT_ID\"]\nCLIENT_SECRET  = ENV[\"SIMIO_CLIENT_SECRET\"]\nAPI_BASE       = ENV[\"SIMIO_API_BASE\"]\n\n# 1. Mint a bearer token.\ntoken_resp = HTTP.post(\n    \"https://$AUTH0_DOMAIN/oauth/token\",\n    [\"content-type\" => \"application/json\"],\n    JSON3.write(Dict(\n        \"client_id\"     => CLIENT_ID,\n        \"client_secret\" => CLIENT_SECRET,\n        \"audience\"      => AUTH0_AUDIENCE,\n        \"grant_type\"    => \"client_credentials\",\n    )),\n)\nTOKEN = JSON3.read(token_resp.body).access_token\nauth() = [\"Authorization\" => \"Bearer $TOKEN\"]\n\n# 2. Build a toy seed dataset and write it to seed.csv.\nRandom.seed!(7)\nn = 800\ndf = DataFrame(\n    age     = rand(18:65, n),\n    segment = sample([\"Value\", \"Mainstream\", \"Premium\"],\n                     StatsBase.Weights([0.35, 0.45, 0.20]), n),\n    channel = sample([\"Retail\", \"Online\", \"Club\"],\n                     StatsBase.Weights([0.50, 0.35, 0.15]), n),\n    region  = rand([\"Northeast\", \"South\", \"Midwest\", \"West\"], n),\n)\nintent = 42.0 .+\n         (df.segment .== \"Premium\") .* 18 .+\n         (df.channel .== \"Online\")  .*  8 .+\n         clamp.((df.age .- 35) ./ 3, -8, 8) .+\n         randn(n) .* 10\ndf.purchase_intent = clamp.(round.(Int, intent), 0, 100)\nCSV.write(\"seed.csv\", df)\n\n# Helper: a 202 response is normal for async work. Poll the job\n# until it is completed. Production clients should add jittered\n# exponential backoff and a deadline appropriate for their workload.\nfunction poll_job(job_id, field)\n    deadline = time() + 600\n    while true\n        time() > deadline && error(\"job $job_id timed out\")\n        r = HTTP.get(\"$API_BASE/v1/jobs/$job_id\", auth())\n        body = JSON3.read(r.body)\n        body.status in (\"failed\", \"expired\", \"cancelled\") &&\n            error(\"job $job_id $(body.status)\")\n        if body.status == \"completed\"\n            v = get(body, Symbol(field), nothing)\n            v === nothing && error(\"job $job_id completed without $field\")\n            return string(v)\n        end\n        sleep(2)\n    end\nend\n\nfunction wait_for_generation(initial)\n    generation_id = get(initial, :generation_id, nothing)\n    if get(initial, :status, \"\") == \"processing\" &&\n       get(initial, :job_id, nothing) !== nothing\n        generation_id = poll_job(initial.job_id, \"generation_id\")\n    end\n    generation_id === nothing && error(\"generation response lacked generation_id\")\n    deadline = time() + 600\n    while true\n        time() > deadline && error(\"generation $generation_id timed out\")\n        meta = JSON3.read(HTTP.get(\n            \"$API_BASE/v1/generations/$generation_id\", auth()).body)\n        if meta.status in (\"ready\", \"partial\") &&\n           get(meta, :artifact_url, nothing) !== nothing\n            return meta\n        end\n        meta.status == \"failed\" && error(\"generation failed\")\n        sleep(2)\n    end\nend\n\n# 3. Upload seed + train.\nupload = HTTP.post(\n    \"$API_BASE/v1/datasets\",\n    vcat(auth(), [\"Idempotency-Key\" => \"dataset-$(round(Int, time()))\"]),\n    HTTP.Form(Dict(\n        \"seed_file\"    => HTTP.Multipart(\"seed.csv\", open(\"seed.csv\"),\n                                         \"text/csv\"),\n        \"display_name\" => \"Quickstart seed\",\n        \"missing_data\" => \"complete\",\n    )),\n)\nupload_body = JSON3.read(upload.body)\ndataset_id  = get(upload_body, :dataset_id, nothing)\ndataset_id  = get(upload_body, :status, \"\") == \"ready\" &&\n    !isnothing(dataset_id) ? string(dataset_id) :\n    poll_job(upload_body.job_id, \"dataset_id\")\n\n# 4. Inspect the trained schema.\nschema = JSON3.read(HTTP.get(\n    \"$API_BASE/v1/datasets/$dataset_id/schema\", auth()).body)\nschema_cols = collect(schema.schema)\nschema_names = [String(col.name) for col in schema_cols]\nfunction resolve_column(candidates...)\n    for candidate in candidates\n        string(candidate) in schema_names && return string(candidate)\n    end\n    normalized = Dict(lowercase(replace(name, \"_\" => \".\")) => name\n                      for name in schema_names)\n    for candidate in candidates\n        key = lowercase(replace(string(candidate), \"_\" => \".\"))\n        haskey(normalized, key) && return normalized[key]\n    end\n    error(\"None of $(candidates) is in the cleaned schema. Available: $schema_names\")\nend\nfunction require_levels(column, levels)\n    idx = findfirst(==(column), schema_names)\n    raw_levels = hasproperty(schema_cols[idx], :levels) ? schema_cols[idx].levels : String[]\n    available = Set(string.(raw_levels))\n    missing = setdiff(Set(levels), available)\n    !isempty(missing) && error(\"Schema column $column is missing levels: $missing\")\nend\nsegment_col = resolve_column(\"segment\")\nchannel_col = resolve_column(\"channel\")\nage_col = resolve_column(\"age\")\nintent_col = resolve_column(\"purchase_intent\", \"purchase.intent\")\nrequire_levels(segment_col, [\"Premium\", \"Mainstream\", \"Value\"])\nrequire_levels(channel_col, [\"Online\", \"Retail\", \"Club\"])\n\n# 5. Define a scenario as ordinary DataFrames, then convert to the\n#    API condition object. target_share values are desired outcome\n#    percentages, subject to feasibility jitter.\ncategorical_targets = DataFrame(\n    column = [segment_col, segment_col, segment_col,\n              channel_col, channel_col, channel_col],\n    level = [\"Premium\", \"Mainstream\", \"Value\",\n             \"Online\", \"Retail\", \"Club\"],\n    target_share = [0.55, 0.35, 0.10, 0.70, 0.20, 0.10],\n)\nnumeric_ranges = DataFrame(\n    column = [age_col, intent_col],\n    min = [25, 70],\n    max = [44, 100],\n)\n\nfunction categorical_conditions(targets)\n    Dict(\n        col => Dict(row.level => row.target_share\n                    for row in eachrow(targets[targets.column .== col, :]))\n        for col in unique(targets.column)\n    )\nend\n\nnumeric_conditions = Dict(\n    row.column => Dict(\"min\" => row.min, \"max\" => row.max)\n    for row in eachrow(numeric_ranges)\n)\n\nscenario = Dict(\n    \"row_count\"     => 5000,\n    \"output_format\" => \"parquet\",\n    \"conditions\"    => Dict(\n        \"categorical\" => categorical_conditions(categorical_targets),\n        \"numeric\"     => numeric_conditions,\n    ),\n)\n\n# 6. Generate the scenario-conditioned synthetic artifact.\ngen = HTTP.post(\n    \"$API_BASE/v1/datasets/$dataset_id/generations\",\n    vcat(auth(), [\"content-type\"    => \"application/json\",\n                  \"Idempotency-Key\" => \"generation-$(round(Int, time()))\"]),\n    JSON3.write(scenario),\n)\ngen_body      = JSON3.read(gen.body)\nmeta = wait_for_generation(gen_body)\n\n# 7. Download the artifact.\nartifact_url = startswith(meta.artifact_url, \"http\") ?\n    String(meta.artifact_url) : \"$API_BASE$(meta.artifact_url)\"\nopen(\"synthetic.parquet\", \"w\") do io\n    write(io, HTTP.get(artifact_url, auth()).body)\nend\n\n# 8. Read the result.\nsynth = DataFrame(Parquet2.Dataset(\"synthetic.parquet\"))\nprintln(nrow(synth), \" rows\")\nfirst(synth, 5)\n```\n\n<a id=\"quickstart-spss\"><\/a>\n### SPSS\n\nSPSS does not have a native HTTP client, so the realistic flow is to\ndrive the API from a small shell wrapper and then read the result with\nSPSS syntax. SPSS users typically already have survey data they want to\nuse as a seed instead of synthesizing one — `seed_file` accepts CSV,\nParquet, Excel, and SAV directly.\n\nRequires `curl` and `jq` (see [jq install docs](https://jqlang.org/download/))\non the box that runs the shell script.\nThe shell wrapper resolves cleaned column names client-side; the API\nalso validates submitted columns and levels against the cleaned schema\nand returns a 400 with a schema hint if they do not match.\n\n```sh\n# 1. Mint a bearer token.\nTOKEN=$(curl -sS -X POST \"https://${SIMIO_AUTH0_DOMAIN}/oauth/token\" \\\n  -H \"content-type: application/json\" \\\n  -d \"{\n    \\\"client_id\\\":\\\"${SIMIO_CLIENT_ID}\\\",\n    \\\"client_secret\\\":\\\"${SIMIO_CLIENT_SECRET}\\\",\n    \\\"audience\\\":\\\"${SIMIO_AUTH0_AUDIENCE}\\\",\n    \\\"grant_type\\\":\\\"client_credentials\\\"\n  }\" | jq -r \".access_token\")\nAUTH=\"Authorization: Bearer ${TOKEN}\"\n\n# Helper: poll a 202 job until it completes. Production clients\n# should add jittered exponential backoff and a deadline matched\n# to their workload.\npoll_job_field() {\n  job_id=\"$1\"; field=\"$2\"; deadline=$((SECONDS + 600))\n  while [ \"${SECONDS}\" -lt \"${deadline}\" ]; do\n    sleep 2\n    job=$(curl -sS -H \"${AUTH}\" \"${SIMIO_API_BASE}/v1/jobs/${job_id}\")\n    status=$(echo \"${job}\" | jq -r \".status // empty\")\n    if [ \"${status}\" = \"failed\" ] || [ \"${status}\" = \"expired\" ] || [ \"${status}\" = \"cancelled\" ]; then\n      echo \"${job}\" >&2; exit 1\n    fi\n    if [ \"${status}\" = \"completed\" ]; then\n      value=$(echo \"${job}\" | jq -r \".${field} // empty\")\n      if [ -n \"${value}\" ]; then printf \"%s\" \"${value}\"; return 0; fi\n      echo \"${job}\" >&2; echo \"job completed without ${field}\" >&2; exit 1\n    fi\n  done\n  echo \"job ${job_id} timed out\" >&2; exit 1\n}\n\nwait_generation_artifact() {\n  generation_id=\"$1\"; deadline=$((SECONDS + 600))\n  while [ \"${SECONDS}\" -lt \"${deadline}\" ]; do\n    meta=$(curl -sS -H \"${AUTH}\" \"${SIMIO_API_BASE}/v1/generations/${generation_id}\")\n    status=$(echo \"${meta}\" | jq -r \".status // empty\")\n    artifact=$(echo \"${meta}\" | jq -r \".artifact_url // empty\")\n    if { [ \"${status}\" = \"ready\" ] || [ \"${status}\" = \"partial\" ]; } && [ -n \"${artifact}\" ]; then\n      printf \"%s\" \"${meta}\"; return 0\n    fi\n    if [ \"${status}\" = \"failed\" ]; then echo \"${meta}\" >&2; exit 1; fi\n    sleep 2\n  done\n  echo \"generation ${generation_id} timed out\" >&2; exit 1\n}\n\n# 3. Upload seed + train. Use your existing SAV/CSV file here; we use\n#    survey.sav as an example.\nUPLOAD=$(curl -sS -X POST \"${SIMIO_API_BASE}/v1/datasets\" \\\n  -H \"${AUTH}\" -H \"Idempotency-Key: dataset-$(date +%s)\" \\\n  -F \"seed_file=@./survey.sav;type=application/x-spss-sav\" \\\n  -F \"display_name=SPSS quickstart seed\" \\\n  -F \"missing_data=complete\")\nDATASET_ID=$(echo \"${UPLOAD}\" | jq -r \".dataset_id // empty\")\nJOB_ID=$(echo \"${UPLOAD}\" | jq -r \".job_id // empty\")\nif [ -z \"${DATASET_ID}\" ]; then\n  DATASET_ID=$(poll_job_field \"${JOB_ID}\" dataset_id);\nfi\n\n# 4. Inspect the trained schema. Use these cleaned names in\n#    conditions; do not assume original seed headers survived.\nSCHEMA=$(curl -sS -H \"${AUTH}\" \\\n  \"${SIMIO_API_BASE}/v1/datasets/${DATASET_ID}/schema\")\necho \"${SCHEMA}\" | jq .\nresolve_col() {\n  a=\"$1\"; b=\"${2:-$1}\"\n  echo \"${SCHEMA}\" | jq -r --arg a \"${a}\" --arg b \"${b}\" \\\n    '.schema[] | select(.name == $a or .name == $b) | .name' | head -n 1\n}\nSEGMENT_COL=$(resolve_col segment)\nCHANNEL_COL=$(resolve_col channel)\nAGE_COL=$(resolve_col age)\nINTENT_COL=$(resolve_col purchase_intent purchase.intent)\nif [ -z \"${SEGMENT_COL}\" ] || [ -z \"${CHANNEL_COL}\" ] || [ -z \"${AGE_COL}\" ] || [ -z \"${INTENT_COL}\" ]; then\n  echo \"Expected quickstart columns were not all retained after cleaning\" >&2; exit 1\nfi\n\n# 5. Build the scenario request in a shell variable. Categorical\n#    values are desired outcome percentages. Use output_format=csv\n#    so the artifact lands in a format SPSS reads directly.\nSCENARIO=$(jq -cn --arg segment \"${SEGMENT_COL}\" --arg channel \"${CHANNEL_COL}\" \\\n  --arg age \"${AGE_COL}\" --arg intent \"${INTENT_COL}\" '{\n  row_count: 5000,\n  output_format: \"csv\",\n  conditions: {\n    categorical: {\n      ($segment): {Premium: 0.55, Mainstream: 0.35, Value: 0.10},\n      ($channel): {Online: 0.70, Retail: 0.20, Club: 0.10}\n    },\n    numeric: {\n      ($age): {min: 25, max: 44},\n      ($intent): {min: 70, max: 100}\n    }\n  }\n}')\n\nGEN=$(curl -sS -X POST \"${SIMIO_API_BASE}/v1/datasets/${DATASET_ID}/generations\" \\\n  -H \"${AUTH}\" -H \"content-type: application/json\" \\\n  -H \"Idempotency-Key: generation-$(date +%s)\" \\\n  --data \"${SCENARIO}\")\nGENERATION_ID=$(echo \"${GEN}\" | jq -r \".generation_id // empty\")\nGEN_JOB_ID=$(echo \"${GEN}\" | jq -r \".job_id // empty\")\nGEN_STATUS=$(echo \"${GEN}\" | jq -r \".status // empty\")\nif [ \"${GEN_STATUS}\" = \"processing\" ] && [ -n \"${GEN_JOB_ID}\" ]; then\n  GENERATION_ID=$(poll_job_field \"${GEN_JOB_ID}\" generation_id)\nfi\n\n# 6. Download the artifact.\nGEN_META=$(wait_generation_artifact \"${GENERATION_ID}\")\nARTIFACT_URL=$(echo \"${GEN_META}\" | jq -r \".artifact_url\")\ncase \"${ARTIFACT_URL}\" in\n  http*) curl -sS -L \"${ARTIFACT_URL}\" -o synthetic.csv ;;\n  *)     curl -sS -L -H \"${AUTH}\" \\\n          \"${SIMIO_API_BASE}${ARTIFACT_URL}\" -o synthetic.csv ;;\nesac\n```\n\nThen read the result in SPSS — adjust the variable list to match your\nseed's column names and types:\n\n```spss\nGET DATA\n  /TYPE=TXT\n  /FILE='synthetic.csv'\n  /ENCODING='UTF8'\n  /DELCASE=LINE\n  /DELIMITERS=\",\"\n  /QUALIFIER='\"'\n  /ARRANGEMENT=DELIMITED\n  /FIRSTCASE=2\n  /IMPORTCASE=ALL\n  /VARIABLES=\n    age F8.0\n    segment A20\n    channel A20\n    region A20\n    purchase_intent F8.0.\nCACHE.\nEXECUTE.\n```\n\n<a id=\"model-declarations\"><\/a>\n## Model Declarations\n\nA wave number or fielding year is a time axis. It often holds only\na few distinct values, so automatic typing treats it as a category.\nThat default fits survey answers. For a time axis it loses the\nordering. A modeling declaration names numeric columns to model as\ncontinuous measures.\n\nThe entire declaration is one list:\n\n```yaml\ncontinuous_columns:\n  - wave\n```\n\nEquivalent JSON (`{\"continuous_columns\": [\"wave\"]}`) is accepted;\nscalars and one-item lists canonicalize identically. Attach it to\ndataset creation as a `model_declaration` multipart part; it works\nwith either missing_data mode and combines with route_plan.\n\nDeclared columns keep their scale and type in generated data, the\ndataset schema annotates them with `model_as = continuous`, and a\nstable declared axis enables forward conditioning — generating the\nnext wave beyond the observed data. When recent waves moved too\nmuch to project, Simulacra refuses the request and the message\nnames the columns that moved. Nothing in your request needs fixing;\nthe refusal reflects the data.\n\nValidation refuses unknown or duplicate keys, empty lists, names\nabsent from the cleaned schema, and non-numeric columns\n(`simio_model_declaration_syntax_error` 400 /\n`simio_model_declaration_semantic_error` 422). Undeclared datasets are\nbyte-identical to previous behavior.\n\n<a id=\"routing-declarations\"><\/a>\n## Routing Declarations\n\nMost datasets need only the standard `missing_data=complete` or\n`missing_data=preserve` setting. Some surveys use deeply nested skip or\ndisplay logic to decide which batteries each respondent sees. When you\nwant those questionnaire paths preserved exactly, add a routing\ndeclaration with `preserve`. Ordinary missing responses inside an asked\npopulation are still learned from the seed.\n\nThe declaration stays with the dataset and applies automatically whenever\nyou generate from it.\n\n### Try it with the Quickstart seed\n\nThe Python Quickstart creates a `channel` column. This variation adds\n`online_experience`, a 1–5 question shown only when `channel` is\n`Online`, and writes the corresponding declaration:\n\n```python\nfrom pathlib import Path\n\nonline = df[\"channel\"].eq(\"Online\")\ndf[\"online_experience\"] = pd.Series(pd.NA, index=df.index, dtype=\"Int64\")\ndf.loc[online, \"online_experience\"] = rng.integers(1, 6, online.sum())\ndf.to_csv(\"routed-seed.csv\", index=False)\n\nPath(\"routes.yaml\").write_text(\"\"\"\\\nroutes:\n  - when: {channel: [Online]}\n    ask: online_experience\n\"\"\", encoding=\"utf-8\")\n```\n\nA declaration contains only a `routes` list. Every rule has:\n\n- `when`: one always-asked categorical trigger and the values that open\n  the route; and\n- `ask`: one answer column or a list of answer columns opened by it.\n\nNames and values must match the cleaned upload.\n\nValidate it without fitting first:\n\n```sh\ncurl -sS -X POST \"${SIMIO_API_BASE}/v1/route-plans/validate\" \\\n  -H \"Authorization: Bearer ${ACCESS_TOKEN}\" \\\n  -F \"seed_file=@routed-seed.csv;type=text/csv\" \\\n  -F \"route_plan=@routes.yaml;type=application/yaml\"\n```\n\nPreflight runs the production preserve-mode cleaning and validation\npath, then returns normalized rules and actionable checks without fit,\npersistence, or quota use. Dataset creation repeats the validation.\nSend the same file alongside the seed:\n\n```sh\ncurl -sS -X POST \"${SIMIO_API_BASE}/v1/datasets\" \\\n  -H \"Authorization: Bearer ${ACCESS_TOKEN}\" \\\n  -F \"seed_file=@routed-seed.csv;type=text/csv\" \\\n  -F \"missing_data=preserve\" \\\n  -F \"route_plan=@routes.yaml;type=application/yaml\"\n```\n\nBefore fitting, Simulacra checks cleaned names and values, the supported\nrule shape, and every observed routed answer. Missing answers inside an\nasked population remain ordinary item nonresponse. An observed answer\noutside its declared route is reported as a contradiction to correct in\nthe data or declaration.\n\nSuccessful responses show the normalized rules, routing checks, and any\nreview warnings. Once the dataset is created, routing applies\nautomatically whenever you generate from it.\n\nA `high_missingness_within_route` warning means at least five answers\nand at least 20% of eligible responses are missing for the named\nquestion. The route is valid, but review the source export before\nfitting to confirm those blanks are expected.\n\nStable declaration errors are:\n\n- `400 route_plan_syntax_error` — malformed document or field shape;\n- `422 route_plan_semantic_error` — incompatible trigger graph or mode;\n- `422 route_plan_empirical_error` — observed answers contradict the\n  declared asked population;\n- `422 route_plan_unsupported_schema` — a shape deferred beyond v1.\n\nA hard condition on a routed answer entails the trigger values that make\nit observable. A contradictory trigger-plus-answer request fails before\ngeneration. Ordinary conditioning never releases a route to answer a\nquestion the respondent was not asked.\n\n## Conditioning Reference\n\n- Categorical values under `conditions.categorical` are desired outcome\n  percentages: `0.70` means a 70% target share for that level, subject\n  to feasibility jitter.\n- Numeric ranges under `conditions.numeric` are bilateral: include both\n  `min` and `max`.\n- Always derive column names and levels from\n  `/v1/datasets/{dataset_id}/schema`. Do not guess from memory or\n  prompt context.\n- Tight scenarios can produce fewer rows than requested even when\n  quota remains; usage-billing is counted on generated rows rather\n  than requested rows.\n- Send an `Idempotency-Key` header on every `POST /v1/datasets` and\n  `POST /v1/datasets/{id}/generations` so retries are safe.\n\n## Notes For Coding Agents\n\nAgents should start at `/llms.txt`, use `/openapi.json` as the\ncanonical contract, and fetch `/v1/datasets/{dataset_id}/schema` before\nconstructing conditions. Do not infer private columns, dataset IDs,\nor credential values from examples. Preserve `X-Request-Id` and any\nresponse-body `request_id` in logs for support, but never log bearer\ntokens or client secrets.\n\n## Client Operating Rules\n\n- Treat `202 Accepted` as normal; poll `/v1/jobs/{job_id}`.\n- Store `X-Request-Id` and any response-body `request_id` for support.\n- Use `/v1/datasets/{dataset_id}/schema` before conditional generation.\n- Tight scenarios can produce fewer rows than requested even when\n  quota remains; usage-billing is counted on generated rows rather\n  than requested rows.\n- Do not put client secrets in browsers, shared notebooks, or logs.\n\n## Versioning And Changelog Policy\n\nThe `/v1/*` contract is stable for production integrations.\nThe OpenAPI `info.version` is the deployed service release.\nBackward-compatible customer-visible contract additions increment\nthe minor version; compatible customer-visible fixes increment the\npatch version.\nChanges that require migration to an incompatible resource or\nresponse contract use a new major version and normally a future\n`/v2` surface. Retired request controls and validation corrections\nare documented in **What's New** and do not by themselves create a\nnew API major.\n\nTarget notice for planned breaking changes is at least 30 days.\nSecurity, legal, or emergency reliability fixes may move faster, and may include direct customer communication and a clear rollback or migration path when applicable.\n\nCustomer-visible contract changes are listed in **What's New** above.\nIf your tooling consumes `/openapi.json`, diff the spec before\ndeploy and treat unknown new fields as forward-compatible.","contact":{"name":"Simulacra Support","email":"support@simulacra-data.com"},"version":"1.8.13"},"paths":{"/__docs__/":{"get":{"parameters":[],"operationId":"/__docs__/-get"}},"/":{"get":{"parameters":[],"operationId":"/-get"}},"/docs":{"get":{"parameters":[],"operationId":"/docs-get"}},"/healthz":{"get":{"parameters":[],"operationId":"/healthz-get"}},"/v1/datasets":{"post":{"summary":"05. Upload and train a seed dataset","description":"Accepts a CSV / Parquet / Excel / SAV file as multipart/form-data with `seed_file` plus optional `display_name`, `missing_data`, `route_plan`, `wait_seconds`, and `idempotency_key` fields. The API validates, cleans, trains, and retains the dataset according to the tenant's configured storage mode. The response includes `dataset_id` when already complete or `job_id` when processing continues; `wait_seconds` remains accepted for compatibility but does not hold the request open. Always send an `Idempotency-Key` so retries are safe. Prepare the upload itself (row shape, blanks, `missing_data`) per the public guide: [Preparing survey data](https://www.simulacra-data.com/resources/preparing-survey-data).","security":[{"bearerAuth":[]}],"parameters":[],"operationId":"createDataset","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["seed_file"],"properties":{"seed_file":{"type":"string","format":"binary","description":"Seed file: CSV, Parquet, Excel, or SAV. Max 500 MB. The file's content hash is part of the idempotency fingerprint."},"display_name":{"type":"string","maxLength":200,"description":"Human-readable label. No control characters."},"missing_data":{"type":"string","enum":["complete","preserve"],"default":"complete","description":"Missing-data mode selected when the dataset is created. `complete` (the default) models categorical NA as a distinct category and predicts missing continuous values. `preserve` learns the seed data's missingness patterns and reproduces them in generated data. The selection is fixed for the dataset; choose another mode by creating a new dataset."},"route_plan":{"type":"string","format":"binary","description":"Optional compact UTF-8 YAML or JSON routing declaration (max 256 KB). Requires `missing_data=preserve`. Supply a `routes` list whose rules contain only one `when` mapping and an `ask` column or column list. The API validates it against the cleaned upload before fitting. Responses echo normalized rules and actionable checks; internal hashes, provenance, gate IDs, and compiler metadata stay private."},"model_declaration":{"type":"string","format":"binary","description":"Optional compact UTF-8 YAML or JSON modeling declaration (max 64 KB): one `continuous_columns` list naming numeric columns to model as continuous measures (e.g. a wave or year index — the prerequisite for forward conditioning). The API validates it against the cleaned upload before fitting; unknown keys and non-numeric columns are refused. Works in both missing_data modes."},"wait_seconds":{"type":"integer","minimum":0,"maximum":60,"default":0,"description":"Compatibility field accepted from 0 through 60. It does not hold the request open; poll job_id after 202."},"idempotency_key":{"type":"string","description":"Form-field equivalent of the Idempotency-Key header. Header preferred. Format constraint (`^[A-Za-z0-9._:-]{1,128}$`) lives here in the description rather than in schema.pattern, for consistency with the header schema and to keep plumber2 from short-circuiting the handler- level idempotency validator."}}}}}},"responses":{"200":{"description":"Training completed before the immediate status read.","content":{"application/json":{"schema":{"type":"object","required":["dataset_id","status","request_id","expires_at","cleaning_summary","training_summary","routing"],"properties":{"dataset_id":{"type":"string"},"status":{"type":"string","enum":["ready"]},"request_id":{"type":"string"},"expires_at":{"type":"string","format":"date-time"},"cleaning_summary":{"type":"object","additionalProperties":false,"required":["missing_data","input_rows","input_columns","clean_rows","clean_columns","retained_schema","type_changes","dropped_columns","row_actions","warnings"],"properties":{"missing_data":{"type":"string","enum":["complete","preserve"],"description":"Missing-data mode selected when this dataset was created. Changing it requires creating a new dataset."},"input_rows":{"type":"integer","minimum":0,"nullable":true},"input_columns":{"type":"integer","minimum":0,"nullable":true},"clean_rows":{"type":"integer","minimum":0,"nullable":true},"clean_columns":{"type":"integer","minimum":0,"nullable":true},"retained_schema":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["name","type"],"properties":{"name":{"type":"string"},"type":{"type":"string","enum":["numeric","integer","logical","factor","ordered","character","other"]}}}},"type_changes":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["name","from","to"],"properties":{"name":{"type":"string"},"from":{"type":"string","enum":["numeric","integer","logical","factor","ordered","character","other"]},"to":{"type":"string","enum":["numeric","integer","logical","factor","ordered","character","other"]}}}},"dropped_columns":{"type":"array","description":"Observable column removals. identifier_like_column identifies row identifiers; unsupported_datetime identifies unsupported date/time fields; constant_categorical and low_variance carry no usable variation; redundant_numeric duplicates other numeric information; rare_category cannot be estimated reliably from available rows.","items":{"type":"object","additionalProperties":false,"required":["name","reason"],"properties":{"name":{"type":"string"},"reason":{"type":"string","enum":["identifier_like_column","unsupported_datetime","constant_categorical","rare_category","low_variance","redundant_numeric","other"]}}}},"row_actions":{"type":"array","description":"Observable row removals. rare_category means one or more rows contained categories too sparse to estimate reliably.","items":{"type":"object","additionalProperties":false,"required":["reason","rows_removed"],"properties":{"reason":{"type":"string","enum":["rare_category"]},"rows_removed":{"type":"integer","minimum":1}}}},"warnings":{"type":"array","description":"Customer-actionable cleaning warnings; normally empty.","items":{"type":"object","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"routing":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"},"rules":{"type":"array","nullable":true,"items":{"type":"object","required":["when","ask"],"properties":{"when":{"type":"object"},"ask":{"type":"array","items":{"type":"string"}}}}},"checks":{"type":"object","nullable":true,"properties":{"answered_outside_routes":{"type":"integer"},"missing_within_routes":{"type":"array","items":{"type":"object","required":["column","missing_answers","message"],"properties":{"column":{"type":"string"},"missing_answers":{"type":"integer"},"eligible_rows":{"type":"integer","nullable":true},"missing_rate":{"type":"number","nullable":true},"message":{"type":"string"}}}}}},"warnings":{"type":"array","nullable":true,"items":{"type":"object","required":["code","column","message"],"properties":{"code":{"type":"string"},"column":{"type":"string"},"message":{"type":"string"}}}}},"description":"Accepted customer routing rules and actionable validation checks. Internal hashes and compiler metadata are intentionally omitted."},"training_summary":{"type":"object","description":"Trained-at timestamp + duration. See GET /v1/datasets/{id} for the full shape."}}},"example":{"dataset_id":"ds_2YH3Kxq8wABC123def456","status":"ready","request_id":"req_2YH3Kxq8wABC123def456","expires_at":"2026-05-02T17:30:00Z","cleaning_summary":{"missing_data":"complete","input_rows":800,"input_columns":8,"clean_rows":794,"clean_columns":5,"retained_schema":[{"name":"age","type":"numeric"},{"name":"segment","type":"factor"}],"type_changes":[{"name":"member","from":"numeric","to":"logical"}],"dropped_columns":[{"name":"respondent.id","reason":"identifier_like_column"}],"row_actions":[{"reason":"rare_category","rows_removed":6}],"warnings":[]},"routing":{"enabled":false},"training_summary":{"trained_at":"2026-05-01T17:30:42Z","duration_ms":42000}}}},"links":{"GetDataset":{"operationId":"getDataset","parameters":{"dataset_id":"$response.body#/dataset_id"},"description":"Fetch metadata for the created dataset."},"GetDatasetSchema":{"operationId":"getDatasetSchema","parameters":{"dataset_id":"$response.body#/dataset_id"},"description":"Fetch the cleaned schema before building generation conditions."},"CreateGeneration":{"operationId":"createGeneration","parameters":{"dataset_id":"$response.body#/dataset_id"},"description":"Create synthetic rows from the trained dataset."}}},"202":{"description":"Training accepted; not yet complete. Poll GET /v1/jobs/{job_id} until status is `completed`, then call GET /v1/datasets/{dataset_id} for the full metadata. The dataset_id is already returned here so the polling URL can be constructed without waiting.","content":{"application/json":{"schema":{"type":"object","required":["dataset_id","status","request_id","job_id"],"properties":{"dataset_id":{"type":"string"},"status":{"type":"string","enum":["processing"]},"request_id":{"type":"string"},"job_id":{"type":"string"}}},"example":{"dataset_id":"ds_2YH3Kxq8wABC123def456","status":"processing","request_id":"req_2YH3Kxq8wABC123def456","job_id":"job_2YH3Kxq8wABC123def456"}}},"links":{"GetJob":{"operationId":"getJob","parameters":{"job_id":"$response.body#/job_id"},"description":"Poll asynchronous dataset training."}}},"400":{"description":"Validation error in form fields or body shape.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_invalid_field"},"title":{"type":"string","example":"Bad Request"},"status":{"type":"integer","example":400},"detail":{"type":"string","example":"wait_seconds must be a non-negative integer up to 60."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_invalid_field"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_invalid_field","title":"Bad Request","status":400,"detail":"wait_seconds must be a non-negative integer up to 60.","code":"simio_invalid_field"}}}},"422":{"description":"The route plan is unsupported, incompatible with the cleaned schema, or contradicted by observed routing cells.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/route_plan_empirical_error"},"title":{"type":"string","example":"Unprocessable Entity"},"status":{"type":"integer","example":422},"detail":{"type":"string","example":"The routing declaration does not match the cleaned dataset."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"route_plan_empirical_error"}}},"example":{"type":"https://api.simulacra-data.com/errors/route_plan_empirical_error","title":"Unprocessable Entity","status":422,"detail":"The routing declaration does not match the cleaned dataset.","code":"route_plan_empirical_error"}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"409":{"description":"Same idempotency key was used with a different request body, or a request with this key is still in flight.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_idempotency_conflict"},"title":{"type":"string","example":"Conflict"},"status":{"type":"integer","example":409},"detail":{"type":"string","example":"idempotency_key was used with a different request body."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_idempotency_conflict"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_idempotency_conflict","title":"Conflict","status":409,"detail":"idempotency_key was used with a different request body.","code":"simio_idempotency_conflict"}}}},"413":{"description":"Upload exceeds the per-request size cap (500 MB default).","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_file_too_large"},"title":{"type":"string","example":"Payload Too Large"},"status":{"type":"integer","example":413},"detail":{"type":"string","example":"seed_file exceeds the maximum upload size."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_file_too_large"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_file_too_large","title":"Payload Too Large","status":413,"detail":"seed_file exceeds the maximum upload size.","code":"simio_file_too_large"}}}},"415":{"description":"Content-Type is not multipart/form-data.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_unsupported_content_type"},"title":{"type":"string","example":"Unsupported Media Type"},"status":{"type":"integer","example":415},"detail":{"type":"string","example":"Content-Type must be multipart/form-data."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_unsupported_content_type"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_unsupported_content_type","title":"Unsupported Media Type","status":415,"detail":"Content-Type must be multipart/form-data.","code":"simio_unsupported_content_type"}}}}},"x-codeSamples":[{"lang":"cURL","label":"Upload a routed survey","source":"curl -sS -X POST \"${SIMIO_API_BASE}/v1/datasets\" \\\n  -H \"Authorization: Bearer ${ACCESS_TOKEN}\" \\\n  -F \"seed_file=@routed-seed.csv;type=text/csv\" \\\n  -F \"missing_data=preserve\" \\\n  -F \"route_plan=@routes.yaml;type=application/yaml\""}],"tags":["Guided Workflow"],"x-openai-isConsequential":true},"get":{"summary":"List datasets for the authenticated tenant","description":"Paginated list of dataset metadata, newest first. Use `limit` and `cursor` to page. Returns metadata only — schemas come from GET /v1/datasets/{dataset_id}/schema. The cursor is opaque; clients must not parse or construct it manually.","security":[{"bearerAuth":[]}],"parameters":[],"operationId":"listDatasets","responses":{"200":{"description":"Page of datasets.","content":{"application/json":{"schema":{"type":"object","required":["datasets","limit","request_id"],"properties":{"datasets":{"type":"array","items":{"type":"object","required":["dataset_id","status","created_at","expires_at"],"properties":{"dataset_id":{"type":"string"},"status":{"type":"string","enum":["ready"]},"display_name":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"}}}},"limit":{"type":"integer"},"next_cursor":{"type":"string","nullable":true},"request_id":{"type":"string"}}},"example":{"datasets":[{"dataset_id":"ds_2YH3Kxq8wABC123def456","status":"ready","display_name":"Quickstart seed","created_at":"2026-05-01T17:30:00Z","expires_at":"2026-05-02T17:30:00Z"}],"limit":25,"next_cursor":{},"request_id":"req_2YH3Kxq8wABC123def456"}}}},"400":{"description":"Bad limit (>100, non-integer) or malformed cursor.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_invalid_field"},"title":{"type":"string","example":"Bad Request"},"status":{"type":"integer","example":400},"detail":{"type":"string","example":"limit must be an integer between 1 and 100."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_invalid_field"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_invalid_field","title":"Bad Request","status":400,"detail":"limit must be an integer between 1 and 100.","code":"simio_invalid_field"}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}}},"x-codeSamples":[{"lang":"cURL","label":"cURL","source":"curl -sS \"$SIMIO_API_BASE/v1/datasets?limit=25\" \\\n  -H \"Authorization: Bearer $TOKEN\""}],"tags":["Dataset Management"],"x-openai-isConsequential":false}},"/v1/route-plans/validate":{"post":{"summary":"04. Validate a routing declaration without fitting","description":"Accepts `seed_file` and a compact `route_plan` as multipart data. The route plan is YAML or JSON containing a `routes` list; each rule contains one `when` trigger/value mapping and an `ask` column or column list. You do not provide dataset names, checksums, gate IDs, provenance, or missing-answer policy. Runs the production preserve-mode cleaning path, validates routing against the resulting schema and observed cells, infers ordinary item nonresponse inside asked populations, and returns only the normalized rules and actionable checks. No dataset is fitted, stored, or billed; hashes and compiler metadata remain internal.","security":[{"bearerAuth":[]}],"parameters":[],"operationId":"validateRoutePlan","requestBody":{"required":true,"content":{"multipart/form-data":{"schema":{"type":"object","required":["seed_file","route_plan"],"properties":{"seed_file":{"type":"string","format":"binary"},"route_plan":{"type":"string","format":"binary","description":"UTF-8 YAML or JSON (max 256 KB). Compact form: `routes: [{when: {trigger: [values]}, ask: columns}]`. `ask` may be one column or a list. Internal binding metadata is derived after the cleaned seed is validated."},"display_name":{"type":"string","maxLength":200}}}}}},"responses":{"200":{"description":"Routing declaration is valid for the cleaned upload.","content":{"application/json":{"schema":{"type":"object","required":["valid","request_id","routing"],"properties":{"valid":{"type":"boolean"},"request_id":{"type":"string"},"routing":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"},"rules":{"type":"array","nullable":true,"items":{"type":"object","required":["when","ask"],"properties":{"when":{"type":"object"},"ask":{"type":"array","items":{"type":"string"}}}}},"checks":{"type":"object","nullable":true,"properties":{"answered_outside_routes":{"type":"integer"},"missing_within_routes":{"type":"array","items":{"type":"object","required":["column","missing_answers","message"],"properties":{"column":{"type":"string"},"missing_answers":{"type":"integer"},"eligible_rows":{"type":"integer","nullable":true},"missing_rate":{"type":"number","nullable":true},"message":{"type":"string"}}}}}},"warnings":{"type":"array","nullable":true,"items":{"type":"object","required":["code","column","message"],"properties":{"code":{"type":"string"},"column":{"type":"string"},"message":{"type":"string"}}}}},"description":"Accepted customer routing rules and actionable validation checks. Internal hashes and compiler metadata are intentionally omitted."}}}}}},"400":{"description":"Malformed declaration or upload.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/route_plan_syntax_error"},"title":{"type":"string","example":"Bad Request"},"status":{"type":"integer","example":400},"detail":{"type":"string","example":"The route_plan part is required."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"route_plan_syntax_error"}}},"example":{"type":"https://api.simulacra-data.com/errors/route_plan_syntax_error","title":"Bad Request","status":400,"detail":"The route_plan part is required.","code":"route_plan_syntax_error"}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"429":{"description":"Routing preflight request limit reached.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_route_preflight_rate_limited"},"title":{"type":"string","example":"Too Many Requests"},"status":{"type":"integer","example":429},"detail":{"type":"string","example":"Wait briefly and try the validation again."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_route_preflight_rate_limited"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_route_preflight_rate_limited","title":"Too Many Requests","status":429,"detail":"Wait briefly and try the validation again.","code":"simio_route_preflight_rate_limited"}}}},"422":{"description":"Declaration contradicts the cleaned data or uses unsupported routing.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/route_plan_empirical_error"},"title":{"type":"string","example":"Unprocessable Entity"},"status":{"type":"integer","example":422},"detail":{"type":"string","example":"The routing declaration does not match the cleaned dataset."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"route_plan_empirical_error"}}},"example":{"type":"https://api.simulacra-data.com/errors/route_plan_empirical_error","title":"Unprocessable Entity","status":422,"detail":"The routing declaration does not match the cleaned dataset.","code":"route_plan_empirical_error"}}}}},"x-codeSamples":[{"lang":"cURL","label":"Validate routing before fitting","source":"cat > routes.yaml <<'YAML'\nroutes:\n  - when: {channel: [Online]}\n    ask: online_experience\nYAML\n\ncurl -sS -X POST \"${SIMIO_API_BASE}/v1/route-plans/validate\" \\\n  -H \"Authorization: Bearer ${ACCESS_TOKEN}\" \\\n  -F \"seed_file=@routed-seed.csv;type=text/csv\" \\\n  -F \"route_plan=@routes.yaml;type=application/yaml\""}],"tags":["Guided Workflow"],"x-openai-isConsequential":false}},"/v1/datasets/{dataset_id}":{"get":{"summary":"Get a dataset by id","description":"Returns dataset metadata: status, retention timestamp, display name, cleaning summary, and a public training summary. Schemas live at GET /v1/datasets/{dataset_id}/schema. A successful read bumps the dataset's `last_used_at` and extends `expires_at` by the original TTL gap, so an active customer's dataset stays alive without explicit /extend calls.","security":[{"bearerAuth":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"description":"Dataset identifier returned by POST /v1/datasets. Matches `^[A-Za-z0-9_-]{1,128}$`. Malformed values return 404.","schema":{"type":"string"},"example":"ds_2YH3Kxq8wABC123def456"}],"responses":{"200":{"description":"Dataset metadata.","content":{"application/json":{"schema":{"type":"object","required":["dataset_id","status","request_id","created_at","expires_at","cleaning_summary","training_summary","routing"],"properties":{"dataset_id":{"type":"string"},"status":{"type":"string","enum":["ready"],"description":"Persisted records always have status='ready'. Training failures are surfaced through the error envelope, not as a 200 with a failure status."},"request_id":{"type":"string"},"display_name":{"type":"string","nullable":true},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"cleaning_summary":{"type":"object","additionalProperties":false,"required":["missing_data","input_rows","input_columns","clean_rows","clean_columns","retained_schema","type_changes","dropped_columns","row_actions","warnings"],"properties":{"missing_data":{"type":"string","enum":["complete","preserve"],"description":"Missing-data mode selected when this dataset was created. Changing it requires creating a new dataset."},"input_rows":{"type":"integer","minimum":0,"nullable":true},"input_columns":{"type":"integer","minimum":0,"nullable":true},"clean_rows":{"type":"integer","minimum":0,"nullable":true},"clean_columns":{"type":"integer","minimum":0,"nullable":true},"retained_schema":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["name","type"],"properties":{"name":{"type":"string"},"type":{"type":"string","enum":["numeric","integer","logical","factor","ordered","character","other"]}}}},"type_changes":{"type":"array","items":{"type":"object","additionalProperties":false,"required":["name","from","to"],"properties":{"name":{"type":"string"},"from":{"type":"string","enum":["numeric","integer","logical","factor","ordered","character","other"]},"to":{"type":"string","enum":["numeric","integer","logical","factor","ordered","character","other"]}}}},"dropped_columns":{"type":"array","description":"Observable column removals. identifier_like_column identifies row identifiers; unsupported_datetime identifies unsupported date/time fields; constant_categorical and low_variance carry no usable variation; redundant_numeric duplicates other numeric information; rare_category cannot be estimated reliably from available rows.","items":{"type":"object","additionalProperties":false,"required":["name","reason"],"properties":{"name":{"type":"string"},"reason":{"type":"string","enum":["identifier_like_column","unsupported_datetime","constant_categorical","rare_category","low_variance","redundant_numeric","other"]}}}},"row_actions":{"type":"array","description":"Observable row removals. rare_category means one or more rows contained categories too sparse to estimate reliably.","items":{"type":"object","additionalProperties":false,"required":["reason","rows_removed"],"properties":{"reason":{"type":"string","enum":["rare_category"]},"rows_removed":{"type":"integer","minimum":1}}}},"warnings":{"type":"array","description":"Customer-actionable cleaning warnings; normally empty.","items":{"type":"object","additionalProperties":false,"required":["code","message"],"properties":{"code":{"type":"string"},"message":{"type":"string"}}}}}},"routing":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"},"rules":{"type":"array","nullable":true,"items":{"type":"object","required":["when","ask"],"properties":{"when":{"type":"object"},"ask":{"type":"array","items":{"type":"string"}}}}},"checks":{"type":"object","nullable":true,"properties":{"answered_outside_routes":{"type":"integer"},"missing_within_routes":{"type":"array","items":{"type":"object","required":["column","missing_answers","message"],"properties":{"column":{"type":"string"},"missing_answers":{"type":"integer"},"eligible_rows":{"type":"integer","nullable":true},"missing_rate":{"type":"number","nullable":true},"message":{"type":"string"}}}}}},"warnings":{"type":"array","nullable":true,"items":{"type":"object","required":["code","column","message"],"properties":{"code":{"type":"string"},"column":{"type":"string"},"message":{"type":"string"}}}}},"description":"Accepted customer routing rules and actionable validation checks. Internal hashes and compiler metadata are intentionally omitted."},"training_summary":{"type":"object","properties":{"trained_at":{"type":"string","format":"date-time","nullable":true},"duration_ms":{"type":"integer","nullable":true}}}}},"example":{"dataset_id":"ds_2YH3Kxq8wABC123def456","status":"ready","request_id":"req_2YH3Kxq8wABC123def456","display_name":"Quickstart seed","created_at":"2026-05-01T17:30:00Z","expires_at":"2026-05-02T17:30:00Z","cleaning_summary":{"missing_data":"complete","input_rows":800,"input_columns":8,"clean_rows":794,"clean_columns":5,"retained_schema":[{"name":"age","type":"numeric"},{"name":"segment","type":"factor"}],"type_changes":[{"name":"member","from":"numeric","to":"logical"}],"dropped_columns":[{"name":"respondent.id","reason":"identifier_like_column"}],"row_actions":[{"reason":"rare_category","rows_removed":6}],"warnings":[]},"routing":{"enabled":false},"training_summary":{"trained_at":"2026-05-01T17:30:42Z","duration_ms":42000}}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"404":{"description":"Dataset missing, expired, or owned by another tenant.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_dataset_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Dataset not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_dataset_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_dataset_not_found","title":"Not Found","status":404,"detail":"Dataset not found.","code":"simio_dataset_not_found"}}}}},"x-codeSamples":[{"lang":"cURL","label":"cURL","source":"curl -sS \"$SIMIO_API_BASE/v1/datasets/ds_2YH3Kxq8wABC123def456\" \\\n  -H \"Authorization: Bearer $TOKEN\""}],"operationId":"getDataset","tags":["Dataset Management"],"x-openai-isConsequential":false},"delete":{"summary":"Delete a dataset","description":"Deletes the dataset and retained files associated with it. Idempotent — deleting an already-deleted dataset returns 204. Cross-tenant attempts return 404 (per the V1 anti-enumeration convention).","security":[{"bearerAuth":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"description":"Dataset identifier returned by POST /v1/datasets. Matches `^[A-Za-z0-9_-]{1,128}$`. Malformed values return 404.","schema":{"type":"string"},"example":"ds_2YH3Kxq8wABC123def456"}],"responses":{"204":{"description":"Dataset deleted (or did not exist). No body."},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"404":{"description":"Dataset belongs to another tenant.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_dataset_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Dataset not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_dataset_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_dataset_not_found","title":"Not Found","status":404,"detail":"Dataset not found.","code":"simio_dataset_not_found"}}}}},"x-codeSamples":[{"lang":"cURL","label":"cURL","source":"curl -sS -i -X DELETE \\\n  \"$SIMIO_API_BASE/v1/datasets/ds_2YH3Kxq8wABC123def456\" \\\n  -H \"Authorization: Bearer $TOKEN\""}],"operationId":"deleteDataset","tags":["Dataset Management"],"x-openai-isConsequential":true}},"/v1/datasets/{dataset_id}/schema":{"get":{"summary":"06. Get the inferred schema of a dataset","description":"Column-by-column schema (name, type, levels for categoricals, range for numerics) inferred during training. Use this BEFORE constructing a `conditions` block on POST /v1/datasets/{id}/ generations — the level names and numeric bounds are the source of truth, never client-side guesses.","security":[{"bearerAuth":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"description":"Dataset identifier returned by POST /v1/datasets. Matches `^[A-Za-z0-9_-]{1,128}$`. Malformed values return 404.","schema":{"type":"string"},"example":"ds_2YH3Kxq8wABC123def456"}],"responses":{"200":{"description":"Inferred schema.","content":{"application/json":{"schema":{"type":"object","required":["dataset_id","schema","routing","request_id"],"properties":{"dataset_id":{"type":"string"},"request_id":{"type":"string"},"routing":{"type":"object","required":["enabled"],"properties":{"enabled":{"type":"boolean"},"rules":{"type":"array","nullable":true,"items":{"type":"object","required":["when","ask"],"properties":{"when":{"type":"object"},"ask":{"type":"array","items":{"type":"string"}}}}},"checks":{"type":"object","nullable":true,"properties":{"answered_outside_routes":{"type":"integer"},"missing_within_routes":{"type":"array","items":{"type":"object","required":["column","missing_answers","message"],"properties":{"column":{"type":"string"},"missing_answers":{"type":"integer"},"eligible_rows":{"type":"integer","nullable":true},"missing_rate":{"type":"number","nullable":true},"message":{"type":"string"}}}}}},"warnings":{"type":"array","nullable":true,"items":{"type":"object","required":["code","column","message"],"properties":{"code":{"type":"string"},"column":{"type":"string"},"message":{"type":"string"}}}}},"description":"Accepted customer routing rules and actionable validation checks. Internal hashes and compiler metadata are intentionally omitted."},"schema":{"type":"array","description":"Column manifest produced at training time. One entry per retained column; order matches the cleaned training frame, not the original upload.","items":{"type":"object","required":["name","type"],"properties":{"name":{"type":"string"},"type":{"type":"string","description":"Source-language column type. Categorical kinds: `factor`, `ordered`, `character`, `string`, `logical`, `boolean`. Numeric kinds: `numeric`, `integer`, `double`."},"levels":{"type":"array","items":{"type":"string"},"description":"Categorical kinds only."},"min":{"type":"number","nullable":true,"description":"Numeric kinds only."},"max":{"type":"number","nullable":true,"description":"Numeric kinds only."},"model_as":{"type":"string","enum":["continuous"],"description":"Present when an accepted modeling declaration covers this column: it is modeled as a continuous measure (the forward-conditionable axis). Absent otherwise."},"conditioning_excluded":{"type":"boolean","nullable":true,"description":"TRUE when the column was retained for training but is not eligible for use in the `conditions` block (e.g., free-text or high-cardinality identifiers)."}}}}}},"example":{"dataset_id":"ds_2YH3Kxq8wABC123def456","request_id":"req_2YH3Kxq8wABC123def456","routing":{"enabled":false},"schema":[{"name":"age","type":"integer","min":18,"max":65,"conditioning_excluded":false},{"name":"segment","type":"factor","levels":["Value","Mainstream","Premium"],"conditioning_excluded":false},{"name":"channel","type":"factor","levels":["Retail","Online","Club"],"conditioning_excluded":false},{"name":"region","type":"factor","levels":["Northeast","South","Midwest","West"],"conditioning_excluded":false},{"name":"purchase_intent","type":"numeric","min":0,"max":100,"conditioning_excluded":false}]}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"404":{"description":"Dataset missing, expired, or owned by another tenant.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_dataset_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Dataset not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_dataset_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_dataset_not_found","title":"Not Found","status":404,"detail":"Dataset not found.","code":"simio_dataset_not_found"}}}}},"x-codeSamples":[{"lang":"cURL","label":"cURL","source":"curl -sS \"$SIMIO_API_BASE/v1/datasets/ds_2YH3Kxq8wABC123def456/schema\" \\\n  -H \"Authorization: Bearer $TOKEN\""}],"operationId":"getDatasetSchema","tags":["Guided Workflow"],"x-openai-isConsequential":false}},"/v1/datasets/{dataset_id}/extend":{"post":{"summary":"Extend a dataset's retention window","description":"Pushes the dataset's `expires_at` further out by the same TTL increment used at creation (typically 24 hours). Subject to a lifetime cap (7 days by default) — extending past that returns 409. Successful reads via GET /v1/datasets/{id} also bump the expiry, so explicit /extend calls are usually only needed for long idle periods between reads.","security":[{"bearerAuth":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"description":"Dataset identifier returned by POST /v1/datasets. Matches `^[A-Za-z0-9_-]{1,128}$`. Malformed values return 404.","schema":{"type":"string"},"example":"ds_2YH3Kxq8wABC123def456"}],"responses":{"200":{"description":"New expires_at returned.","content":{"application/json":{"schema":{"type":"object","required":["dataset_id","status","expires_at","created_at","request_id"],"properties":{"dataset_id":{"type":"string"},"status":{"type":"string","enum":["ready"]},"expires_at":{"type":"string","format":"date-time"},"created_at":{"type":"string","format":"date-time"},"request_id":{"type":"string"}}},"example":{"dataset_id":"ds_2YH3Kxq8wABC123def456","status":"ready","expires_at":"2026-05-03T17:30:00Z","created_at":"2026-05-01T17:30:00Z","request_id":"req_2YH3Kxq8wABC123def456"}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"404":{"description":"Dataset missing, expired, or owned by another tenant.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_dataset_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Dataset not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_dataset_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_dataset_not_found","title":"Not Found","status":404,"detail":"Dataset not found.","code":"simio_dataset_not_found"}}}},"409":{"description":"Dataset has reached its lifetime retention cap and cannot be extended further. Re-upload the seed to create a fresh dataset.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_extend_cap_exceeded"},"title":{"type":"string","example":"Conflict"},"status":{"type":"integer","example":409},"detail":{"type":"string","example":"Dataset has reached its maximum continuous retention window."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_extend_cap_exceeded"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_extend_cap_exceeded","title":"Conflict","status":409,"detail":"Dataset has reached its maximum continuous retention window.","code":"simio_extend_cap_exceeded"}}}}},"x-codeSamples":[{"lang":"cURL","label":"cURL","source":"curl -sS -X POST \\\n  \"$SIMIO_API_BASE/v1/datasets/ds_2YH3Kxq8wABC123def456/extend\" \\\n  -H \"Authorization: Bearer $TOKEN\""}],"operationId":"extendDatasetRetention","tags":["Dataset Management"],"x-openai-isConsequential":true}},"/v1/datasets/{dataset_id}/generations":{"post":{"summary":"07. Create a synthetic-data generation","description":"Body is a JSON object with `row_count`, `output_format`, optional `conditions.categorical`, optional `conditions.numeric`, optional `wait_seconds`, and optional `idempotency_key`. The API returns promptly with 202 + `job_id` when work continues; `wait_seconds` is accepted for compatibility but does not hold the request open. Quota is reserved at request time and committed only when the generation completes. Tight conditions can return fewer rows than requested with `status: \"partial\"`, never more. ALWAYS fetch GET /v1/datasets/{id}/schema before constructing conditions — level names and numeric bounds are the source of truth.  > **Try-It tip:** the docs rewrite this example using the cleaned schema captured in step 05, so the request body uses your dataset's actual columns, levels, and numeric ranges — not the segment/channel example.","security":[{"bearerAuth":[]}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["row_count","output_format"],"properties":{"row_count":{"type":"integer","minimum":1,"description":"Number of synthetic rows requested. Subject to a per-request cap (tenant policy)."},"output_format":{"type":"string","enum":["parquet","csv","arrow"],"description":"Artifact serialization format."},"wait_seconds":{"type":"integer","minimum":0,"maximum":60,"default":0,"description":"Compatibility field accepted from 0 through 60. It does not hold the request open; poll job_id after 202."},"idempotency_key":{"type":"string","description":"Body-field equivalent of Idempotency-Key header. Format `^[A-Za-z0-9._:-]{1,128}$`; constraint kept in description (not in schema.pattern) for consistency with the header and to keep plumber2 from short-circuiting the handler-level validator."},"conditions":{"type":"object","additionalProperties":false,"description":"Optional scenario conditioning. Categorical values are desired outcome percentages (target shares); numeric values are bilateral min/max ranges.","properties":{"categorical":{"type":"object","description":"Map of column name -> { level: target_share }. Omitted fitted levels receive a zero target share; the supplied non-negative shares for each column must sum to 1 within 1e-6. Level names must come from /v1/datasets/{id}/schema.","additionalProperties":{"type":"object","additionalProperties":{"type":"number","minimum":0}}},"numeric":{"type":"object","description":"Map of column name -> { min, max }. Both bounds are required; min must be <= max. Bounds outside the schema's reported min/max are clamped.","additionalProperties":{"type":"object","additionalProperties":false,"required":["min","max"],"properties":{"min":{"type":"number"},"max":{"type":"number"}}}}}}}},"example":{"row_count":1000,"output_format":"parquet","conditions":{"categorical":{"segment":{"Premium":0.55,"Mainstream":0.35,"Value":0.1},"channel":{"Online":0.7,"Retail":0.2,"Club":0.1}},"numeric":{"age":{"min":25,"max":44},"purchase_intent":{"min":70,"max":100}}}}}}},"parameters":[{"name":"dataset_id","in":"path","required":true,"description":"Dataset identifier returned by POST /v1/datasets. Matches `^[A-Za-z0-9_-]{1,128}$`. Malformed values return 404.","schema":{"type":"string"},"example":"ds_2YH3Kxq8wABC123def456"},{"name":"Idempotency-Key","in":"header","required":false,"description":"Optional idempotency key (alternative to the body field). If both are sent, the body field takes precedence. Format: `^[A-Za-z0-9._:-]{1,128}$`. Scoped per-dataset. Constraint kept in description rather than schema.pattern to keep plumber2 from short-circuiting the handler-level validator.","schema":{"type":"string"},"example":"generation-1714589400"}],"responses":{"200":{"description":"Generation completed before the immediate status read; full row count produced.","content":{"application/json":{"schema":{"type":"object","required":["generation_id","dataset_id","status","request_id","output_format","rows_requested","rows_generated","rows_charged","usage_url","expires_at"],"properties":{"generation_id":{"type":"string"},"dataset_id":{"type":"string"},"status":{"type":"string","enum":["ready"]},"request_id":{"type":"string"},"output_format":{"type":"string","enum":["parquet","csv","arrow"]},"rows_requested":{"type":"integer"},"rows_generated":{"type":"integer"},"rows_charged":{"type":"integer","description":"Rows committed to quota. Partial generations charge only the rows actually returned."},"usage_url":{"type":"string","enum":["/v1/usage"]},"expires_at":{"type":"string","format":"date-time"}}},"example":{"generation_id":"gen_2YH3Kxq8wABC123def456","dataset_id":"ds_2YH3Kxq8wABC123def456","status":"ready","request_id":"req_2YH3Kxq8wABC123def456","output_format":"parquet","rows_requested":5000,"rows_generated":5000,"rows_charged":5000,"usage_url":"/v1/usage","expires_at":"2026-05-02T17:31:00Z"}}},"links":{"GetGeneration":{"operationId":"getGeneration","parameters":{"generation_id":"$response.body#/generation_id"},"description":"Fetch generation status and artifact_url."}}},"202":{"description":"Generation accepted; poll GET /v1/jobs/{job_id} until status is `completed`. The `generation_id` is already returned here so callers can construct the polling URL and the eventual GET /v1/generations/{generation_id} URL up front.","content":{"application/json":{"schema":{"type":"object","required":["generation_id","dataset_id","status","request_id","job_id"],"properties":{"generation_id":{"type":"string"},"dataset_id":{"type":"string"},"status":{"type":"string","enum":["processing"]},"request_id":{"type":"string"},"job_id":{"type":"string"}}},"example":{"generation_id":"gen_2YH3Kxq8wABC123def456","dataset_id":"ds_2YH3Kxq8wABC123def456","status":"processing","request_id":"req_2YH3Kxq8wABC123def456","job_id":"job_2YH3Kxq8wABC123def456"}}},"links":{"GetJob":{"operationId":"getJob","parameters":{"job_id":"$response.body#/job_id"},"description":"Poll asynchronous generation work."},"GetGeneration":{"operationId":"getGeneration","parameters":{"generation_id":"$response.body#/generation_id"},"description":"When generation_id is present, poll generation metadata directly."}}},"207":{"description":"Partial success: `rows_generated` < `rows_requested`. Tight conditions can yield fewer rows than requested, never more. Body shape matches 200 but with `status: 'partial'`.","content":{"application/json":{"example":{"generation_id":"gen_2YH3Kxq8wABC123def456","dataset_id":"ds_2YH3Kxq8wABC123def456","status":"partial","request_id":"req_2YH3Kxq8wABC123def456","output_format":"parquet","rows_requested":5000,"rows_generated":3217,"rows_charged":3217,"usage_url":"/v1/usage","expires_at":"2026-05-02T17:31:00Z"}}}},"400":{"description":"Validation error: missing/invalid row_count, bad output_format, malformed conditions, or condition columns/levels not present in the cleaned schema.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_unknown_condition_column"},"title":{"type":"string","example":"Bad Request"},"status":{"type":"integer","example":400},"detail":{"type":"string","example":"Condition column 'purchase_intent' in conditions.numeric is not in the cleaned schema. Call GET /v1/datasets/{dataset_id}/schema and use the returned column names and levels."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_unknown_condition_column"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_unknown_condition_column","title":"Bad Request","status":400,"detail":"Condition column 'purchase_intent' in conditions.numeric is not in the cleaned schema. Call GET /v1/datasets/{dataset_id}/schema and use the returned column names and levels.","code":"simio_unknown_condition_column"}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"402":{"description":"Tenant lifetime row quota exhausted.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_quota_exhausted"},"title":{"type":"string","example":"Payment Required"},"status":{"type":"integer","example":402},"detail":{"type":"string","example":"Tenant row quota exhausted."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_quota_exhausted"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_quota_exhausted","title":"Payment Required","status":402,"detail":"Tenant row quota exhausted.","code":"simio_quota_exhausted"}}}},"404":{"description":"Dataset missing, expired, or owned by another tenant.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_dataset_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Dataset not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_dataset_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_dataset_not_found","title":"Not Found","status":404,"detail":"Dataset not found.","code":"simio_dataset_not_found"}}}},"409":{"description":"Idempotency key reused with a different body.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_idempotency_conflict"},"title":{"type":"string","example":"Conflict"},"status":{"type":"integer","example":409},"detail":{"type":"string","example":"idempotency_key was used with a different request body."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_idempotency_conflict"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_idempotency_conflict","title":"Conflict","status":409,"detail":"idempotency_key was used with a different request body.","code":"simio_idempotency_conflict"}}}},"413":{"description":"The JSON body exceeds 64 KiB or row_count exceeds the authenticated integration's per-request cap.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_request_too_large"},"title":{"type":"string","example":"Payload Too Large"},"status":{"type":"integer","example":413},"detail":{"type":"string","example":"Request body exceeds the 64 KiB size limit."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_request_too_large"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_request_too_large","title":"Payload Too Large","status":413,"detail":"Request body exceeds the 64 KiB size limit.","code":"simio_request_too_large"}}}}},"operationId":"createGeneration","tags":["Guided Workflow"],"x-openai-isConsequential":true},"get":{"summary":"List generations for a dataset","description":"Paginated list of generation metadata for this dataset, newest first. Same `limit`/`cursor` semantics as GET /v1/datasets. Cross-tenant probes return 404 even when the result list would be empty.","security":[{"bearerAuth":[]}],"parameters":[{"name":"dataset_id","in":"path","required":true,"description":"Dataset identifier returned by POST /v1/datasets. Matches `^[A-Za-z0-9_-]{1,128}$`. Malformed values return 404.","schema":{"type":"string"},"example":"ds_2YH3Kxq8wABC123def456"},{"name":"limit","in":"query","required":false,"description":"Items per page. Default 25, max 100. Integer in 1..100; values above 100 or non-integer return 400.","schema":{"type":"integer"},"example":25},{"name":"cursor","in":"query","required":false,"description":"Opaque pagination cursor from a previous response's `next_cursor` field. Do not parse or construct manually.","schema":{"type":"string"},"example":""}],"responses":{"200":{"description":"Page of generations.","content":{"application/json":{"schema":{"type":"object","required":["dataset_id","generations","limit","request_id"],"properties":{"dataset_id":{"type":"string"},"generations":{"type":"array","items":{"type":"object","required":["generation_id","dataset_id","status","output_format","rows_requested","rows_generated","created_at","expires_at"],"properties":{"generation_id":{"type":"string"},"dataset_id":{"type":"string"},"status":{"type":"string","enum":["ready","partial"]},"output_format":{"type":"string","enum":["parquet","csv","arrow"]},"rows_requested":{"type":"integer"},"rows_generated":{"type":"integer"},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"}}}},"limit":{"type":"integer"},"next_cursor":{"type":"string","nullable":true},"request_id":{"type":"string"}}},"example":{"dataset_id":"ds_2YH3Kxq8wABC123def456","generations":[{"generation_id":"gen_2YH3Kxq8wABC123def456","dataset_id":"ds_2YH3Kxq8wABC123def456","status":"ready","output_format":"parquet","rows_requested":5000,"rows_generated":5000,"created_at":"2026-05-01T17:31:00Z","expires_at":"2026-05-02T17:31:00Z"}],"limit":25,"next_cursor":{},"request_id":"req_2YH3Kxq8wABC123def456"}}}},"400":{"description":"Bad limit (>100) or malformed cursor.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_invalid_field"},"title":{"type":"string","example":"Bad Request"},"status":{"type":"integer","example":400},"detail":{"type":"string","example":"limit must be an integer between 1 and 100."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_invalid_field"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_invalid_field","title":"Bad Request","status":400,"detail":"limit must be an integer between 1 and 100.","code":"simio_invalid_field"}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"404":{"description":"Parent dataset missing, expired, or owned by another tenant.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_dataset_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Dataset not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_dataset_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_dataset_not_found","title":"Not Found","status":404,"detail":"Dataset not found.","code":"simio_dataset_not_found"}}}}},"x-codeSamples":[{"lang":"cURL","label":"cURL","source":"curl -sS \"$SIMIO_API_BASE/v1/datasets/ds_2YH3Kxq8wABC123def456/generations?limit=25\" \\\n  -H \"Authorization: Bearer $TOKEN\""}],"operationId":"listDatasetGenerations","tags":["Dataset Management"],"x-openai-isConsequential":false}},"/v1/generations/{generation_id}":{"get":{"summary":"09. Get a generation by id","description":"Returns generation metadata plus a short-lived download handoff for the generated file. Treat `artifact_url` as opaque: it may be API-relative or absolute depending on your tenant's storage mode. If your enterprise integration requires client-side decryption metadata, it appears in `artifact_encryption`; managed tenants receive a relay URL and do not need to handle that metadata. Successful reads refresh the parent dataset's activity timestamp.","security":[{"bearerAuth":[]}],"parameters":[{"name":"generation_id","in":"path","required":true,"description":"Generation identifier returned by POST /v1/datasets/{dataset_id}/generations. Matches `^[A-Za-z0-9_-]{1,128}$`. Malformed values return 404.","schema":{"type":"string"},"example":"gen_2YH3Kxq8wABC123def456"}],"responses":{"200":{"description":"Generation metadata + artifact handoff.","content":{"application/json":{"schema":{"type":"object","required":["generation_id","dataset_id","status","request_id","output_format","rows_requested","rows_generated","rows_charged","usage_url","created_at","expires_at"],"properties":{"generation_id":{"type":"string"},"dataset_id":{"type":"string"},"status":{"type":"string","enum":["ready","partial"],"description":"Persisted records always have status='ready' (full row count) or 'partial' (some rows). Generation failures route through the error envelope, not a 200 with a failure status."},"request_id":{"type":"string"},"output_format":{"type":"string","enum":["parquet","csv","arrow"]},"rows_requested":{"type":"integer"},"rows_generated":{"type":"integer"},"rows_charged":{"type":"integer","description":"Rows committed to quota for this completed generation. Partial generations charge only the rows actually returned."},"usage_url":{"type":"string","enum":["/v1/usage"],"description":"Authenticated current quota snapshot."},"created_at":{"type":"string","format":"date-time"},"expires_at":{"type":"string","format":"date-time"},"artifact_url":{"type":"string","nullable":true,"description":"Short-lived artifact URL. Use it exactly as returned; do not parse or construct it. It may be API-relative or absolute depending on tenant storage mode. Null when the artifact store is not configured."},"artifact_encryption":{"type":"object","description":"Encryption metadata for enterprise storage integrations. Managed tenants receive `scheme='managed'` and no client-side decrypt fields.","properties":{"scheme":{"type":"string","enum":["envelope-aes-256-gcm","managed"]},"wrapped_dek":{"type":"string","nullable":true},"iv":{"type":"string","nullable":true},"encryption_context":{"type":"object","nullable":true}}}}},"examples":{"managed":{"summary":"Managed tenant","value":{"generation_id":"gen_2YH3Kxq8wABC123def456","dataset_id":"ds_2YH3Kxq8wABC123def456","status":"ready","request_id":"req_2YH3Kxq8wABC123def456","output_format":"parquet","rows_requested":5000,"rows_generated":5000,"rows_charged":5000,"usage_url":"/v1/usage","created_at":"2026-05-01T17:31:00Z","expires_at":"2026-05-02T17:31:00Z","artifact_url":"/v1/generations/gen_2YH3Kxq8wABC123def456/download?expires=1714600000&sig=...","artifact_encryption":{"scheme":"managed"}}},"enterprise":{"summary":"Enterprise tenant","value":{"generation_id":"gen_2YH3Kxq8wABC123def456","dataset_id":"ds_2YH3Kxq8wABC123def456","status":"ready","request_id":"req_2YH3Kxq8wABC123def456","output_format":"parquet","rows_requested":5000,"rows_generated":5000,"rows_charged":5000,"usage_url":"/v1/usage","created_at":"2026-05-01T17:31:00Z","expires_at":"2026-05-02T17:31:00Z","artifact_url":"https://storage.example.com/...","artifact_encryption":{"scheme":"envelope-aes-256-gcm","wrapped_dek":"AQI...base64...","iv":"base64...","encryption_context":{"tenant_id":"ten_abc","generation_id":"gen_2YH3Kxq8wABC123def456"}}}}}}},"links":{"DownloadGenerationArtifact":{"operationId":"downloadGenerationArtifact","parameters":{"generation_id":"$response.body#/generation_id"},"description":"Managed-mode tenants download through the API relay when artifact_url is API-relative."}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"404":{"description":"Generation missing, expired, or owned by another tenant.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_generation_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Generation not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_generation_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_generation_not_found","title":"Not Found","status":404,"detail":"Generation not found.","code":"simio_generation_not_found"}}}}},"x-codeSamples":[{"lang":"cURL","label":"cURL","source":"curl -sS \"$SIMIO_API_BASE/v1/generations/gen_2YH3Kxq8wABC123def456\" \\\n  -H \"Authorization: Bearer $TOKEN\""}],"operationId":"getGeneration","tags":["Guided Workflow"],"x-openai-isConsequential":false}},"/v1/usage":{"get":{"summary":"Get current quota usage","description":"Returns the authenticated integration's current generated-row headroom from the same ledger used to enforce generation. Billing figures are company/subscription-window scoped; `per_request_row_cap` is the authenticated integration's subscription-consumption request limit. Open, unexpired reservations are included. Activity events are not a billing source.","security":[{"bearerAuth":[]}],"parameters":[],"operationId":"getUsage","responses":{"200":{"description":"Current enforcement-ledger quota snapshot.","headers":{"Cache-Control":{"description":"Usage responses are never cacheable.","schema":{"type":"string","example":"no-store"}}},"content":{"application/json":{"schema":{"type":"object","additionalProperties":false,"required":["company_ref","quota_scope","window","rows_committed","rows_reserved","row_limit","rows_available","per_request_row_cap","exhausted","as_of"],"properties":{"company_ref":{"type":"string"},"quota_scope":{"type":"string","enum":["subscription_window","lifetime"]},"window":{"type":"object","nullable":true,"additionalProperties":false,"required":["start","end"],"properties":{"start":{"type":"string","format":"date"},"end":{"type":"string","format":"date"}}},"rows_committed":{"type":"integer","minimum":0},"rows_reserved":{"type":"integer","minimum":0},"row_limit":{"type":"integer","minimum":0,"nullable":true},"rows_available":{"type":"integer","minimum":0,"nullable":true},"per_request_row_cap":{"type":"integer","minimum":1},"exhausted":{"type":"boolean"},"as_of":{"type":"string","format":"date-time"}}},"example":{"company_ref":"ks_and_r","quota_scope":"subscription_window","window":{"start":"2026-07-01","end":"2026-07-31"},"rows_committed":50,"rows_reserved":0,"row_limit":50000,"rows_available":49950,"per_request_row_cap":20000,"exhausted":false,"as_of":"2026-07-22T15:00:00Z"}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"409":{"description":"The authenticated integration does not resolve to exactly one active positive-row-limit subscription window.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_quota_not_configured"},"title":{"type":"string","example":"Quota not configured"},"status":{"type":"integer","example":409},"detail":{"type":"string","example":"No unambiguous active subscription is configured for this account. Contact your Simulacra administrator."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_quota_not_configured"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_quota_not_configured","title":"Quota not configured","status":409,"detail":"No unambiguous active subscription is configured for this account. Contact your Simulacra administrator.","code":"simio_quota_not_configured"}}}}},"tags":["Reference"],"x-openai-isConsequential":false}},"/v1/jobs/{job_id}":{"get":{"summary":"08. Get async job status","description":"Returns the status of a dataset creation or generation job dispatched by the corresponding POST. Poll here after a 202 response. When `status` reaches `completed`, the response includes the `dataset_id` (for `dataset_create` jobs) or `generation_id` (for `generation_create` jobs) produced.","security":[{"bearerAuth":[]}],"parameters":[{"name":"job_id","in":"path","required":true,"description":"Job identifier returned in 202 responses from POST /v1/datasets and POST /v1/datasets/{dataset_id}/generations. Matches `^job_[A-Za-z0-9_-]{4,128}$`. Malformed values return 404.","schema":{"type":"string"},"example":"job_2YH3Kxq8wABC123def456"}],"responses":{"200":{"description":"Job state. Terminal states are completed / failed / cancelled / expired.","content":{"application/json":{"schema":{"type":"object","required":["job_id","job_type","status","request_id","created_at"],"properties":{"job_id":{"type":"string"},"job_type":{"type":"string","enum":["dataset_create","generation_create"]},"status":{"type":"string","enum":["queued","running","completed","failed","cancelled","expired"]},"stage":{"type":"string","description":"Internal pipeline phase, surfaced for diagnostics."},"request_id":{"type":"string"},"created_at":{"type":"string","format":"date-time"},"started_at":{"type":"string","format":"date-time","nullable":true},"completed_at":{"type":"string","format":"date-time","nullable":true},"dataset_id":{"type":"string","nullable":true,"description":"Populated when job_type='dataset_create' and status='completed'."},"generation_id":{"type":"string","nullable":true,"description":"Populated when job_type='generation_create' and status='completed'."},"error":{"type":"object","nullable":true,"description":"Populated when status='failed'.","properties":{"code":{"type":"string"},"message":{"type":"string"}}}}},"examples":{"running-train":{"summary":"Dataset training still running","value":{"job_id":"job_2YH3Kxq8wABC123def456","job_type":"dataset_create","status":"running","stage":"training","request_id":"req_2YH3Kxq8wABC123def456","created_at":"2026-05-01T17:30:00Z","started_at":"2026-05-01T17:30:01Z","completed_at":{},"dataset_id":{},"generation_id":{},"error":{}}},"completed-generation":{"summary":"Generation done","value":{"job_id":"job_2YH3Kxq8wABC123def456","job_type":"generation_create","status":"completed","stage":"completed","request_id":"req_2YH3Kxq8wABC123def456","created_at":"2026-05-01T17:30:00Z","started_at":"2026-05-01T17:30:01Z","completed_at":"2026-05-01T17:30:42Z","dataset_id":"ds_2YH3Kxq8wABC123def456","generation_id":"gen_2YH3Kxq8wABC123def456","error":{}}}}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"404":{"description":"Job not found, expired, or owned by another tenant.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_job_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Job not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_job_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_job_not_found","title":"Not Found","status":404,"detail":"Job not found.","code":"simio_job_not_found"}}}}},"x-codeSamples":[{"lang":"cURL","label":"cURL","source":"curl -sS \"$SIMIO_API_BASE/v1/jobs/job_2YH3Kxq8wABC123def456\" \\\n  -H \"Authorization: Bearer $TOKEN\""}],"operationId":"getJob","tags":["Guided Workflow"],"x-openai-isConsequential":false}},"/v1/signup":{"post":{"summary":"01. Submit a tenant signup request","description":"> **No auth required:** this is the access-request step before credentials exist.  Start here when your team needs API access. Submit the company name and contact email; this access request can be sent directly from the interactive docs before credentials exist. A newly created request returns a request_id that you can poll at GET /v1/signup/{request_id} while Simulacra reviews the request. Save it immediately: re-submitting the same email does not create another pending row, but the public email-only endpoint never repeats an existing request_id or reveals the existing request's state.","security":[],"parameters":[],"operationId":"submitSignupRequest","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["company_name","contact_email"],"properties":{"company_name":{"type":"string","minLength":1,"maxLength":256,"description":"Customer company or team name."},"contact_email":{"type":"string","format":"email","minLength":3,"maxLength":254,"description":"Email address Simulacra uses to contact the requester about approval status. Credentials are claimed through the API after approval, not sent by email."}}},"example":{"company_name":"Acme Research","contact_email":"data-science@acme.example"}}}},"responses":{"202":{"description":"Signup request accepted. A newly created request includes the one-time-disclosed request_id; save it and poll GET /v1/signup/{request_id}. A same-email replay returns only status=accepted and never discloses an existing request capability or state.","content":{"application/json":{"schema":{"oneOf":[{"type":"object","additionalProperties":false,"required":["request_id","status"],"properties":{"request_id":{"type":"string","pattern":"^req_[A-Za-z0-9]{1,64}$","description":"One-time-disclosed capability; pass to GET /v1/signup/{request_id} to poll status."},"status":{"type":"string","enum":["pending"],"description":"Always 'pending' on initial submit."}}},{"type":"object","additionalProperties":false,"required":["status"],"properties":{"status":{"type":"string","enum":["accepted"],"description":"Generic replay acknowledgement; no existing request state is disclosed."}}}]},"examples":{"created":{"summary":"New request","value":{"request_id":"req_2YH3Kxq8wABC123def456","status":"pending"}},"replay":{"summary":"Same-email replay","value":{"status":"accepted"}}}}},"links":{"GetSignupRequest":{"operationId":"getSignupRequest","parameters":{"request_id":"$response.body#/request_id"},"description":"For a newly created request, poll the approval state using the returned request_id. Replay acknowledgements intentionally contain no request_id."}}},"400":{"description":"Validation error: missing or malformed field.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_invalid_field"},"title":{"type":"string","example":"Bad Request"},"status":{"type":"integer","example":400},"detail":{"type":"string","example":"Field 'contact_email' must be a valid email address up to 254 chars."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_invalid_field"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_invalid_field","title":"Bad Request","status":400,"detail":"Field 'contact_email' must be a valid email address up to 254 chars.","code":"simio_invalid_field"}}}}},"tags":["Guided Workflow"],"x-openai-isConsequential":true}},"/v1/signup/{request_id}":{"get":{"summary":"02. Poll a signup request's status","description":"> **No auth required:** use the `request_id` returned by POST /v1/signup.  Check an access request using the request_id returned by POST /v1/signup. Paste that value into the `request_id` path field before using Try-It. This status check is available before credentials exist and does not require a bearer token. The response is `pending`, `approved`, or `declined`. When approved, this response includes a one-time credential_claim_token. Exchange it at POST /v1/credential-claims to retrieve the client_secret. The token expires and can be used once.","security":[],"parameters":[{"name":"request_id","in":"path","required":true,"description":"The request_id returned from POST /v1/signup. Matches `^req_[A-Za-z0-9]{1,64}$`. Malformed values return 404 (anti-enumeration). The regex lives in this description and not in `schema.pattern` because plumber2 enforces parameter `pattern` as a request-time gate ahead of the handler — that short-circuits the handler's intended 404 to a generic 500. The handler's own `validate_signup_request_id_path()` is the source of truth.","schema":{"type":"string"},"example":"req_2YH3Kxq8wABC123def456"}],"responses":{"200":{"description":"Signup request found. `client_id` and `credential_claim_token` are included only when `status` is `approved` and the credential has not yet been claimed.","content":{"application/json":{"schema":{"type":"object","required":["request_id","status"],"properties":{"request_id":{"type":"string","pattern":"^req_[A-Za-z0-9]{1,64}$"},"status":{"type":"string","enum":["pending","approved","declined"]},"client_id":{"type":"string","description":"Auth0 M2M client_id. Present only when `status='approved'`."},"credential_claim_token":{"type":"string","description":"One-time token for POST /v1/credential-claims. Present only until the credential is claimed or the claim expires."}}},"examples":{"pending":{"summary":"Pending review","value":{"request_id":"req_2YH3Kxq8wABC123def456","status":"pending"}},"approved":{"summary":"Approved with client_id","value":{"request_id":"req_2YH3Kxq8wABC123def456","status":"approved","client_id":"acme_research_m2m_8f2a1c","credential_claim_token":"cred_0123456789abcdef0123456789abcdef"}},"declined":{"summary":"Declined","value":{"request_id":"req_2YH3Kxq8wABC123def456","status":"declined"}}}}},"links":{"ClaimCredentials":{"operationId":"claimCredentials","requestBody":{"claim_token":"$response.body#/credential_claim_token"},"description":"When status is approved, pass credential_claim_token as `claim_token` to claim the one-time client secret."}}},"404":{"description":"No signup request matches this request_id.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_signup_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Signup request not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_signup_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_signup_not_found","title":"Not Found","status":404,"detail":"Signup request not found.","code":"simio_signup_not_found"}}}}},"x-codeSamples":[{"lang":"cURL","label":"cURL","source":"# Use the exact request_id returned by POST /v1/signup.\nREQUEST_ID=\"req_2YH3Kxq8wABC123def456\"\ncurl -sS \"$SIMIO_API_BASE/v1/signup/${REQUEST_ID}\""}],"operationId":"getSignupRequest","tags":["Guided Workflow"],"x-openai-isConsequential":false}},"/v1/credential-claims":{"post":{"summary":"03. Claim an approved API credential","description":"Exchange a one-time credential claim token for the Auth0 client_secret. This is the only endpoint that returns the secret. The token expires and can be used once. Use the returned client_id, client_secret, token_url, and audience to mint an Auth0 bearer token, then apply that bearer token in the docs AUTHENTICATION panel before continuing to dataset upload. The applied token is held only for the current browser-tab session; reopening the docs page requires a fresh exchange.  > **Next:** open the AUTHENTICATION panel and either paste a JWT into HTTP Bearer or use the *Exchange credentials and fill HTTP Bearer* form before step 04.","security":[],"parameters":[],"operationId":"claimCredentials","requestBody":{"required":true,"content":{"application/json":{"schema":{"type":"object","required":["claim_token"],"properties":{"claim_token":{"type":"string","description":"One-time token returned by signup or rotation status."}}},"example":{"claim_token":"cred_0123456789abcdef0123456789abcdef"}}}},"responses":{"200":{"description":"Credential returned. Store the client_secret immediately.","content":{"application/json":{"example":{"client_id":"abc123","client_secret":"shown-once","token_url":"https://simulacra-data.us.auth0.com/oauth/token","audience":"https://api.simulacra-data.com","grant_type":"client_credentials","purpose":"initial"}}}},"400":{"description":"The claim_token field is missing or malformed.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_invalid_field"},"title":{"type":"string","example":"Bad Request"},"status":{"type":"integer","example":400},"detail":{"type":"string","example":"Field 'claim_token' is malformed."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_invalid_field"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_invalid_field","title":"Bad Request","status":400,"detail":"Field 'claim_token' is malformed.","code":"simio_invalid_field"}}}},"413":{"description":"The JSON request body exceeds the 64 KiB size limit.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_request_too_large"},"title":{"type":"string","example":"Payload Too Large"},"status":{"type":"integer","example":413},"detail":{"type":"string","example":"Request body exceeds the 64 KiB size limit."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_request_too_large"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_request_too_large","title":"Payload Too Large","status":413,"detail":"Request body exceeds the 64 KiB size limit.","code":"simio_request_too_large"}}}},"404":{"description":"Claim token is unknown, expired, or already used.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_credential_claim_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Credential claim not found, expired, or already used."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_credential_claim_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_credential_claim_not_found","title":"Not Found","status":404,"detail":"Credential claim not found, expired, or already used.","code":"simio_credential_claim_not_found"}}}}},"tags":["Guided Workflow"],"x-openai-isConsequential":true}},"/v1/credential-rotation-requests":{"post":{"summary":"Request API credential rotation","description":"Create a support request to rotate the authenticated client's API credential. Simulacra operators complete the rotation in SimInternal; poll the returned rotation_request_id until a one-time claim token is available.","security":[{"bearerAuth":[]}],"parameters":[],"operationId":"createCredentialRotationRequest","requestBody":{"required":false,"content":{"application/json":{"schema":{"type":"object","properties":{"reason":{"type":"string","description":"Optional customer-facing reason or ticket reference."}}},"example":{"reason":"Rotating after personnel change."}}}},"responses":{"202":{"description":"Rotation request accepted.","content":{"application/json":{"example":{"rotation_request_id":"rot_0123456789abcdef0123456789abcdef","client_id":"abc123","status":"pending"}}},"links":{"GetCredentialRotationRequest":{"operationId":"getCredentialRotationRequest","parameters":{"rotation_request_id":"$response.body#/rotation_request_id"},"description":"Poll the rotation request until the replacement credential is claimable."}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"413":{"description":"The JSON request body exceeds the 64 KiB size limit.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_request_too_large"},"title":{"type":"string","example":"Payload Too Large"},"status":{"type":"integer","example":413},"detail":{"type":"string","example":"Request body exceeds the 64 KiB size limit."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_request_too_large"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_request_too_large","title":"Payload Too Large","status":413,"detail":"Request body exceeds the 64 KiB size limit.","code":"simio_request_too_large"}}}}},"tags":["Reference"],"x-openai-isConsequential":true}},"/v1/credential-rotation-requests/{rotation_request_id}":{"get":{"summary":"Poll an API credential rotation request","description":"Check a previously submitted credential-rotation request. When the request is completed, the response includes a one-time credential_claim_token. Exchange it at POST /v1/credential-claims to retrieve the new secret.","security":[{"bearerAuth":[]}],"parameters":[{"name":"rotation_request_id","in":"path","required":true,"description":"The rot_<id> returned by POST /v1/credential-rotation-requests.","schema":{"type":"string"},"example":"rot_0123456789abcdef0123456789abcdef"}],"responses":{"200":{"description":"Rotation request status.","content":{"application/json":{"example":{"rotation_request_id":"rot_0123456789abcdef0123456789abcdef","client_id":"abc123","status":"completed","credential_claim_token":"cred_0123456789abcdef0123456789abcdef"}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"404":{"description":"No rotation request matches this id for the authenticated client.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_rotation_request_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Credential rotation request not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_rotation_request_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_rotation_request_not_found","title":"Not Found","status":404,"detail":"Credential rotation request not found.","code":"simio_rotation_request_not_found"}}}}},"operationId":"getCredentialRotationRequest","tags":["Reference"],"x-openai-isConsequential":false}},"/v1/credential-rotations":{"post":{"summary":"Rotate the authenticated tenant's API credential","description":"Atomically rotates the calling tenant's Auth0 client_secret and issues a one-time credential_claim_token. POST the token to /v1/credential-claims to retrieve the new client_secret.  Rate-limited to 3 rotations per 24 hours per client_id.  Idempotency-Key is honored. A replay returns the same rotation_request_id and credential_claim_token with HTTP 200 (vs 201 on first call). The replay does NOT call Auth0 again and does not consume the rate-limit budget.  **Capture the credential_claim_token from this response immediately.** It is one-time-use and the response is the only place the token will be surfaced — there is no GET endpoint that returns it. If the response is lost before the token is redeemed, retry the same request with the same Idempotency-Key within 24 hours to recover the same credential_claim_token. Support recovery is only required when no Idempotency-Key was sent or the token was already redeemed.","security":[{"bearerAuth":[]}],"parameters":[],"operationId":"/v1/credential-rotations-post"}},"/v1/generations/{generation_id}/download":{"get":{"summary":"10. Download a generation artifact (managed mode only)","description":"Downloads artifact bytes for managed-mode tenants through the API relay. This endpoint is used only when GET /v1/generations/{id} returns an API-relative `artifact_url`. Enterprise storage integrations may receive a direct URL plus encryption metadata from GET /v1/generations/{id} instead.  Two checks gate access: 1. `expires` and `sig` query parameters are validated as an    API-minted handoff. The values come from `artifact_url` in    GET /v1/generations/{id}; do not construct them by hand. 2. The Auth0 bearer token is required in addition to the URL    signature.  An expired or invalid signature returns 404 (not 401) so that URL-shape probing cannot enumerate generation IDs.","security":[{"bearerAuth":[]}],"parameters":[{"name":"generation_id","in":"path","required":true,"description":"Generation identifier returned by POST /v1/datasets/{dataset_id}/generations. Matches `^[A-Za-z0-9_-]{1,128}$`. Malformed values return 404.","schema":{"type":"string"},"example":"gen_2YH3Kxq8wABC123def456"},{"name":"expires","in":"query","required":true,"description":"Unix timestamp marking URL expiry; minted by the API.","schema":{"type":"integer","format":"int64"},"example":1714600000},{"name":"sig","in":"query","required":true,"description":"HMAC signature over (generation_id, expires); minted by the API.","schema":{"type":"string"},"example":"8f2a1c..."}],"responses":{"200":{"description":"Artifact bytes. Content-Type matches the generation's `output_format` (parquet, csv, or arrow).","content":{"application/octet-stream":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Bearer token missing, malformed, expired, or not minted by the configured Auth0 tenant. The body uses a uniform message — auth failures intentionally do not distinguish missing vs. invalid vs. wrong-audience.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"about:blank"},"title":{"type":"string","example":"Unauthorized"},"status":{"type":"integer","example":401},"detail":{"type":"string","example":"Invalid credentials."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`."}}},"example":{"type":"about:blank","title":"Unauthorized","status":401,"detail":"Invalid credentials."}}}},"404":{"description":"Generation missing, owned by another tenant, OR the URL signature is invalid / expired. Returned uniformly to avoid ID enumeration.","content":{"application/problem+json":{"schema":{"type":"object","required":["type","title","status","detail"],"properties":{"type":{"type":"string","example":"https://api.simulacra-data.com/errors/simio_generation_not_found"},"title":{"type":"string","example":"Not Found"},"status":{"type":"integer","example":404},"detail":{"type":"string","example":"Generation not found."},"code":{"type":"string","description":"Stable Simulacra error code. When present, the `type` URL resolves to `/errors/{code}`.","example":"simio_generation_not_found"}}},"example":{"type":"https://api.simulacra-data.com/errors/simio_generation_not_found","title":"Not Found","status":404,"detail":"Generation not found.","code":"simio_generation_not_found"}}}}},"x-codeSamples":[{"lang":"cURL","label":"cURL","source":"curl -sS -L \\\n  \"$SIMIO_API_BASE/v1/generations/gen_2YH3Kxq8wABC123def456/download?expires=1714600000&sig=8f2a1c...\" \\\n  -H \"Authorization: Bearer $TOKEN\" \\\n  -o synthetic.parquet"}],"operationId":"downloadGenerationArtifact","tags":["Guided Workflow"],"x-openai-isConsequential":false}}},"components":{"securitySchemes":{"bearerAuth":{"type":"http","scheme":"bearer","bearerFormat":"JWT","description":"Auth0 M2M bearer token. Audience must match the API's configured audience."}}},"tags":[{"name":"Guided Workflow","description":"Follow the numbered operation pages in order for the first-run signup -> credential -> optional routing preflight -> dataset -> schema -> generation -> download workflow. After claiming credentials, mint an Auth0 bearer token and paste it into the AUTHENTICATION panel before the upload step; the token is held only for this browser-tab session, so reopening the docs page requires a fresh exchange."},{"name":"Dataset Management","description":"List, inspect, extend, delete, and audit datasets after the core workflow."},{"name":"Reference","description":"Credential rotation and other supporting operations that are not part of the first-run workflow."}],"servers":[{"url":""}]}