Focus Group for agents
One call puts a piece of copy in front of 4–8 simulated panelists and returns their scores, objections and revisions — including the one who disliked it. Three rails: REST with a key, MCP, and keyless x402.
These panelists are simulated by a language model. Nobody was surveyed, no real person is represented, and none of this is market research data. Every response carries synthetic: true for exactly this reason. Do not present this output as research.
Authentication
Three credentials work, and they differ only in what they can reach.
- fcg_live_ — this app's own key, minted at Dashboard → API keys. Runs panels, reads them, exports them. Cannot pass
brain_ids, because that reaches into your private Ounie account. - ounie_live_— your ounie.com developer key, the fleet MASTER key. Everything above, plus grounding a panel in your Ounie brains. Enable “Use across Ounie apps” on ounie.com first.
- x402 — no account at all. Pay per call in USDC on Base. See below.
Authorization: Bearer fcg_live_… # or, when your client cannot set headers: https://focusgroup.ounie.com/api/panels?api_key=fcg_live_…
Every keyed call draws the key owner's shared Ounie credit wallet and is refused with 402 insufficient_credits when the balance is short — an agent can never overdraw.
Run a panel
/api/panels8 credits| Field | Type | Notes |
|---|---|---|
| artifact | string | The copy under test, 100–8000 chars. Required unless artifact_url is given. |
| artifact_url | string | A public http(s) page whose readable text becomes the artifact. Ignored when artifact is supplied. |
| icp | string | Who the panel represents. Optional, but the sharper it is the sharper the objections. |
| focus | enum | clarity | objections | pricing | positioning | trust — default objections. |
| panel_size | int | 4–8, default 6. Does not change the price. |
| brain_ids | uuid[] | Up to 5 Ounie brains to ground the panelists in. Needs the master key. |
| title | string | Optional name for the panel. |
curl -X POST https://focusgroup.ounie.com/api/panels \
-H "Authorization: Bearer fcg_live_…" \
-H "Content-Type: application/json" \
-d '{
"artifact": "One tool. Every workflow. Start free…",
"icp": "Solo consultants who bill hourly",
"focus": "pricing",
"panel_size": 6
}'{
"ok": true,
"panel_id": "…",
"synthetic": true,
"notice": "These panelists are simulated…",
"panel_score": 2.8,
"dissent": "The freelancer never reaches the form — there is no price on the page.",
"top_objections": ["No price anywhere", "…"],
"revisions": ["Put the number on the page", "…"],
"personas": [
{
"id": "p1",
"label": "Solo consultant, 8 years, bills hourly",
"archetype": "Budget-conscious skeptic",
"context": "Evaluating on a Sunday, will not book a call",
"score": 2,
"firstReaction": "…",
"objections": ["…"],
"wouldAct": false,
"suggestedRevision": "…",
"quote": "…",
"citations": [],
"synthetic": true
}
],
"grounded": false,
"credits_spent": 8
}label is a role description. If the model returns something that reads as a person's name, the server replaces it with the archetype before it reaches you — so you can never accidentally publish “what Sarah Chen said” about copy nobody read.Read and export
Reads of a panel you already paid for are free, forever.
/api/panelsfree/api/panels/{id}free/api/panels/{id}/export?format=csv|markdown|jsonfree/api/panels/{id}rename · free/api/panels/{id}freeCSV and Markdown exports carry the “simulated, not surveyed” notice inside the file, because an exported table is the thing most likely to be mistaken for research once it leaves this app.
MCP
One MCP server at https://focusgroup.ounie.com/api/mcp (Streamable HTTP) and /api/sse (legacy). Works with Claude, Cursor, ChatGPT, the AI SDK, and the Ounie AI Team — which cannot set static headers, so the key rides the URL as ?api_key=.
{
"mcpServers": {
"focus-group": {
"url": "https://focusgroup.ounie.com/api/mcp",
"headers": { "Authorization": "Bearer fcg_live_…" }
}
}
}| Tool | Cost | What it does |
|---|---|---|
| run_panel | 8 cr | Run a simulated panel against an artifact. |
| list_panels | free | Your panels, newest first, with scores and dissent. |
| get_panel | free | One panel with every panelist's full reaction. |
| export_panel | free | CSV or Markdown text. |
| get_credit_balance | free | Spendable Ounie credits. |
| get_pricing | free | Public — no auth needed. |
| whoami | free | Owner id, key id, and whether this key can ground in brains. |
x402 — no account, pay per call
/api/x402/panelUSDC on BaseFor agents with no Ounie account. The first call returns 402 with a price quoted in atomic USDC; retry the same request with an X-Payment header. The price is computed from your payload, never a constant in the route.
| Field | Type | Notes |
|---|---|---|
| artifact | string | 100–8000 chars. Required unless artifact_url is given. |
| artifact_url | string | A public http(s) page. Read for free before any price is quoted. |
| icp | string | Who the panel represents. |
| focus | enum | clarity | objections | pricing | positioning | trust |
| panel_size | int | 4–8 |
| source_text | string | Optional customer research to ground the panelists in, 200–20000 chars. Replaces brain_ids on this rail — a keyless caller has no brains. |
| source_title | string | What to call that research in citations. |
# 1. quote
curl -X POST https://focusgroup.ounie.com/api/x402/panel \
-H "Content-Type: application/json" \
-d '{"artifact":"…","icp":"Solo consultants","focus":"pricing"}'
→ 402 { "x402Version": 1, "accepts": [{ "scheme": "exact",
"network": "base", "maxAmountRequired": "…", "payTo": "0x…" }] }
# 2. pay
curl -X POST https://focusgroup.ounie.com/api/x402/panel \
-H "X-Payment: <base64 signed authorization>" \
-H "Content-Type: application/json" -d '{ … }'focus, a too-short artifact, a non-public artifact_url, a page with no readable text — returns 4xx with no price attached. We will not make you sign an authorization for work that cannot succeed. Anything only knowable afterwards — a panel with no dissenter — is caught after your payment is verified but before it is settled, so nothing moves on chain and your signed authorization simply goes unused.Panels paid over x402 are returned in the response and not stored — there is no account to store them against.
The honesty gate
A panel where every panelist scored the artifact 4 or 5 is not a good result. It is a broken panel, and the entire value of this product is the reaction you did not want. So it is checked in code, not left to the prompt:
- At least one panelist must score 3 or lower.
- That panelist must raise at least one concrete objection.
- The disagreement must survive into the summary as a non-empty
dissentline, rather than being averaged away.
If the first pass fails, we retry once with a stricter brief. If it fails again the run is refused: 422 thin_panel on the credit rail with a full pool-exact refund, and 402 thin_panel before settlement on x402. You are never billed for a room that agreed with you.
This is also why a genuinely strong piece of copy still comes back with a low scorer. That panelist is the person your copy is not for, explaining why — which is usually the more useful half of the answer.
Errors
| Status | Error | Meaning |
|---|---|---|
| 400 | invalid_payload | A required field is missing or an enum value is unknown. Always fatal; never quoted on x402. |
| 400 | artifact_too_short | Under 100 characters of copy. |
| 400 / 422 | artifact_unreadable | The URL was non-public, unreachable, the wrong content type, or returned no readable text. 400 on x402 (before the quote), 422 on the credit rail (before anything is reserved). |
| 401 | auth_required | No valid credential. |
| 402 | insufficient_credits | Carries required_credits, balance_credits and buy_credits_url. |
| 403 | master_key_required | brain_ids was passed with a per-app key. Drop brain_ids or use your ounie_live_ key. |
| 404 | unknown_brain | A brain_id is not on your account. |
| 422 / 402 | thin_panel | No genuine dissenter after a retry. Refunded in full; never settled on x402. |
| 502 | generation_failed | The model returned nothing usable. Refunded in full. |