A pricing decision.
Every layer of
voxq.
Build the study, inspect the model’s assumptions, then put the questions in front of customers.
Meet Priya
Priya is a product engineer at a 14-person developer-tools company. The team is considering usage-based pricing and has ten days to investigate the risks. She wants to distinguish an objection to price from an objection to unpredictable bills.
This is an illustrative scenario. Her first two questions ask customers to explain their concerns; they do not estimate churn or willingness to pay.
Set up one study workspace
The terminal examples assume a repository checkout, Rust, Bun, jq,
and an Ollama model you already have installed. Replace
YOUR_INSTALLED_MODEL in the downloaded
JSON before running inference. The examples do not download models
or call paid providers automatically.
cargo build export PATH="$PWD/target/debug:$PATH" export VOXQ_DB="$PWD/pricing-study.sqlite" export VOXQ_API="http://127.0.0.1:3102" # Download the study JSON below and save it as pricing-study.json. # Every curl below sends this token. Left unset, the server answers anyone who # can reach the port; set to an empty string, it rejects every request. export VOXQ_INTERNAL_TOKEN="$(openssl rand -hex 32)" # Export the identical value in each terminal that issues curl requests. # serve defaults to 0.0.0.0; bind loopback to keep the port on this machine. voxq --db "$VOXQ_DB" serve --bind 127.0.0.1:3102
Download pricing-study.json. CLI, MCP, Scheme, and the private REST server can share this database. Choose one creation tab; the tabs are alternatives, so running all four creates four surveys.
1. Define the question bank
Two hypothetical personas probe different billing concerns. Their
descriptions are assumptions for the model, not recruited or
representative groups. Save the returned survey ID as
SURVEY_ID for later requests.
jq '.questions' pricing-study.json > questions.json jq '.personas' pricing-study.json > personas.json jq '.bots' pricing-study.json > bots.json voxq --db "$VOXQ_DB" create \ --name "Pricing predictability" \ --questions questions.json --personas personas.json --bots bots.json # Read the returned ID, then: export SURVEY_ID="YOUR_RETURNED_SURVEY_ID"
curl --fail-with-body -H "Authorization: Bearer $VOXQ_INTERNAL_TOKEN" \ -X POST "$VOXQ_API/surveys" \ -H 'Content-Type: application/json' \ --data-binary @pricing-study.json # Read .id in the response and set SURVEY_ID in your shell.
Tool: create_survey Arguments: the contents of pricing-study.json Replace YOUR_INSTALLED_MODEL before calling the tool. Keep the returned id for subsequent tool calls. The two questions are open-ended. See the MCP reference below for the current structured-question schema limitation.
(define s (survey "Pricing predictability" "Explore variable-billing objections")) (persona s "solo-builder" 25 45 "An independent developer paying for tools personally. Usage varies with client work.") (persona s "team-lead" 30 55 "An engineering lead responsible for a team budget and explaining monthly costs.") (bot s "local-model" "ollama" "YOUR_INSTALLED_MODEL") (question s "Your plan changes from a flat fee to usage-based billing. What would you need to know before deciding whether to stay?") (question s "Which change would make variable billing easier to manage, and why?") (display s) ;; Save as pricing-study.scm, then run in your shell: ;; voxq --db "$VOXQ_DB" script pricing-study.scm ;; Keep the printed survey ID.
For CLI and REST JSON, the question field is
type:
open,
multiple_choice,
multi_select,
ranking,
dynamic, or
scale. Choice questions need
choices; scale questions need bounds.
Stable question IDs connect the two sets of answers.
Add an answer-led discovery thread
Priya also wants to discover pricing concerns she did not anticipate. Add a dynamic root to the REST survey payload. Simulation answers the opening question only; real interviews can generate bounded follow-ups.
{"text":"Tell me about an unexpected billing experience.",
"type":"dynamic", "required":false,
"dynamic":{"goal":"Understand unexpected costs and their impact without assuming dissatisfaction.",
"max_follow_ups":2}}
Follow-up SSE events use
question_type: "open", with
parent_question_id, a 1-based
follow_up_index, and
max_follow_ups. Submit the generated
question ID through the existing answer endpoint. Skipping a
follow-up ends that thread. GET/reconnect never generates a new
question.
GET /surveys/{id}/interviews/analysis
returns a separate discoveries array
with parent ID, generated question ID, text, answer, and
rationale. It excludes bearer session IDs. Generated answers do
not enter fixed-question comparison. Semantic adherence to the
goal is model-dependent; the API enforces shape, turn caps, and
persisted provenance.
For multi_select and
ranking, the answer field remains a
string containing a JSON array of distinct authored options.
Rankings must contain every option. Analysis retains
per-respondent arrays as strings, not a pooled percentage.
{"question_id":"priorities", "answer":"[\"Predictability\",\"Price\",\"Flexibility\"]"}
The studio’s same-origin, signed-in
POST /api/authoring/propose accepts
{message, history, draft} and
returns {message, questions, mode}.
It proposes only: no survey is saved or overwritten. Local mode
uses templates, connected mode uses Not Organic inference and
records usage.
2. Check the estimated cost
Priya checks the cost before starting a run. An estimate uses assumed tokens per answer; it is not a spending cap. A missing model rate produces an unavailable estimate, so inspect provider pricing before proceeding.
# Estimation has no native command on this surface. # Use the REST tab, or the studio on its own workspace. # The REST server must use the same database as your CLI/MCP study.
curl --fail-with-body -H "Authorization: Bearer $VOXQ_INTERNAL_TOKEN" \ -X POST "$VOXQ_API/surveys/$SURVEY_ID/estimate"
# Estimation has no native command on this surface. # Use the REST tab, or the studio on its own workspace. # The REST server must use the same database as your CLI/MCP study.
# Estimation has no native command on this surface. # Use the REST tab, or the studio on its own workspace. # The REST server must use the same database as your CLI/MCP study.
3. Run the simulation
One run asks both questions through both personas using the configured model: four attempted answers. Additional models increase that count. Rerunning appends responses; summaries combine saved runs.
voxq --db "$VOXQ_DB" run "$SURVEY_ID"
curl --fail-with-body -H "Authorization: Bearer $VOXQ_INTERNAL_TOKEN" \ -X POST "$VOXQ_API/surveys/$SURVEY_ID/run"
Tool: run_survey
Arguments: {"survey_id": "YOUR_RETURNED_SURVEY_ID"}
;; Scheme defines surveys and reads saved results. ;; It has no native simulation runner. ;; After creating the survey, run from your shell: voxq --db "$VOXQ_DB" run "$SURVEY_ID"
4. Inspect what the models said
Priya reads the individual explanations before drawing conclusions. If “budget predictability” appears repeatedly, it becomes a hypothesis to check with customers. It is not evidence that customers will stay.
voxq --db "$VOXQ_DB" results "$SURVEY_ID" voxq --db "$VOXQ_DB" results "$SURVEY_ID" --raw voxq --db "$VOXQ_DB" export "$SURVEY_ID" --output responses.json
curl --fail-with-body -H "Authorization: Bearer $VOXQ_INTERNAL_TOKEN" \ "$VOXQ_API/surveys/$SURVEY_ID/results/raw" curl --fail-with-body -H "Authorization: Bearer $VOXQ_INTERNAL_TOKEN" \ "$VOXQ_API/surveys/$SURVEY_ID/analysis"
Tool: get_results
Arguments: {"survey_id": "YOUR_RETURNED_SURVEY_ID"}
Tool: get_analysis
Arguments: {"survey_id": "YOUR_RETURNED_SURVEY_ID"}
(display (get-results "YOUR_RETURNED_SURVEY_ID")) (display (get-analysis "YOUR_RETURNED_SURVEY_ID"))
5. Open the survey to people
Open the same authored bank for human responses. Publishing permits new interview sessions and answers; closing blocks both. Surveys with attachments cannot currently be published for human interviews.
# Publishing has no native command on this surface. # Use the REST tab, or the studio on its own workspace. # The REST server must use the same database as your CLI/MCP study.
curl --fail-with-body -H "Authorization: Bearer $VOXQ_INTERNAL_TOKEN" \
-X POST "$VOXQ_API/surveys/$SURVEY_ID/publish" \
-H 'Content-Type: application/json' -d '{"accepting_interviews":true}'
# Publishing has no native command on this surface. # Use the REST tab, or the studio on its own workspace. # The REST server must use the same database as your CLI/MCP study.
# Publishing has no native command on this surface. # Use the REST tab, or the studio on its own workspace. # The REST server must use the same database as your CLI/MCP study.
For a study created in the studio, use Deploy → Open to respondents, then copy the interview link. Your own app, email list, or recruitment process supplies the participants.
6. Ask inside your product
The embed belongs on the product page where customers encounter the proposed change. The script host must be the Bun studio host serving the account-owned survey. It cannot be the private Rust listener or this marketing site.
# For a studio-owned study, copy the generated snippet from Deploy. # The CLI does not publish a browser widget.
# Read the private API's sanitized survey configuration: curl --fail-with-body -H "Authorization: Bearer $VOXQ_INTERNAL_TOKEN" \ "$VOXQ_API/surveys/$SURVEY_ID/embed-config" # For the standard widget, use the studio-owned survey and host below. # Standalone REST studies need a server integration for browser access.
# There is no embed MCP tool. # Use Deploy in the studio for the generated snippet.
;; There is no embed function in Scheme. ;; Use the studio's Deploy view for a studio-owned study.
<div data-voxq-survey="YOUR_STUDIO_SURVEY_ID"
data-voxq-height="620"></div>
<script src="https://YOUR_VOXQ_HOST/embed.js" async></script>
Replace both placeholders using the studio’s generated snippet. The loader creates an iframe and validates resize messages. It does not send answers or interview session capabilities to the parent page. Read attributes, dynamic mounting, and browser troubleshooting, or configure your own domain.
7. Compare the answers
Priya reads the customer explanations beside simulated answers and identifies concerns the model missed. For choice and scale questions, the report aggregates each group with the same logic and displays separate sample sizes. Open answers remain text to review; there is no automatic semantic accuracy score.
# Human comparison has no native command on this surface. # Use the REST tab, or the studio on its own workspace. # The REST server must use the same database as your CLI/MCP study.
curl --fail-with-body -H "Authorization: Bearer $VOXQ_INTERNAL_TOKEN" \ "$VOXQ_API/surveys/$SURVEY_ID/interviews/analysis"
# Human comparison has no native command on this surface. # Use the REST tab, or the studio on its own workspace. # The REST server must use the same database as your CLI/MCP study.
# Human comparison has no native command on this surface. # Use the REST tab, or the studio on its own workspace. # The REST server must use the same database as your CLI/MCP study.
The REST response contains
predicted and
human summaries. It includes saved
answers from incomplete interviews and excludes failed
simulations. Combined simulation runs and self-selected customers
are different samples; agreement on this study does not validate
another study.
Population targets and reusable audiences
Priya can save the personas and target mix in Audience & models → Reusable audiences, then explicitly apply that audience to an unsaved study. The reducer replaces only personas and population settings. Questions, product context, and model choices survive. Human screeners are study-specific and are stripped from reusable audiences; copying a whole study remaps its screener to the fresh question ID.
The optional population field is
included in the survey create payload. Give each persona exactly
one target, use percentages summing to 100, and supply a reference
or explicit assumption. No automatic normalization or learned
calibration occurs.
{
"population": {
"reference": "Illustrative assumption: 70% individual buyers, 30% team leads.",
"targets": [
{ "persona_label": "individual", "share": 70 },
{ "persona_label": "team_lead", "share": 30 }
],
"human_screener": {
"question_id": "AUTHORED_REQUIRED_SINGLE_CHOICE_ID",
"choices": { "Just me": "individual", "My team": "team_lead" }
}
}
}
This is a payload fragment, not a complete study. Persona labels,
screener ID, and choice keys must match the authored bank. Every
screener option needs a mapping. The saved-audience API is the
authenticated Bun surface
GET / POST /api/audiences, scoped to
the current workspace. Saving requires write permission and does
not invoke inference.
GET /api/surveys/{id}/interviews/analysis
adds population alongside the original
comparison. Its human and predicted per-question summaries include
raw and weighted values, segment coverage, and exclusions. A
missing positive-target segment suppresses the weighted question
result. Never renormalize the remaining segments or substitute
zero. Human effective n is diagnostic, not statistical confidence.
Open and dynamic answers are not weighted scalar measures.
Connect the studio to Not Organic
The studio uses a server-side PKCE connection with an HttpOnly
browser-binding cookie. Its callback is
/auth/notorganic. The app origin must
be registered and mapped to the voxq product before live sign-in
can succeed.
VOXQ_AUTH_MODE=live VOXQ_PUBLIC_ORIGIN=https://APP_HOST_TO_BE_CHOSEN VOXQ_MAX_COST_MICROUSD=OPERATOR_CHOSEN_INTEGER
These are configuration placeholders. The ceiling applies to each inference request. Credentials and rotating refresh tokens remain in server memory. A server restart requires sign-in again; multiple instances need shared encrypted credential storage and refresh coordination.
The studio reads the wallet and opens Not Organic for wallet management. Per-answer settlement remains unavailable until the gateway supplies a verifiable charge. The persona alias stays excluded from live requests until its upstream is enabled. Read the Not Organic integration contract.
Live voice through the owner’s gateway connection
Voxq uses an authenticated
GET WebSocket upgrade to Not Organic’s
/v1/live/sessions. This is the gateway’s Live path, not a Voxq-to-OpenAI key
exchange or the older
/v1/realtime route. No OpenAI key
belongs in Voxq or its browser bundle. The Bun server uses the
study owner’s server-held credentials for authorization,
reservations, and owner-wallet billing.
# Operator configuration, not browser environment variables: VOXQ_AUTH_MODE=live VOXQ_LIVE_VOICE=true VOXQ_VOICE_MAX_COST_MICROUSD=POSITIVE_OPERATOR_CHOSEN_INTEGER VOXQ_VOICE_DAILY_CONNECTIONS=20
The voice ceiling is required and separate from the ordinary inference ceiling. The daily connection limit defaults to 20, is capped at 100, and uses a rolling 24-hour window. At most 3 connections are allowed per interview, including reconnects. The ceiling multiplied by the daily connection limit gives a budget bound, not an exact bill. Configuring a cap does not make respondent use free.
The owner must reconnect OAuth to grant
realtime:connect when it is not
already authorized, and separately accept
Not Organic realtime logging consent. This
provider consent is not the respondent’s microphone or
local-recording consent. Deployment configuration alone does not
enable a study: its Deploy toggle requires both
can_write and
can_spend. Disabling a study’s voice
setting stops active voice connections.
The authenticated Bun route
GET /api/surveys/{id}/voice-settings
returns
{enabled, configured, max_cost_microusd, daily_limit, calls,
confirmed_seconds}. POST to the same route with
{enabled: boolean}, then GET
authoritative state again. These are product-backend routes, not
private Rust API endpoints. A false
configured disables the UI toggle and
shows setup guidance.
Confirmed seconds may exclude unfinalized connections; actual settlement belongs to the Not Organic wallet, not a seconds-to-price calculation in Voxq. Live account, funded usage, and settlement proof remain pending. Credentials and active voice coordination currently require a single Bun instance; do not deploy horizontal replicas as though distributed ownership and refresh locking were implemented.
Voice, recording, and embed permissions
Keep typed controls available. Speech populates an editable answer but never submits it automatically. Microphone use is explicit opt-in. Recording requires an additional explicit local consent: the audio and timeline downloads stay on the respondent’s device, with no server recording storage or persisted voice transcript. Explicitly submitted survey answers remain normal persisted research data. Live voice still goes through Not Organic and OpenAI for remote processing.
Use HTTPS for the host and survey. The generated iframe must
retain microphone and autoplay delegation; sandboxed embeds also
need allow-downloads for local
exports. The host’s Permissions-Policy and any ancestor frame
policies must permit microphone use for the actual survey origin.
Browser consent and playback restrictions still apply. Downloads
belong in the sandbox permissions, not a fictitious
downloads Permissions-Policy feature.
# Host HTTP response header, replace the example origin: Permissions-Policy: microphone=(self "https://APP_HOST_TO_BE_CHOSEN"), autoplay=(self "https://APP_HOST_TO_BE_CHOSEN") # Relevant attributes on the generated iframe: allow="microphone; autoplay" sandbox="allow-scripts allow-same-origin allow-forms allow-downloads"
These are permission examples, not a replacement for the loader or a complete deployment policy. Copy the app-generated embed and review the host’s existing policies instead of weakening them globally. Test microphone, audio playback, and explicit downloads on the published page; offer the full interview link when a site builder cannot delegate permissions.
CLI reference
Use voxq --help and
voxq create --help for the installed
binary. Keep the global --db flag
before the command when sharing a study across surfaces.
voxq init --dir starter --name "Pricing study" voxq --db "$VOXQ_DB" list voxq --db "$VOXQ_DB" show "$SURVEY_ID" voxq --db "$VOXQ_DB" run "$SURVEY_ID" voxq --db "$VOXQ_DB" results "$SURVEY_ID" --raw voxq --db "$VOXQ_DB" export "$SURVEY_ID" --output responses.json voxq --db "$VOXQ_DB" script pricing-study.scm
delete SURVEY_ID removes the survey
and its responses. There are no native estimate, publish,
interview, or cost commands. Use REST for those operations.
REST reference
The private Rust API serves /surveys.
The studio’s Bun backend serves browser requests at
/api/surveys, enforces session
ownership, and rewrites model aliases. These are different trust
boundaries; keep the internal bearer token on servers and in local
terminals.
| Operation | Route |
|---|---|
| Create / list | POST / GET /surveys |
| Read / delete | GET / DELETE /surveys/{id} |
| Estimate / run | POST /surveys/{id}/estimate · POST /surveys/{id}/run |
| Saved responses | GET /surveys/{id}/results · GET /surveys/{id}/results/raw |
| Simulation analysis / cost | GET /surveys/{id}/analysis · GET /surveys/{id}/cost |
| Open or close | POST /surveys/{id}/publish |
| Public display configuration | GET /surveys/{id}/embed-config |
| Start an interview | POST /surveys/{id}/interviews |
| Session status / events | GET /surveys/{id}/interviews/{session_id} · GET /surveys/{id}/interviews/{session_id}/stream |
| Submit an answer | POST /surveys/{id}/interviews/{session_id}/answer |
| Compare groups | GET /surveys/{id}/interviews/analysis |
| Research export | GET /surveys/{id}/export (hosted: /api/surveys/{id}/export) |
Research exports return a versioned JSON envelope with study details, answer/skip rows, comparisons, population reports, costs, and notes. The hosted route requires workspace membership and configured SSO, but no spending permission. The web UI prepares this once and encodes the same canonical records as CSV or JSON. Access tokens, external respondent IDs, and provider raw payloads are omitted. Respondent labels group answers within one export only. Collection may continue during preparation; this is not a transactional reporting cutoff.
Interview session IDs grant access to that session. Keep them out of analytics, parent-window messages, and public logs. Prefer the supplied interview client unless you need to implement session recovery, answer validation, and streaming yourself.
MCP reference
Configure a client to start the local binary over stdio. Replace both absolute paths with your checkout and study location; client configuration formats may vary.
{
"mcpServers": {
"voxq": {
"command": "/absolute/path/to/voxq/target/debug/voxq",
"args": [
"--db",
"/absolute/path/to/pricing-study.sqlite",
"mcp"
]
}
}
}
Available tools: create_survey,
list_surveys,
get_survey,
run_survey,
get_results,
get_analysis,
delete_survey, and
run_script. ID-based tools accept
survey_id. Script execution accepts
script.
The current advertised MCP schema calls the question field
question_type, while deserialization
reads type. This guide uses open
questions to avoid that mismatch. Use REST for choice and scale
studies and verify the saved question types. MCP creation also
does not retain product context.
voxq --db "$VOXQ_DB" http-mcp --bind 127.0.0.1:8000
Scheme reference
Steel Scheme builds surveys declaratively and reads saved results. Its question helpers create open questions; use CLI or REST JSON for choice and scale. The persona helper takes five arguments after its name and does not set the separate context field.
(survey name description) (question survey-id text) (persona survey-id label min-age max-age description) (bot survey-id name provider model) (bot-at survey-id name provider model base-url key-env) (vision-bot-at survey-id name provider model base-url key-env) (question-with-image survey-id text image-src image-alt) (question-with-doc survey-id text local-text-path filename) (save-result survey-id bot-name persona-label question-id answer) (get-survey survey-id) (get-results survey-id) (get-analysis survey-id) (list-surveys) (delete-survey survey-id)
Provider choices are openai,
anthropic,
ollama, and
custom. Model availability comes from
your provider configuration. Image questions require
vision-capable bots; document resolution currently reads local
text files. Attachments apply to simulation, not published human
interviews.