Developers
MCP documentation
Add Mimi to Claude, Cursor, VS Code or any assistant that speaks the Model Context Protocol, and test creatives against real audiences from a conversation: find cohorts, run a study, read the results and ask follow-ups, without leaving your chat or editor.
Overview
| Server URL | https://getmimi.xyz/api/mcp |
| Transport | Streamable HTTP (JSON responses; stateless, no session to keep) |
| Authentication | Authorization: Bearer mimi_… with a key from Settings › API and MCP |
| Tools | 35, the same operations as the REST API |
| Prompts | 5 ready-made jobs |
| Limits | Shared with the API: 120 requests a minute, plus the account's simulation budget |
The server also sends the assistant a short guide when it connects: find the audience, create the study, wait for the results, read them by cohort and version, and present them as a simulation rather than a measurement. Most assistants need no other instruction.
Connect a client
- Make a key in Settings › API and MCP. Name it after the assistant, so you can revoke just that one later.
- Add the server to your client with the snippet below.
- Ask: "Use Mimi to check which cohorts would react worst to this headline."
claude mcp add --transport http mimi https://getmimi.xyz/api/mcp \
--header "Authorization: Bearer $MIMI_API_KEY"// Settings › Developer › Edit config (claude_desktop_config.json)
{
"mcpServers": {
"mimi": {
"command": "npx",
"args": ["-y", "mcp-remote", "https://getmimi.xyz/api/mcp", "--header", "Authorization:${MIMI_AUTH}"],
"env": { "MIMI_AUTH": "Bearer mimi_your_key_here" }
}
}
}// ~/.cursor/mcp.json
{
"mcpServers": {
"mimi": {
"url": "https://getmimi.xyz/api/mcp",
"headers": { "Authorization": "Bearer mimi_your_key_here" }
}
}
}// .vscode/mcp.json: VS Code asks for the key once and stores it securely
{
"inputs": [
{ "type": "promptString", "id": "mimi-key", "description": "mimi API key", "password": true }
],
"servers": {
"mimi": {
"type": "http",
"url": "https://getmimi.xyz/api/mcp",
"headers": { "Authorization": "Bearer ${input:mimi-key}" }
}
}
}// ~/.codeium/windsurf/mcp_config.json
{
"mcpServers": {
"mimi": {
"serverUrl": "https://getmimi.xyz/api/mcp",
"headers": { "Authorization": "Bearer mimi_your_key_here" }
}
}
}# ~/.codex/config.toml: reads the key from the MIMI_API_KEY environment variable
[mcp_servers.mimi]
url = "https://getmimi.xyz/api/mcp"
bearer_token_env_var = "MIMI_API_KEY"
# Creating a study with media can take three minutes; Codex gives up on a tool after 60 seconds otherwise.
tool_timeout_sec = 300// ~/.gemini/settings.json
{
"mcpServers": {
"mimi": {
"httpUrl": "https://getmimi.xyz/api/mcp",
"headers": { "Authorization": "Bearer mimi_your_key_here" }
}
}
}# Streamable HTTP, JSON responses, no session needed.
curl https://getmimi.xyz/api/mcp \
-H "Authorization: Bearer $MIMI_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'ChatGPT and claude.ai connectors
Connectors added in the ChatGPT and claude.ai web apps sign in with OAuth, which Mimi does not offer yet. Until it does, use Claude Desktop, Claude Code or another client above, or in ChatGPT build a custom GPT whose action imports /api/v1/openapi.json with your key as the bearer token.
Attach media
Call upload_media, then POST the file's bytes to its uploadUrl with the returned Content-Type. Pass the upload response's storageId, the same filename, MIME type and exact size in create_study.media. An assistant needs a file-upload capability for that binary POST; the MCP tool returns the URL and does not upload local files itself.
Media analysis can take up to three minutes; set the client tool timeout to five minutes. Keep the same uploads and idempotencyKey when retrying. If analysis fails, the tool reports an error before creating a run; with run: false, it can save a draft with a media warning. See the media API guide for limits and recovery.
Tools
Read-only tools change nothing and can be called freely. create_study, run_study, create_website_test, create_feed_test, ask_study and ask_followup spend credits, so most assistants ask before calling them. They need a key with the run scope, and they count against the key's spend cap. A key made on the Connect page can read and run but not share, and has a spend cap of 5,000 credits until you raise it. A key reaches only its owner's own studies. quote_study prices a run first: create the study with run: false, quote it, then call run_study. Give every paid call an idempotencyKey (a new UUID each time): a retry with the same key and request returns the first study, test, run or answer and is not charged again, and the same key with a different request is refused with idempotency_mismatch. cancel_run stops a run in progress and returns its credit hold, less what the answers it already made used.
ask_study or ask_followup
ask_study answers a question about a finished run from what it recorded: every cohort's answers, the shares, objections, quotes and the written analysis. It asks the cohorts nothing. Each answer costs a few credits, and it takes 60 questions an hour, shared with the app's Chat. Use it first when the answer may already be in the results ("which cohort liked Version B least, and why?"). ask_followup puts a new question to the cohorts themselves, for what the run never asked ("would a $10 lower price change your mind?"). The server's guide tells assistants the same.
ask_study waits for the whole answer and returns it as markdown with at most one chart left in it as a fenced ```chart block of JSON (bar, stacked, compare or heatmap; the API reference has each shape), with the questions, versions and cohorts it drew on, up to three questions to ask next and the one untested change it recommends. Its numbers are model estimates read from the simulated run, so present them as the rest of the result is presented.
Website tests were called customer tests. The older tool names (create_customer_test, list_customer_tests, get_customer_test, get_customer_test_results, get_customer_test_fixes, cancel_customer_test, share_customer_test, unshare_customer_test) still work and do the same thing; use the website-test names below.
whoamiread-only
Return the mimi account the API key belongs to, its credit balance (100 credits = 1 USD) and its limits: what is left of each budget (requests a minute, follow-ups and chat questions an hour, files read by the vision step an hour) and when each resets, plus the key's own limits: its scopes (read, run, share), its spend cap in credits (null for none) and what it has spent and holds. Study runs, web research and website tests have no daily cap and no cap on how many run at once; credits and the key's spend cap are their only limits. Call it first to check a key works.
No parameters.
describe_audienceread-only
Find the real audience cohorts that fit a plain-language description of who should weigh in ("moms in the US into meal prep", "men 18-24 into sneakers"). Returns cohort keys ranked by fit, how mimi read the description (gender, ages, place, topics) and a one-line reason per match. Use the keys in create_study.
| Field | Type | Description |
|---|---|---|
descriptionrequired | string | Who should weigh in, in plain words: "women 25-40 in the US into clean skincare".3–500 chars |
limit | integer | How many cohorts to return, best match first.1–24 · default 10 |
search_audiencesread-only
Search mimi's catalogue of real audience cohorts (people grouped by what they watch and comment on) by keyword, family (interest, creator, US panel), gender and age. Returns keys, labels, sizes and whether each cohort can be used in a new study. Prefer describe_audience for a natural-language brief.
| Field | Type | Description |
|---|---|---|
query | string | Words to find in a cohort's label, interest or summary, e.g. "skincare" or "home espresso".0–64 chars |
family | "interest" | "creator" | "panel" | interest: people grouped by what they engage with (default). creator: one creator's or brand's audience. panel: US general-population cells by age and gender. |
gender | "women" | "men" | Only cohorts whose label states this gender. |
ageMin | integer | Only cohorts whose stated age range reaches this age.13–100 |
ageMax | integer | Only cohorts whose stated age range starts at or below this age.13–100 |
page | integer | 1–200 · default 1 |
perPage | integer | 1–50 · default 20 |
get_audienceread-only
Read one audience cohort by key: its label, summary, size, what its members talk about and, where available, verbatim comments that show how they speak.
| Field | Type | Description |
|---|---|---|
keyrequired | string | A cohort key from search_audiences or describe_audience, e.g. aud-v3-tiktok-412.3–64 chars |
list_studiesread-only
List the account's studies, newest first, with each one's goal, status and one-line result when it has finished.
| Field | Type | Description |
|---|---|---|
view | "active" | "archived" | default "active" |
limit | integer | 1–100 · default 25 |
cursor | string | The nextCursor of the page before, for the next page; leave out for the newest studies.1–2000 chars |
upload_mediamakes changes
Get a short-lived URL to upload one image, video, audio or PDF file (40 MB at most) that the audience should see with a creative. POST the file's bytes to uploadUrl with the file's Content-Type; it answers {"storageId": ...}. Then pass {storageId, filename, mimeType, sizeBytes} in create_study's media. Unused uploads are deleted after a day.
| Field | Type | Description |
|---|---|---|
filenamerequired | string | The file's name, e.g. "launch.mp4".1–200 chars |
mimeTyperequired | "image/jpeg" | "image/png" | "image/webp" | "image/gif" | "video/mp4" | "video/quicktime" | "video/webm" | "audio/mpeg" | "audio/wav" | "application/pdf" | The file's type. |
sizeBytesrequired | integer | The file's size in bytes, 40 MB at most.1–41943040 |
create_studymakes changes
Create a study and (by default) start its run: real audience cohorts react to a creative, compare 2-6 versions, or answer survey questions. Attach the creative's images, video, audio or PDFs with media (upload each file first with upload_media). Give the audience as cohort keys or as a plain description mimi matches for you. A run usually takes about a minute, several minutes with web research; follow it with get_study or get_results using waitSeconds. Costs credits (100 credits = 1 USD): the most it can cost is held when it starts and you pay only what it used. quote_study prices a draft before it runs. Send an idempotencyKey to retry safely: the same key and request return the first study (replayed: true), never start a second run and are not charged again, and the same key with a different request is refused with idempotency_mismatch. Needs a key with the run scope; a key with a spend cap starts no run that would take it past the cap.
| Field | Type | Description |
|---|---|---|
namerequired | string | A name you will recognise in the studies list.3–120 chars |
goal | "read" | "ab" | "multi" | "backlash" | "concept" | "survey" | "spread" | read: one creative. ab: two versions. multi: up to six. backlash: who objects and how strongly. concept: pressure-test an idea. survey: questions only, no creative. spread: how it travels between cohorts. Inferred from the versions when left out. |
creative | string | What the audience reacts to: an ad script, a post, a headline, a product idea. Use `versions` instead to compare several.10–4000 chars |
versions | string[] | Two to six alternatives to compare, A first.2–6 items |
context | string | Where and how people meet the creative: "a 15-second TikTok ad", "a subject line in a promotions tab".0–2000 chars |
description | string | A note for yourself about what this study is for.0–1000 chars |
questionsrequired | object[] | 1-10 questions. At least one must have options (choice, score or ranking). A ranking counts as two.1–10 items |
questions[].textrequired | string | The question as a respondent reads it.1–500 chars |
questions[].type | "choice" | "open" | "score" | "ranking" | choice: pick one option. open: a free-text answer. score: a 0-100 rating. ranking: order the options best to worst.default "choice" |
questions[].options | string[] | 2-10 options for a choice question, 3-10 for a ranking. Leave out for open and score.0–10 items |
audiencerequired | object | |
audience.cohorts | string[] | Exact cohort keys to ask (from search_audiences or describe_audience).1–12 items |
audience.describe | string | Or describe who should answer; mimi picks the best-matching cohorts.3–500 chars |
audience.count | integer | With `describe`: how many cohorts to pick.1–12 · default 5 |
fundingGoalUsd | integer | Concept studies only: a crowdfunding goal in US dollars. The results then include the chance a campaign reaches it.1–100000000 |
depth | "quick" | "standard" | "extended" | "max" | Responses per cohort: quick 6, standard 12, extended 24, max 40. Each cohort answers in model calls that state a whole distribution, so depth is how many responses those are read as, not how many simulation calls are made (quick asks 6 open answers per cohort instead of 12). extended also asks every cohort one follow-up why after the run, and max three follow-ups per cohort and a deeper analysis; their price includes that work, charged only when it was done.default "standard" |
research | "off" | "current" | current: read today's web context about the creative's subject before the audience answers.default "off" |
run | boolean | Start the run now. false saves a draft you can run later with run_study.default true |
seed | integer | A whole number from 0 to 4294967295 that fixes mimi's own random choices for the run: draw order, the quotes each call is shown, propagation. The models still answer afresh, so two runs with one seed are close, not identical. A fresh seed is drawn when left out; get_results returns the one used as runMeta.seed.0–4294967295 |
idempotencyKey | uuid | A UUID. Sending the same request with the same key again returns the first study instead of making a second, and never starts a second run. The same key with a different request is refused with idempotency_mismatch (409). |
media | object[] | Images, video or audio the audience sees with the creative, uploaded first with upload_media. Up to 4 files per version. mimi reads each file (frames and sound for a video) before the run, as the app does for an upload. Versions may share the same text when their media differ: that tests the image or video.0–12 items |
media[].storageId | string | The storageId the upload URL answered with.1–64 chars |
media[].filename | string | The file's name, e.g. "launch.mp4".1–200 chars |
media[].mimeType | "image/jpeg" | "image/png" | "image/webp" | "image/gif" | "video/mp4" | "video/quicktime" | "video/webm" | "audio/mpeg" | "audio/wav" | "application/pdf" | The file's type. |
media[].sizeBytes | integer | The file's size in bytes, 40 MB at most.1–41943040 |
media[].version | "a" | "b" | "c" | "d" | "e" | "f" | Which version the file belongs to: a is the creative (or the first version), b the second, and so on.default "a" |
links | object[] | Social posts the audience sees as the creative, by link instead of upload. They count toward the 4 files per version.0–12 items |
links[].url | string | A public TikTok, YouTube, Instagram or X post, e.g. "https://www.tiktok.com/@brand/video/7301234567890". mimi reads the post (its video, caption and sound) as the app does for a pasted link.8–2000 chars |
links[].version | "a" | "b" | "c" | "d" | "e" | "f" | Which version the post belongs to, as for media.default "a" |
get_studyread-only
Read a study: what it asks, which cohorts answer, and its run's status (draft, running, complete, failed), phase and progress. Pass waitSeconds (up to 55) to hold until the run finishes, then call get_results.
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id, as create_study or list_studies returned it.1–64 chars |
waitSeconds | integer | Hold the request up to this many seconds while the run is in progress, and answer as soon as it finishes.0–55 · default 0 |
run_studymakes changes
Start a run for a draft study, or run a finished one again (for a fresh sample, a different depth, or with or without web research: pass depth or research as quote_study takes them). Refused while a run is in progress. Costs credits like create_study; refused with insufficient_credits when the balance cannot cover it, and with spend_cap_reached when it would take the key past its spend cap.
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id, as create_study or list_studies returned it.1–64 chars |
depth | "quick" | "standard" | "extended" | "max" | Responses per cohort for this run; the study's own setting when left out. |
research | "off" | "current" | current: read today's web context first; off: run without it. The study's own setting when left out. quote_study takes the same option to price it. |
seed | integer | A whole number from 0 to 4294967295 that fixes mimi's own random choices for the run: draw order, the quotes each call is shown, propagation. The models still answer afresh, so two runs with one seed are close, not identical. A fresh seed is drawn when left out; get_results returns the one used as runMeta.seed.0–4294967295 |
idempotencyKey | uuid | A UUID, new for each run you mean to start. Sending the same request with the same key again (say after a timeout) returns the run the first one started, with replayed: true, and never starts or charges a second. The same key with a different request is refused with idempotency_mismatch (409). |
quote_studyread-only
Price a run of a study before starting it: its quoted price, which is the most it is charged and what is held when it starts (estimate and hold are the same number), in credits (100 credits = 1 USD), and the account's balance. Pass depth or research to price a different run than the study's own settings: extended adds one follow-up why per cohort after the run and max three per cohort plus a deeper analysis, so each depth up costs more. Create the study with run: false to price it before it runs, then start it with run_study.
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id, as create_study or list_studies returned it.1–64 chars |
depth | "quick" | "standard" | "extended" | "max" | Responses per cohort to price; the study's own setting when left out. |
research | "off" | "current" | Price reading today's web context first; the study's own setting when left out. |
archive_studymakes changes
Archive a study (archived: true) so it leaves the active list, or restore it (archived: false). Nothing is deleted: its runs, results and share link are kept, and list_studies with view "archived" lists it.
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id, as create_study or list_studies returned it.1–64 chars |
archived | boolean | true moves the study out of the active list; false brings it back.default true |
cancel_runmakes changes
Stop the study's run in progress (or the run named by runId). Its job on the simulation service is cancelled before its next paid call, the responses so far are discarded, and its credit hold is returned less what the answers already made used (nothing, if none came back). A run still going after three hours is cancelled and refunded in full on its own. A run that already finished is left as it is (stopped: false).
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id.1–∞ chars |
runId | string | The run to stop. Leave it out to stop the study's run that is in progress.1–∞ chars |
get_resultsread-only
Read a finished study's results: for every question the answer shares with their ranges, overall, per version and per cohort; averages for scores; orders for rankings; open answers with their themes; objections ranked by how upset each cohort is; representative quotes; and the written recommendation, findings, risks and next steps. Also the reads the report shows: the backlash level and peak upset, the virality level per version, how a comparison's versions stand against each other, a concept's funding forecast, and the run's cohorts, versions, settings, coverage, release, timing and cost. Lists are capped (50 objections, 24 quotes, 20 open answers per question); in a comparison the quotes are shared across its versions; `totals` says how many there were. Pass waitSeconds to wait for a running study. Reads the study's latest run unless you pass runId (as get_study or the app's ?run= link names it), so an earlier finished run stays readable while a re-run is going or after one failed. Answers `ready: false` with the run's progress while it is still going. `runMeta.creditChargedCents` is what the run was charged, its analysis extras included, and `runMeta.credits` gives the parts (run, extras, total, quote). `run.degraded` is true when fewer than half of the run's planned answers came back (it is charged only for the answers that came back; `run.coverage` is the share that did). The written parts (recommendation, themes, virality) land a minute or two after the run; `analysis` says which are ready, and a wait holds for them too.
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id, as create_study or list_studies returned it.1–64 chars |
waitSeconds | integer | Wait up to this many seconds for a running study to finish before answering.0–55 · default 0 |
calls | boolean | Also return, for each question, every model call's own answer distribution per cohort and version (questions[].calls), to compute your own intervals. Large; off by default.default false |
runId | string | The run to read, as get_study or the app's ?run= link names it. The study's latest run when left out.1–64 chars |
ask_studymakes changes
Ask a question about a finished study's results and get a written answer, the same one the report's Chat panel gives. It answers only from what that run recorded: every cohort's answers, the shares, objections, quotes and the written analysis. It runs nothing new and asks the cohorts nothing; to put a new question to the cohorts themselves, use ask_followup. Asks about the latest finished run unless you pass runId. Waits for the whole answer (usually well under a minute) and returns it as markdown, with at most one chart left in it as a fenced ```chart block of JSON (bar, stacked, compare or heatmap; the shapes are in the API docs), plus sources (the question numbers, version keys and cohort count it drew on), up to three followups (next questions it left open) and action (one untested change it recommends, or null). Its numbers are model estimates read from the simulated run, not measurements. The question and answer also appear in the study's Chat in the app. Each answer costs credits (shown in the Chat panel), charged once it is written; a failed answer is free. 60 questions an hour per account, shared with the app's Chat; questions are 500 characters at most. Needs a key with the run scope, since it spends credits; each answer counts on the key's spend cap, and is refused with spend_cap_reached when the key has reached it.
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id, as create_study or list_studies returned it.1–64 chars |
questionrequired | string | A question about the run's recorded results: "Which cohort liked Version B least, and why?"3–500 chars |
runId | string | The run to ask about, as get_study or the app's ?run= link names it. The latest finished run when left out.1–64 chars |
idempotencyKey | uuid | A UUID, new for each call. Sending the same request with the same key again (say after a timeout) returns the first answer with replayed: true and is not charged again. The same key with a different request is refused with idempotency_mismatch (409). |
ask_followupmakes changes
Ask a finished study's cohorts a follow-up question ("what would make this more believable?"). Each cohort answers in its own voice, grounded in what its real members say. Takes up to a minute. 40 follow-ups an hour per account. Each cohort's answer costs credits, charged once it is written. Needs a key with the run scope; the answers count on the key's spend cap, and are refused with spend_cap_reached when the key has reached it.
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id, as create_study or list_studies returned it.1–64 chars |
questionrequired | string | A follow-up the study's cohorts answer in their own voice: "What would make you trust this brand more?"3–500 chars |
cohorts | string[] | Only these cohorts of the study answer; all of them when left out.1–12 items |
idempotencyKey | uuid | A UUID, new for each call. Sending the same request with the same key again (say after a timeout) returns the first answer with replayed: true and is not charged again. The same key with a different request is refused with idempotency_mismatch (409). |
list_followupsread-only
List the follow-up questions asked of a study's latest run, with each cohort's answer.
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id, as create_study or list_studies returned it.1–64 chars |
report_ad_performancemakes changes
After you run the ads a study compared, send back how each one did, so Mimi's pick can be checked against what happened and Mimi improves as it sees more ads. Name each ad by the study's version key ("a", "b" and on, as get_study lists them; "a" for a single creative) and send its counts (impressions, clicks, installs), money (spend, revenue, currency) or rates (clickRate as clicks per impression, installRate as installs per click, from 0 to 1), engagementSeconds, and any other number by name in metrics. Send what you have: a report merges into the last one, so a number sent replaces the one stored and one left out is kept. Send cumulative totals, not the day's change. About the latest finished run unless you pass runId. Answers what get_ad_performance returns. Free; needs a key with the run scope.
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id, as create_study or list_studies returned it.1–64 chars |
runId | string | The run whose pick you tested, as get_study names it. The latest finished run when left out.1–64 chars |
source | string | Where the numbers come from, e.g. your ad network's name.1–80 chars |
adsrequired | object[] | One entry per ad you ran, each named by its version key.1–6 items |
ads[].versionrequired | string | Which of the study's ads this is: its version key, "a" to "f", as get_study lists them. "a" for a study of one creative. |
ads[].adId | string | Your own id for the ad, to match it in your system.1–120 chars |
ads[].impressions | integer | Times the ad was shown.0–∞ |
ads[].clicks | integer | Taps or clicks on the ad.0–∞ |
ads[].installs | integer | App installs the ad drove.0–∞ |
ads[].spend | number | What the ad cost, in `currency`.0–∞ |
ads[].revenue | number | Revenue the ad's installs brought in, in `currency`, for ROAS.0–∞ |
ads[].currency | string | ISO 4217 code for spend and revenue, e.g. USD. |
ads[].clickRate | number | Clicks per impression, 0 to 1 (0.034 is 3.4%), if you send the rate instead of the counts.0–1 |
ads[].installRate | number | Installs per click, 0 to 1, if you send the rate instead of the counts.0–1 |
ads[].engagementSeconds | number | Average engagement time per impression, in seconds.0–∞ |
ads[].metrics | object | Any other number you track for the ad, by name, e.g. { "d7Retention": 0.21 }. Up to 20. |
ads[].from | string | Start of the period the numbers cover. |
ads[].to | string | End of the period the numbers cover. |
get_ad_performanceread-only
Read the real numbers reported for a study's ads (report_ad_performance), one entry per version: the counts and money as sent, and clickRate (clicks per impression), installRate (installs per click), installsPerThousand (installs per 1,000 impressions) and roas (revenue over spend), each worked out from the counts when they were sent, else as reported. Beside them, the ad Mimi picked: only a version a validated side-by-side verdict named (versionPreference.state "validated_winner"). Otherwise mimiPick is a no-call (version null, basis "no_call") with its reason, for images and video, too-close and unvalidated runs alike. For each measure, which ad actually did best and whether it was Mimi's pick (null for a no-call, which is never scored). About the latest run with numbers reported unless you pass runId.
| Field | Type | Description |
|---|---|---|
idrequired | string | The study's id, as create_study or list_studies returned it.1–64 chars |
runId | string | The run to read. The latest run with reported numbers when left out.1–64 chars |
create_feed_testmakes changes
Start a Feed Emulator test: simulated people from one real audience cohort each scroll an Instagram-style feed (default) or an enabled X feed of 15 to 20 real recent posts picked for their interests, with your ad placed at a random slot from 4 to 7. Each person sees only what a scroller sees (a post's top until they stop; a video plays muted only while they keep watching) and decides on every post whether to scroll past, pause or stop, and whether to like, comment, share, save, open the profile or tap the button; afterwards they are asked what they remember. Upload the ad first with upload_media (a photo 1:1 or 4:5, or a video 9:16 or 4:5 up to 60 seconds; a video also needs its first frame uploaded as `cover`), then give the brand, caption, button and the audience (a cohort key, or `audience.describe`). An optional versionB from the same brand is compared with each person seeing one version. Returns the test's id; it runs for several minutes. Then call get_feed_test with waitSeconds until status is "finished" and get_feed_test_report for calibrated rates against the real posts around the ad. Costs credits (100 credits = 1 USD): the most it can cost is held when it starts, and it keeps only what its people used. Send an idempotencyKey to retry safely. X uses sampled public warehouse posts from scenario authors, not verified individual follow relationships; unavailable or insufficient warehouse evidence never triggers paid collection.
| Field | Type | Description |
|---|---|---|
audiencerequired | object | Whose members the simulated people are drawn from. English-speaking audiences only. |
audience.cohort | string | The exact cohort whose members scroll (a key from search_audiences or describe_audience).3–64 chars |
audience.describe | string | Or describe who should scroll ("US moms into meal prep"); mimi picks the best-matching cohort.3–500 chars |
platform | "instagram" | "tiktok" | "x" | The app whose feed people scroll: its layout, its real recent posts and its ad shapes. Instagram: a square or 4:5 picture, or a 9:16 or 4:5 video. TikTok: a 9:16 picture or video. X: a square, 16:9 or 4:5 picture, or a 16:9, square or 9:16 video.default "instagram" |
adrequired | object | The ad (Version A). |
ad.mediarequired | object | The ad's picture or video, uploaded first with upload_media. |
ad.media.storageIdrequired | string | The storageId upload_media's URL answered with.1–64 chars |
ad.media.mimeTyperequired | string | The file's type as uploaded: image/jpeg, image/png, image/webp, video/mp4, video/quicktime or video/webm.3–100 chars |
ad.media.widthrequired | integer | The picture's or video's width in pixels.100–10000 |
ad.media.heightrequired | integer | Its height in pixels. A photo is square or 4:5, a video 9:16 or 4:5.100–10000 |
ad.media.durationSec | number | A video's length in seconds (60 at most). Required for a video.0.5–60 |
ad.cover | object | For a video: its first frame as a JPEG, PNG or WebP picture (uploaded with upload_media), which is what a scroller sees before they stop. A photo is its own cover. |
ad.cover.storageId | string | The storageId upload_media's URL answered with.1–64 chars |
ad.cover.mimeType | string | The file's type as uploaded: image/jpeg, image/png, image/webp, video/mp4, video/quicktime or video/webm.3–100 chars |
ad.brandrequired | string | The brand's name as it shows on the post.1–80 chars |
ad.caption | string | The post's caption.0–2200 chars · default "" |
ad.ctarequired | "Shop now" | "Learn more" | "Sign up" | "Download" | "Book now" | "Get offer" | "Order now" | "Watch more" | "Contact us" | "Subscribe" | The ad's button. |
versionB | object | An optional Version B from the same brand. Each person sees one version; people are paired by interest and each pair shares a feed. |
versionB.media | object | The ad's picture or video, uploaded first with upload_media. |
versionB.media.storageId | string | The storageId upload_media's URL answered with.1–64 chars |
versionB.media.mimeType | string | The file's type as uploaded: image/jpeg, image/png, image/webp, video/mp4, video/quicktime or video/webm.3–100 chars |
versionB.media.width | integer | The picture's or video's width in pixels.100–10000 |
versionB.media.height | integer | Its height in pixels. A photo is square or 4:5, a video 9:16 or 4:5.100–10000 |
versionB.media.durationSec | number | A video's length in seconds (60 at most). Required for a video.0.5–60 |
versionB.cover | object | For a video: its first frame as a JPEG, PNG or WebP picture (uploaded with upload_media), which is what a scroller sees before they stop. A photo is its own cover. |
versionB.cover.storageId | string | The storageId upload_media's URL answered with.1–64 chars |
versionB.cover.mimeType | string | The file's type as uploaded: image/jpeg, image/png, image/webp, video/mp4, video/quicktime or video/webm.3–100 chars |
versionB.brand | string | The brand's name as it shows on the post.1–80 chars |
versionB.caption | string | The post's caption.0–2200 chars · default "" |
versionB.cta | "Shop now" | "Learn more" | "Sign up" | "Download" | "Book now" | "Get offer" | "Order now" | "Watch more" | "Contact us" | "Subscribe" | The ad's button. |
people | integer | How many simulated people scroll (2 to 50, 20 by default; 4 or more with a Version B).2–50 · default 20 |
idempotencyKey | uuid | A UUID. Sending the same request with the same key again returns the first test instead of starting a second. |
list_feed_testsread-only
List the account's Feed Emulator tests, newest first, with each one's audience, versions and status. view "archived" lists the archived ones instead.
| Field | Type | Description |
|---|---|---|
view | "active" | "archived" | default "active" |
limit | integer | 1–50 · default 25 |
get_feed_testread-only
Read a Feed Emulator test: its audience, its versions and where it stands (queued, collecting posts, scrolling, calibrating, finished, failed, cancelled) with how many people are done. Pass waitSeconds (up to 55) to hold until it finishes, then call get_feed_test_report.
| Field | Type | Description |
|---|---|---|
idrequired | string | The Feed Emulator test's id, as create_feed_test or list_feed_tests returned it.1–64 chars |
waitSeconds | integer | Hold the request up to this many seconds while the test is in progress, and answer as soon as it finishes.0–55 · default 0 |
get_feed_test_sessionsread-only
Read each simulated person's scroll: their version, where the ad sat, and for every post what they did (scrolled past, paused, stopped; how far into a video; liked, commented with what, shared to whom, saved, opened the profile, tapped) and why, then what they remembered. Other people's posts appear as numbered blanks (post-1, post-2): they were used inside the run only. Pass `person` for one person.
| Field | Type | Description |
|---|---|---|
idrequired | string | The Feed Emulator test's id, as create_feed_test or list_feed_tests returned it.1–64 chars |
person | integer | One person's number: answer with that person's scroll alone, step by step.0–49 |
get_feed_test_reportread-only
Read a finished Feed Emulator test's report: how often people stopped for the ad, how far they watched it and how they engaged with it, each as a lift over the real posts in the same people's feeds (calibrated, never a raw simulated answer; an absolute rate only where the real posts' own counts anchor it), unaided and aided recall corrected for saying yes to anything, the themes of what people commented with their comments, and a check of how well the people tracked the real posts' real engagement. With a Version B it says which drew more engagement only once Feed Emulator has passed its check on real ad outcomes; until then it says there is no reliable winner call. Until the test finishes it answers ready: false; pass waitSeconds to wait.
| Field | Type | Description |
|---|---|---|
idrequired | string | The Feed Emulator test's id, as create_feed_test or list_feed_tests returned it.1–64 chars |
waitSeconds | integer | Hold the request up to this many seconds while the test is in progress, and answer as soon as it finishes.0–55 · default 0 |
cancel_feed_testturns something off
Stop a Feed Emulator test that is still in progress. What people had already done is kept and is charged for; a test that is already over is left as it is.
| Field | Type | Description |
|---|---|---|
idrequired | string | The Feed Emulator test's id, as create_feed_test or list_feed_tests returned it.1–64 chars |
create_website_testmakes changes
Start a website test: simulated people drawn from one real audience cohort visit a public website in a browser, look around, and say what they think and what they would do. Give the site's address, the audience (a cohort key, or `audience.describe` to let mimi pick one), who they are to the site (`visitor`: customers by default, or investors, job candidates, partners, press, or your own description), an optional question or task, and how many people (10, 20, 50 or 100). Returns the test's id; it runs for a few minutes. Then call get_website_test with waitSeconds until status is "finished", get_website_test_results for what they did and why, and get_website_test_fixes for ready-made prompts to fix the site. Nobody signs in, fills in a form or buys anything; a site whose bot check keeps the browser out is reported as blocked, never worked around. Tests are limited by available credits and wait for browser capacity across the deployment; more than one test may be queued or running. Tests of pages behind a sign-in are started from the app, not here. Costs credits (100 credits = 1 USD): the most it can cost is held when it starts, and when it ends it keeps only what its people used (their steps and browser time); a test that fails gives the hold back. Refused with insufficient_credits when the balance cannot cover the hold, and with spend_cap_reached when it would take the key past its spend cap. Send an idempotencyKey to retry safely: the same key returns the first test, and the same key with a different request is refused with idempotency_mismatch.
| Field | Type | Description |
|---|---|---|
urlrequired | string | The public website to test, e.g. "https://example.com". People start on this page; they can open other pages of the same site.4–2000 chars |
audiencerequired | object | Whose members the simulated people are drawn from. One audience a test; run another test for another audience. |
audience.cohort | string | The exact cohort whose members visit (a key from search_audiences or describe_audience).3–64 chars |
audience.describe | string | Or describe who should visit ("seed-stage investors who follow defence tech"); mimi picks the best-matching cohort.3–500 chars |
visitor | object | Who the people are to the site. Customers when left out. |
visitor.kind | "customer" | "investor" | "candidate" | "partner" | "press" | "other" | What they are on the site for. customer: might buy or use it. investor: weighing the company. candidate: thinking of working there. partner: an organisation that might work with it. press: deciding whether it is a story. other: describe them in `note`.default "customer" |
visitor.note | string | Who they are, more exactly, in your own words: "procurement officers at a defence agency". Required for kind "other".0–200 chars |
question | string | What they should find out or do, in plain words: "Decide whether you would buy from this site, and say what settled it." A task with a budget ("pick a gift for $75 to $100") holds people to it and checks the price. A fitting default for the visitor kind is used when left out.0–2000 chars |
task | "pick" | "find" | "none" | How the question is to be read, when Mimi's own reading should be corrected. pick: each person picks one thing ("find your favourite" is a pick). find: each person looks for a named thing. none: an opinion only. Read from the question when left out. |
people | integer | How many simulated people visit: 10, 20, 50 or 100. More is steadier and slower.-9007199254740991–∞ · default 10 |
device | "desktop" | "mobile" | The size of browser they use.default "desktop" |
locationState | string | US state code for all visitor browser proxies, such as CA or NY. When omitted, explicit audience state evidence is used or diverse synthetic US states are assigned; these are browser settings, not real participant locations. |
landingUrl | string | A page on the same site where people who arrive from an ad or a post start, when that is not the front page.0–2000 chars |
sharedCapture | boolean | false (default): each person opens the site in a browser of their own and sees what it shows them. true: one browser reads the site once and everyone walks that reading, which is faster.default false |
signIn | boolean | Not available through the API. A test of pages behind a sign-in is started from the app, where its owner signs in to a demo account in a live browser. |
idempotencyKey | uuid | A UUID. Sending the same request with the same key again returns the first test instead of starting a second. The same key with a different request is refused with idempotency_mismatch (409). |
list_website_testsread-only
List the account's website tests, newest first, with each one's site, audience, who visited, status and one-line result once it has finished. view "archived" lists the archived ones instead.
| Field | Type | Description |
|---|---|---|
view | "active" | "archived" | default "active" |
limit | integer | 1–100 · default 25 |
get_website_testread-only
Read a website test: the site, the audience, who the people are to it, the question, and where it stands (queued, capturing, visiting, writing, finished, failed, cancelled) with how many people are done. A failed test says why, including "blocked" when the site's bot check kept the browser out. Pass waitSeconds (up to 55) to hold until it finishes, then call get_website_test_results.
| Field | Type | Description |
|---|---|---|
idrequired | string | The website test's id, as create_website_test or list_website_tests returned it.1–64 chars |
waitSeconds | integer | Hold the request up to this many seconds while the test is in progress, and answer as soon as it finishes.0–55 · default 0 |
get_website_test_resultsread-only
Read a finished website test: how each visit ended (tapped a call to action, might come back, not for them, gave up) as counts, people kept out by a bot check, the score out of 100 and its parts, the headline, what put people off (themes with how many people, a fix and quotes), what works, what to fix first, what people picked on a task test, what the front page's first screen plainly shows, the pages and routes people took, and every person in brief. Pass `person` (a number from `everyone`) for that person's visit action by action: what they noticed, said and did. Until the test finishes it answers ready: false; pass waitSeconds to wait. Every count is computed from the stored visits. The people are simulated: present the result as a strong prior for what to fix and test, not as measured behaviour.
| Field | Type | Description |
|---|---|---|
idrequired | string | The website test's id, as create_website_test or list_website_tests returned it.1–64 chars |
waitSeconds | integer | Wait up to this many seconds for a test in progress to finish before answering.0–55 · default 0 |
person | integer | One person's number from `everyone[].person`: answer with that person's visit, action by action, instead of the whole result.0–99 |
get_website_test_fixesread-only
Read a finished website test's fix plan: for each part of the score (weakest first) where it stands, the line to reach, what to change, people's own words, and a ready-made prompt that asks for the change; then one prompt for the whole site. Use this when the job is to go and improve the site: the prompts are written from the test's own counts and quotes and can be followed as they are. The targets are a line to reach, not a forecast; run the test again afterwards to see what moved.
| Field | Type | Description |
|---|---|---|
idrequired | string | The website test's id, as create_website_test or list_website_tests returned it.1–64 chars |
cancel_website_testturns something off
Stop a website test that is still in progress and close its browsers. What people had already done is kept. A test that is already over is left as it is.
| Field | Type | Description |
|---|---|---|
idrequired | string | The website test's id, as create_website_test or list_website_tests returned it.1–64 chars |
archive_website_testmakes changes
Archive a website test (archived: true) so it leaves the active list, or restore it (archived: false). Nothing is deleted: its result, its visits and its share link are kept, and list_website_tests with view "archived" lists it.
| Field | Type | Description |
|---|---|---|
idrequired | string | The website test's id, as create_website_test or list_website_tests returned it.1–64 chars |
archived | boolean | true moves the test out of the active list; false brings it back.default true |
Prompts
Clients that support MCP prompts show these as templates or slash commands.
| Prompt | What it does | Arguments |
|---|---|---|
test_creative | Read how an audience reacts to one ad, post, headline or idea, cohort by cohort. | creativeaudience |
compare_versions | A/B (or up to six-way) test of alternatives with a chosen audience. | versionsaudience |
survey_audience | Ask an audience survey questions with no creative. | questionsaudience |
find_audience | Turn a description of who you want to reach into real cohorts. | description |
test_website | Send simulated people from an audience to a website and report what they did, what put them off and what to fix. | urlaudiencevisitor |
Example workflows
Test an ad before it ships
“Here's our new TikTok script. Test it with Gen Z gamers and with their parents, and tell me who it loses and why.”
- describe_audience for each group
- create_study (goal read, an open “what would put you off?” question)
- get_results with waitSeconds until ready
- ask_study: which cohort pushed back hardest, and on what
- ask_followup to that cohort: what would win them over
Pick the winning headline
“Which of these four subject lines gets opened by small-business owners in the US? Give me the winner per cohort.”
- search_audiences or describe_audience
- create_study with versions (goal multi)
- get_results: versionPreference, byVersion and byCohort
- share_study to send the report to the team
Find objections early
“Run a backlash check on this campaign line with conservative and progressive US adults.”
- describe_audience for each side
- create_study (goal backlash)
- get_results: objections ranked by how upset each cohort is, with the likely action
Test a website
“Have software engineers visit our careers page as job candidates and tell me why they would not apply. Then give me the changes to make.”
- create_website_test (audience.describe, visitor kind candidate)
- get_website_test with waitSeconds until finished
- get_website_test_results: outcomes, what put people off, the page checklist
- get_website_test_fixes for the prompts to change the site
Quick survey
“Ask US moms 30-45 how they decide which snacks to buy for their kids.”
- create_study with goal survey and audience.describe
- get_results: shares overall and by cohort
Troubleshooting
- 401 unauthorized. The key is missing, mistyped or revoked. Check the header reads
Authorization: Bearer mimi_…with no quotes around the key. - The client tries to sign in in a browser. It did not send the header. Add the key as shown above; Mimi does not use OAuth yet.
- A study stays "running". A run usually takes about a minute, several minutes with web research. Ask the assistant to call
get_resultswithwaitSeconds: 55again, orcancel_runto stop it. A run still going after three hours is cancelled and refunded on its own. - 429 rate_limited. Wait for the time in the message. Runs and website tests have no daily cap; 402 insufficient_credits means the balance does not cover the run, and 402 spend_cap_reached means the key reached the cap its owner set (runs, website tests and paid answers all count). 403 forbidden on
ask_studyorask_followupmeans the key lacks therunscope. - No audience matched. Name interests or topics ("into trail running"), not only demographics, or browse with
search_audiences.
curl https://getmimi.xyz/api/mcp \
-H "Authorization: Bearer $MIMI_API_KEY" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"whoami","arguments":{}}}'