# Polis — full documentation > Polis tests any piece of content against a realistic synthetic audience and returns what they actually thought — built MCP-first for AI agents. # Polis > Polis tests any piece of content against a realistic synthetic audience and returns what they actually thought — built MCP-first for AI agents. Polis is an MCP server. Point an AI agent at `https://polis.ai/api/mcp` with a bearer API key and it can run a real audience simulation against any content in one tool flow. ## Start here - Fastest: a human runs `curl -fsSL https://polis.ai/start | sh -s -- you@example.com` (provisions a key + prints the checkout link). - New agent: `POST https://polis.ai/api/provision` with `{ "email": "..." }` to get a key and a checkout link for the human. - Already have a key: connect MCP at `https://polis.ai/api/mcp`, call `polis_account`, then `polis_estimate` → `polis_test` → `polis_status` → `polis_report`. - Full index: `https://polis.ai/llms.txt` · everything inlined: `https://polis.ai/llms-full.txt` ## Docs - [What Polis is](https://polis.ai/docs/overview.md): The concept, inputs, and what you get back. - [Agent quickstart](https://polis.ai/docs/quickstart.md): Connect, get a key, run a test, read the report. - [How to run a test](https://polis.ai/docs/run-a-test.md): The workflow, a worked example, and how to read the report. - [Test options reference](https://polis.ai/docs/test-options.md): Every input, every variation, and the full report schema. - [MCP tools](https://polis.ai/docs/mcp-tools.md): Every tool, its inputs, and cost. - [Pricing & billing](https://polis.ai/docs/pricing.md): Polis tokens, plans, free trial, metered usage, spend limit. - [Discovery & well-known endpoints](https://polis.ai/docs/discovery.md): How agents and clients discover Polis. --- # What Polis is Polis runs **persona testing** as a service, MCP-first, for AI agents. You give Polis a piece of content. Polis: 1. Works out who the realistic audience is (you can state it; otherwise it is inferred from the content). 2. Generates a panel of concrete, situational synthetic personas — skeptics, scrollers, and accidental viewers included, not demographic stereotypes. 3. Fans the content out across a multi-model swarm so each persona reacts in its own voice (different model lineages reason differently — that diversity is the point). 4. Synthesizes the reactions into a deliverable: audience **segments**, the **top friction points** with evidence, and a **rewrite** that is visibly better than the original. It is designed so an agent can run a real audience simulation as a single tool call, instead of guessing what people will think. ## Inputs - `text` — raw copy. - `tweet` — a tweet / short post. - `linkedin` — a LinkedIn post. - `markdown` — a long-form article or doc. - `url` — a public web page, either `copy` mode (scrape the text) or `visual` mode (screenshot the page; vision-capable personas react to the actual design). ## Output A JSON report: `segments` (3 reaction-pattern clusters), `topFriction` (3 ranked points with verbatim evidence), `rewrite` (same shape/length as the original), optional `structuralNotes` for pages, plus aggregate stats and a sample of raw verbatim reactions. --- # Agent quickstart ## 0. One-liner (fastest) A human can run this in a terminal — it provisions a key and prints the checkout link: ``` curl -fsSL https://polis.ai/start | sh -s -- you@example.com ``` ## 1. Get an API key Either a human signs in at `https://polis.ai/auth/sign-in` and copies a key from the dashboard, **or** an agent self-provisions: ``` POST https://polis.ai/api/provision Content-Type: application/json { "email": "human@company.com" } ``` The response contains an `api_key` (shown once), a `checkout_url` (give this to the human to complete the $5/mo subscription), and `mcp_url`. ## 2. Connect the MCP server Remote MCP, Streamable HTTP transport: ``` https://polis.ai/api/mcp Authorization: Bearer sk_live_... ``` ## 3. Confirm the account is active Call `polis_account`. If it returns `active: false`, relay the `checkout_url` to the human, then poll until `active: true`. ## 4. Estimate, then run Call `polis_estimate` to see the token + USD cost and how it splits against your remaining tokens and the spend limit. Then `polis_test`: ```json { "content_type": "tweet", "content": "Ship faster. Polis tells you what your audience thinks before you post.", "persona_count": 100 } ``` ## 5. Poll, then read the report `polis_test` returns a `run_id`. Poll `polis_status` until `done`, then call `polis_report` for segments, friction, the rewrite, and sample reactions. --- # How to run a test This is the high level walkthrough. For every input option and the full output schema, see [Test options reference](https://polis.ai/docs/test-options). ## The flow A persona test is asynchronous. It is always the same four steps: 1. **Estimate.** Call `polis_estimate` with the same inputs you plan to test. It returns the token cost, the USD cost, and how it splits against your remaining tokens and your spend limit. This never charges anything. 2. **Start.** Call `polis_test`. It charges the estimated tokens up front and returns a `run_id` immediately. The test runs in the background. 3. **Poll.** Call `polis_status` with the `run_id` every few seconds. It reports how many personas have reacted and a `done` flag. 4. **Read.** Once done, call `polis_report` for the deliverable. If a run fails before producing a report, the tokens are refunded automatically. ## Worked example Test a tweet against 100 personas: ```json // polis_test { "content_type": "tweet", "content": "Ship faster. Polis tells you what your audience thinks before you post.", "persona_count": 100 } // -> { "run_id": "9f3c...", "status": "pending", ... } ``` Poll `polis_status` until `done: true`, then `polis_report` with that `run_id`. ## What the report means This is the important part. `polis_report` returns the deliverable plus the raw material behind it. ### segments Three clusters of how the audience reacted, named by **behaviour, not demographics**. Each has a `name`, a `share` (0 to 1, the rough fraction of the audience in that cluster), and a one or two sentence `summary`. Example: ```json { "name": "Hooked but skeptical", "share": 0.38, "summary": "Liked the speed promise but wanted proof. Several asked 'what does it actually do'." } ``` Read these to understand the shape of the room: who leaned in, who bounced, and why each group did. ### topFriction The three biggest reasons the content underperformed, ranked. Each has a `point` (the friction in one line), a `severity` (0 to 10, how much it cost you), and `evidence` (a real verbatim pattern from the reactions that proves the friction is real, not invented). This is your prioritized fix list. ### rewrite A rewritten version of the content that addresses the top friction without losing the original voice, length, or format. For a tweet it stays tweet sized; for a landing page it keeps the same structural slots. It is meant to be visibly better than the original and usable more or less as is. ### structuralNotes (pages only) For `url` and `markdown` content, 0 to 2 short notes about layout or ordering changes (for example "move the proof point above the fold"). Omitted for short feed content. ### stats Aggregate counts so you can see the distribution at a glance: how many personas chose each 5 second decision (`stay`, `scroll`, `click`, `save`), the mean purchase or engagement `intent` (0 to 10), and which swarm models produced the reactions. ### sample_reactions A sample of individual verbatim reactions, the raw material the synthesis is built from. Each persona returns: `archetype` (which audience segment they represent), `first_glance` (a three word gut read), `decision` (stay / scroll / click / save), `noticed_first`, `friction` (their single objection), `quote` (what they would actually say, in their own voice), and `intent` (0 to 10). Read these when a segment summary surprises you and you want the actual words behind it. ## How to act on it Use `topFriction` as the fix list, ship the `rewrite` (or adapt it), and re-run to confirm the friction dropped. `segments` and `sample_reactions` are there when you need to understand or defend a decision. --- # Test options reference The detailed companion to [How to run a test](https://polis.ai/docs/run-a-test). Every `polis_test` (and `polis_estimate`) input and what it changes. ## content_type How the content is framed to the personas. This changes the viewing context the personas are put in, which changes how they react. | content_type | What it is | Framing | |--------------|-----------|---------| | `text` | Any raw copy | Generic short read | | `tweet` | A tweet or short post | Encountered in a fast feed, low attention, high skepticism | | `linkedin` | A LinkedIn post | Professional feed, scroll context | | `markdown` | A long-form article or doc | Arrived to read; judged in the first screens | | `url` | A public web page | Landing page they just opened | For `text`, `tweet`, `linkedin`, `markdown` pass the content in `content`. For `url` pass `url` instead. ## url_mode (url only) | url_mode | What happens | Use it for | Cost | |----------|--------------|------------|------| | `copy` (default) | Polis scrapes the page text and tests the message | Testing the words, value prop, and offer | Standard per-persona | | `visual` | Polis screenshots the page; vision-capable personas react to the actual design and layout | Testing the design, hierarchy, and first impression | ~3x per persona (vision models + image) | `visual` runs can return `structuralNotes` (layout advice). `copy` runs do not. ## audience Optional. A short description of who you are targeting (for example "early-stage B2B founders evaluating analytics tools"). If you omit it, Polis infers the realistic audience from the content, including adjacent and accidental viewers. Set it when the content alone would not reveal the intended audience, or when you want to test against a specific segment. ## persona_count How many synthetic personas to simulate. Range 10 to 1000, default 100. More personas means tighter, more reliable segments and friction, and more cost (it scales roughly linearly). Rough guidance: - **10 to 50** — fast gut check, cheap, directional only. - **100** — standard test, good signal for most decisions. - **300 to 500** — high-confidence read before a launch or paid campaign. - **1000** — maximum resolution; mind your spend limit. Always `polis_estimate` first; cost scales with this number. See [Pricing & billing](https://polis.ai/docs/pricing). ## Full report schema `polis_report` returns: ```json { "run_id": "string", "status": "complete", "ready": true, "content_type": "tweet", "url": "string | null", "audience": "string | null", "personas_reacted": 100, "stats": { "decisions": { "stay": 0, "scroll": 0, "click": 0, "save": 0 }, "mean_intent": 0.0, "model_distribution": { "model": 0 } }, "archetypes": [ { "name": "string", "description": "string", "weight": 0.0 } ], "synthesis": { "segments": [ { "name": "string", "share": 0.0, "summary": "string" } ], "topFriction": [ { "point": "string", "severity": 0, "evidence": "string" } ], "rewrite": "string", "structuralNotes": ["string"] }, "sample_reactions": [ { "archetype": "string", "first_glance": "string", "decision": "stay|scroll|click|save", "noticed_first": "string", "friction": "string", "quote": "string", "intent": 0 } ] } ``` `archetypes` is the audience panel Polis generated (the segments it expected to find). `synthesis` is the deliverable. See [How to run a test](https://polis.ai/docs/run-a-test) for what each field means and how to act on it. ## Failure and refunds If a run fails before producing a report (for example a URL that cannot be fetched), `polis_status` and `polis_report` return the error and the tokens are refunded automatically. Trial and inactive accounts can only run tests that fit within their remaining token balance; a run needing metered overage returns `subscription_required` with the plan options. --- # MCP tools Endpoint: `https://polis.ai/api/mcp` (Streamable HTTP). Auth: `Authorization: Bearer `. | Tool | Cost | Purpose | |------|------|---------| | `polis_account` | free | Subscription status, included allowance left, overage used, usage cap. Returns a checkout URL if inactive. | | `polis_estimate` | free | Token + USD estimate for a prospective run and how it splits vs. the spend limit. | | `polis_test` | charged up front | Start a persona test. Returns a `run_id`. Refunded automatically if the run fails before a report. | | `polis_status` | free | Progress for a `run_id` (reactions done / total, done flag). | | `polis_report` | free | The completed deliverable + aggregate stats + sample verbatim reactions. | `polis_test` inputs: `content_type` (text|tweet|linkedin|markdown|url), `content` (unless url), `url` + `url_mode` (copy|visual) for url, optional `audience`, `persona_count` (10-1000, default 100). Runs are asynchronous. Always: `polis_estimate` → `polis_test` → poll `polis_status` → `polis_report`. --- # Pricing & billing The unit is the **Polis token**. One number you spend, with a visible balance and a monthly reset. You see the exact token cost before every run (`polis_estimate`). Standard rate: **$5 per 1,000 tokens**. ## Free trial Every new account gets **200 free trial tokens** (no card). You can run tests that fit within your balance immediately. Subscribe for usage beyond the trial. ## Plans | Plan | Price/mo | Included tokens | Per-token | |------|----------|-----------------|-----------| | Starter | $5 | 1,000 | standard | | Pro | $50 | 12,500 | ~20% cheaper | | Scale | $200 | 55,000 | ~27% cheaper | Beyond the included tokens, usage is **strictly metered** at the standard rate ($5/1,000) and billed via Stripe. The higher tiers are a discount on the included bucket. Every account has a **monthly spend limit** (default $25); runs that would exceed it are blocked before any charge. No surprise bills. ## Token cost of a run ``` tokens = 60 (base: persona generation + synthesis) + persona_count x 1 (text) or x 3 (visual URL) + 8 (only for url content) + 2/1k tokens of artifact beyond the first 1k, scaled by persona_count (long content is shown to every persona) ``` Example: 100 personas on a tweet = 60 + 100 = 160 tokens = $0.80. Pass `content` to `polis_estimate` for an exact quote on long articles. ## Rough costs by scenario These are computed from the live pricing formula, so they match what you are actually charged. Persona count is the biggest lever; a screenshot (visual url) run costs more per persona than a text run; long content adds a little because every persona sees it. | Scenario | Polis tokens | Approx. cost | |----------|--------------|--------------| | Tweet / short post, 100 personas | 160 | $0.80 | | Quick text test, 25 personas | 85 | $0.43 | | Website (scrape text), 100 personas | 168 | $0.84 | | Website (screenshot, vision), 100 personas | 368 | $1.84 | | Website (screenshot, vision), 200 personas | 668 | $3.34 | | Long article (~3k words), 150 personas | 218 | $1.09 | | Large run, 500 personas, tweet | 560 | $2.80 | A screenshot (`url` + `visual`) run is roughly 3x the per-persona cost of a text run, because vision-capable models are pricier and the page image is sent to every persona. `copy` mode (scrape the text) is the cheap way to test a page's message; `visual` is for testing the actual design. Always call `polis_estimate` first. Tokens for runs that fail before producing a report are refunded automatically. A human completes Stripe Checkout (agents cannot pay yet — see the roadmap for agent-native payment). --- # Discovery - `https://polis.ai/llms.txt` — this index. - `https://polis.ai/llms-full.txt` — all docs inlined for single-fetch ingestion. - Append `.md` to most pages, or send `Accept: text/markdown`, for the Markdown representation. - `https://polis.ai/.well-known/mcp/server-card.json` — MCP server card (tools, transport, auth). - `https://polis.ai/.well-known/oauth-protected-resource` — RFC 9728 protected-resource metadata; an unauthenticated MCP request returns `401` with a `WWW-Authenticate` hint. - `https://polis.ai/api/provision` — unauthenticated agent self-onboarding (returns an API key + a human checkout URL).