Panels API
Create and manage AI panels for surveying groups of minds with structured response aggregation.
Panels API
Panels allow you to survey groups of AI minds with questions and receive aggregated, structured responses. This is useful for market research simulations, persona-based feedback gathering, and multi-perspective analysis.
Base URL: https://getminds.ai/api/v1 or https://api.getminds.ai/v1
Concepts
| Concept | Description |
|---|---|
| Panel | A container for surveying multiple mind groups with questions |
| Mind Group | A collection of minds that respond together (e.g., "Gen Z Users", "Senior Developers") |
| Question | A prompt sent to all minds in the panel's groups |
| Aggregated Response | AI-classified and grouped responses with scale or categorical values |
List Panels
Retrieve all panels belonging to the authenticated user.
Endpoint: GET /api/v1/panels
Headers:
Authorization: Bearer minds_your_api_key
Response
{
"data": [
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Consumer Research Panel",
"flowMode": "panel",
"createdAt": "2025-12-10T12:00:00.000Z",
"updatedAt": "2025-12-10T14:30:00.000Z",
"messageCount": 8,
"groups": [
{
"id": "group-123",
"name": "Gen Z Consumers",
"sparkCount": 5,
"sparks": [
{
"id": "spark-1",
"name": "Emma",
"discipline": "College Student",
"profileImageUrl": "https://..."
}
]
}
]
}
]
}
Response Fields
| Field | Type | Description |
|---|---|---|
id | string | Unique panel identifier |
name | string | Panel name |
flowMode | string | Always "panel" for panel flows |
createdAt | string | ISO 8601 creation timestamp |
updatedAt | string | ISO 8601 last update timestamp |
messageCount | number | Total number of messages (questions + responses) |
groups | array | Mind groups attached to this panel |
groups[].sparkCount | number | Number of minds in the group |
Example Request
curl -X GET "https://getminds.ai/api/v1/panels" \
-H "Authorization: Bearer minds_your_api_key"
Create Panel
Create a new panel with optional mind groups attached.
Endpoint: POST /api/v1/panels
Headers:
Authorization: Bearer minds_your_api_key
Content-Type: application/json
Request Body
{
"name": "Product Feedback Panel",
"groupIds": ["group-123", "group-456"]
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
name | string | Yes | Name of the panel |
groupIds | array | No | Array of mind group IDs to attach to the panel |
Response
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Product Feedback Panel",
"flowMode": "panel",
"createdAt": "2025-12-10T12:00:00.000Z",
"groups": [
{
"id": "group-123",
"name": "Early Adopters",
"sparks": [
{
"id": "spark-1",
"name": "Alex",
"discipline": "Tech Enthusiast",
"profileImageUrl": "https://..."
}
]
}
]
}
}
Example Request
curl -X POST "https://getminds.ai/api/v1/panels" \
-H "Authorization: Bearer minds_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Market Research Panel",
"groupIds": ["group-123", "group-456"]
}'
Error Responses
400 Bad Request - Missing name or invalid group IDs
{
"statusCode": 400,
"message": "name is required"
}
{
"statusCode": 400,
"message": "One or more groups not found"
}
Get Panel Details
Retrieve a specific panel with all its groups and message history.
Endpoint: GET /api/v1/panels/{panelId}
Headers:
Authorization: Bearer minds_your_api_key
Response
{
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"name": "Consumer Research Panel",
"flowMode": "panel",
"createdAt": "2025-12-10T12:00:00.000Z",
"updatedAt": "2025-12-10T14:30:00.000Z",
"groups": [
{
"id": "group-123",
"name": "Gen Z Consumers",
"sparks": [
{
"id": "spark-1",
"name": "Emma",
"discipline": "College Student",
"profileImageUrl": "https://..."
}
]
}
],
"messages": [
{
"id": "msg-1",
"role": "user",
"content": "How important is sustainability when choosing products?",
"metadata": {
"groupIds": ["group-123"]
},
"createdAt": "2025-12-10T14:00:00.000Z"
},
{
"id": "msg-2",
"role": "assistant",
"content": "How important is sustainability when choosing products?",
"metadata": {
"outputData": {
"title": "How important is sustainability when choosing products?",
"type": "scale",
"groups": [
{
"group": "Gen Z Consumers",
"value": "Very Important",
"answers": [
{
"value": "9/10",
"persona": "Emma",
"discipline": "College Student",
"message": "Sustainability is a top priority for me..."
}
]
}
]
},
"outputType": "bar"
},
"createdAt": "2025-12-10T14:00:30.000Z"
}
]
}
}
Example Request
curl -X GET "https://getminds.ai/api/v1/panels/550e8400-e29b-41d4-a716-446655440000" \
-H "Authorization: Bearer minds_your_api_key"
Error Responses
403 Forbidden - Not authorized to access this panel
404 Not Found - Panel does not exist
Ask Panel Question
Send a question to all minds in the panel and receive streaming responses with aggregated results.
Endpoint: POST /api/v1/panels/{panelId}/ask
Headers:
Authorization: Bearer minds_your_api_key
Content-Type: application/json
Request Body
Basic question:
{
"question": "What features would make you switch to a competitor product?",
"groupIds": ["group-123"]
}
With attachments:
{
"question": "Please review this product design and provide feedback",
"attachments": [
{
"url": "https://example.com/design.pdf",
"name": "Product Design v2",
"type": "application/pdf"
},
{
"path": "uploads/mockup.png",
"name": "UI Mockup"
}
],
"links": [
{
"label": "https://competitor.com/product",
"id": "link-1"
}
],
"keywords": [
{
"label": "sustainable packaging",
"url": "https://example.com/article",
"id": "keyword-1"
}
]
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
question | string | Yes | The question to ask all minds in the panel |
groupIds | array | No | Limit the question to specific groups (defaults to all groups) |
attachments | array | No | File attachments (PDFs, images, documents) to provide context. See file attachments below. |
links | array | No | URLs to fetch and analyze (uses Firecrawl for JS-heavy sites). Each has label (URL string) and optional id. |
keywords | array | No | Keywords with associated URLs for context. Each has label (keyword string), url (source URL), and optional id. |
model | string | No | Override the AI model used for panelist responses. See model override below. |
provider | string | No | AI provider for the model override: openai, anthropic, or google. Auto-detected from model name when possible. |
disableDiversityCheck | boolean | No | When true, skips the diversity-enforced regeneration loop (bigram self-similarity, value homogeneity, empty-bucket fill). Intended for ablation / benchmark runs where the orchestration layer is the variable under test. Default: false. |
Response (Server-Sent Events)
The endpoint returns a stream of Server-Sent Events (SSE). Each event is a JSON object with a type field.
Question Classification
Before processing, the system automatically classifies your question into one of three types:
| Type | Description | Example Questions |
|---|---|---|
scale | Numeric ratings (1-5, 1-10, etc.) | "Rate this 1-5", "Score from 0-10" |
categorical | Discrete choices (yes/no, A/B/C) | "Do you agree?", "Which do you prefer: A, B, or C?" |
qualitative | Open-ended opinions | "What do you think?", "What concerns do you have?" |
For qualitative questions, responses are automatically clustered into topics (e.g., "Privacy concerns", "Cost barriers"). Each response's value field contains its assigned topic.
Event Types
1. Start Event
{"type": "start", "total": 10}
Indicates the start of processing with total number of minds.
2. Classification Event
{
"type": "classification",
"classification": {
"type": "scale",
"scaleRange": [1, 5]
}
}
Indicates how the question was classified. For scale questions, includes the detected range. For categorical questions, includes the detected options.
3. Answer Event
{
"type": "answer",
"sparkId": "spark-1",
"sparkName": "Emma",
"discipline": "College Student",
"profileImageUrl": "https://...",
"groupId": "group-123",
"groupName": "Gen Z Consumers",
"answer": "4\n\nI think this is a solid product but could improve..."
}
Sent for each mind's individual response. For scale/categorical questions, the answer starts with the rating/choice followed by reasoning.
4. Aggregating Event
{"type": "aggregating"}
Indicates AI is now aggregating all responses. For qualitative questions, this includes topic clustering.
5. Result Event
{
"type": "result",
"outputData": {
"title": "What features would make you switch to a competitor product?",
"type": "categorical",
"classification": {
"type": "categorical",
"options": ["Yes", "No", "Maybe"]
},
"groups": [
{
"group": "Gen Z Consumers",
"value": "Better Price",
"alignmentScore": 82,
"answers": [
{
"value": "Price",
"persona": "Emma",
"discipline": "College Student",
"message": "I would switch if a competitor offered better pricing...",
"imageUrl": "https://...",
"reliabilityScore": 84
}
]
}
]
},
"outputType": "bar"
}
Contains the aggregated results with classified responses. alignmentScore and per-answer reliabilityScore are computed before the result is returned on v1 endpoints (see Alignment scoring).
6. Done Event
{"type": "done"}
Indicates the stream is complete.
Output Data Structure
| Field | Type | Description |
|---|---|---|
title | string | The original question |
type | string | Response type: "scale", "categorical", or "qualitative" |
classification | object | Classification details (type, scaleRange, or options) |
groups | array | Aggregated responses by spark group |
groups[].group | string | Group name |
groups[].value | string | Dominant value for the group (average for scale, most common for categorical, dominant topic for qualitative) |
groups[].alignmentScore | number? | Average of per-answer reliabilityScore for the group (0–100). See Alignment scoring. Omitted when no answer in the group could be scored. |
groups[].answers | array | Individual mind responses |
groups[].answers[].value | string | Extracted value: number for scale, choice for categorical, topic for qualitative |
groups[].answers[].persona | string | Spark name |
groups[].answers[].discipline | string | Spark discipline/role |
groups[].answers[].message | string | Full response text (reasoning for scale/categorical, full answer for qualitative) |
groups[].answers[].imageUrl | string | Spark profile image URL |
groups[].answers[].reliabilityScore | number? | Per-mind reliability score (0–100): how on-character this mind's answer was against its own persona definition. See Alignment scoring. Omitted when the evaluator was skipped (short systemPrompt, empty message) or failed. |
Response Types Explained
Scale responses:
value: The numeric rating (e.g., "4")message: Brief reasoning for the ratinggroups[].value: Average rating across the group
Categorical responses:
value: The chosen option (e.g., "Yes", "Option A")message: Brief reasoning for the choicegroups[].value: Most common choice in the group
Qualitative responses:
value: Assigned topic/theme (e.g., "Privacy concerns", "Cost barriers")message: Full response textgroups[].value: Dominant topic in the group- Topics are automatically clustered from all responses (3-6 topics identified)
Alignment scoring
Every panel answer includes two scores on the v1 API response:
groups[].answers[].reliabilityScore(0–100, integer, optional) — per-mind score of how on-character the mind's answer is against its ownsystemPrompt. Computed by re-evaluating the response with the same evaluator used for individual spark chats, so the v1 panel value is directly comparable to single-mindreliabilityScorevalues.groups[].alignmentScore(0–100, integer, optional) — average of per-answerreliabilityScorefor that group. The UI surfaces this as the per-group Alignment indicator (High / Medium / Low).
Label bands used by the UI (not in the payload, included here so API consumers can match):
| Band | Range |
|---|---|
| High | 67–100 |
| Medium | 34–66 |
| Low | 0–33 |
When fields are omitted: the evaluator skips answers where the mind's systemPrompt is shorter than 20 characters, where the answer message is empty, or when the evaluator call itself fails. If every answer in a group is skipped, that group's alignmentScore is also omitted.
Timing: on the v1 endpoints, scoring runs synchronously before the response is returned, so the scores are present in the same payload as the rest of outputData. This adds a few seconds of latency on top of panel generation; consumers that need a faster panel result without alignment should batch-evaluate downstream instead of relying on the inline score.
Status: this is a temporary stand-in for a future group-alignment metric (closeness to empirical research findings). The field names will be preserved when that lands; the semantics of alignmentScore may change.
File Attachments
You can attach files, links, and keywords to provide context for panel questions. Minds will receive the processed content before answering.
Attachment Types
1. File Attachments (attachments)
Upload documents, PDFs, images, spreadsheets for analysis:
{
"question": "What improvements would you suggest for this product spec?",
"attachments": [
{
"url": "https://example.com/product-spec.pdf",
"name": "Product Specification v2.1",
"type": "application/pdf"
},
{
"path": "uploads/user-research.docx",
"name": "User Research Findings"
}
]
}
Supported formats:
- Documents: PDF, DOCX, TXT, MD
- Images: PNG, JPG, WEBP (with OCR)
- Spreadsheets: CSV, XLSX
File sources:
url: External URL (downloaded and processed)path: Supabase storage path (auto-signed and processed)
2. Link Attachments (links)
Fetch and analyze web pages (uses Firecrawl for JS-heavy sites + screenshots):
{
"question": "Compare our pricing to these competitors",
"links": [
{ "label": "https://competitor-a.com/pricing", "id": "link-1" },
{ "label": "https://competitor-b.com/pricing", "id": "link-2" }
]
}
Features:
- JavaScript rendering (Firecrawl)
- Screenshot capture for visual context
- Markdown extraction
- Automatic content truncation (3000 chars per link if multiple, 15000 if single)
3. Keyword Context (keywords)
Provide keywords with source URLs for additional context:
{
"question": "How can we improve sustainability?",
"keywords": [
{
"label": "circular economy",
"url": "https://en.wikipedia.org/wiki/Circular_economy",
"id": "kw-1"
},
{
"label": "carbon neutral packaging",
"url": "https://example.com/carbon-neutral-guide",
"id": "kw-2"
}
]
}
Complete Example with Attachments
curl -X POST "https://getminds.ai/api/v1/panels/panel-id/ask" \
-H "Authorization: Bearer minds_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"question": "Based on this product design and competitor analysis, what features should we prioritize?",
"groupIds": ["product-managers", "designers"],
"attachments": [
{
"url": "https://example.com/product-design-v3.pdf",
"name": "Product Design v3",
"type": "application/pdf"
}
],
"links": [
{ "label": "https://competitor.com/features" }
],
"keywords": [
{
"label": "user experience best practices",
"url": "https://uxdesign.com/best-practices"
}
]
}'
Processing:
- Files are analyzed in parallel (PDFs → text extraction, images → OCR/vision)
- Links are fetched with Firecrawl (JS rendering + screenshots)
- Content is injected into the question context for all minds
- Failed attachments are gracefully handled with fallback messages
Tips:
- Attach only relevant files (each adds processing time)
- Use links for dynamic web content
- Use keywords for additional web context
- File processing timeout: 30s per file
- Link fetching timeout: 15s per URL
Example Request
curl -X POST "https://getminds.ai/api/v1/panels/550e8400-e29b-41d4-a716-446655440000/ask" \
-H "Authorization: Bearer minds_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"question": "On a scale of 1-10, how likely are you to recommend this product?"
}'
Example: JavaScript EventSource
const eventSource = new EventSource(
'https://getminds.ai/api/v1/panels/{panelId}/ask',
{
headers: {
'Authorization': 'Bearer minds_your_api_key',
'Content-Type': 'application/json'
}
}
);
// Note: For POST requests with SSE, use fetch with ReadableStream
const response = await fetch('https://getminds.ai/api/v1/panels/{panelId}/ask', {
method: 'POST',
headers: {
'Authorization': 'Bearer minds_your_api_key',
'Content-Type': 'application/json'
},
body: JSON.stringify({
question: 'How satisfied are you with the current pricing?'
})
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const event = JSON.parse(line.slice(6));
console.log('Event:', event.type, event);
}
}
}
Error Responses
400 Bad Request - Missing question or no groups attached
{
"statusCode": 400,
"message": "question is required"
}
{
"statusCode": 400,
"message": "No groups attached to this panel"
}
{
"statusCode": 400,
"message": "No minds in panel groups"
}
403 Forbidden - Not authorized to access this panel
404 Not Found - Panel does not exist
Model Override
By default, panel responses use the model configured in the panel's Langfuse prompt (typically Claude). You can override the model and provider per-request to run experiments across model families:
curl -X POST "https://getminds.ai/api/v1/panels/{panelId}/ask" \
-H "Authorization: Bearer minds_…_key" \
-H "Content-Type: application/json" \
-d '{
"question": "Rate this 1-5",
"model": "gpt-4o",
"provider": "openai"
}'
Supported providers: openai, anthropic, google. The provider is auto-detected for common model name patterns (gpt-*, o1-*, o3-*, o4-* → openai; claude-* → anthropic; gemini-* → google), but can be specified explicitly to disambiguate.
Disable Diversity Check
The panel orchestrator runs a post-generation diversity-enforced regeneration loop (bigram self-similarity check, value-homogeneity detection, empty-bucket fill) before aggregation. This is the L4 layer of the panel recipe.
For ablation studies and benchmark runs where you want to isolate the contribution of this layer, pass disableDiversityCheck: true:
curl -X POST "https://getminds.ai/api/v1/panels/{panelId}/ask" \
-H "Authorization: Bearer minds_…_key" \
-H "Content-Type: application/json" \
-d '{
"question": "What features matter most to you?",
"disableDiversityCheck": true
}'
With the flag enabled, panelist responses are returned exactly as initially generated — no second-pass regeneration is triggered, even if responses overlap heavily. Classification (L3), per-spark RAG (L2), and aggregation (L5) still run normally. Cost-savings: ~5–25% fewer LLM calls per panel question, depending on how many sparks the diversity check would have flagged.
When to use: Method comparisons, A/B tests of orchestration layers, reproducing baseline behavior. Production panels should leave this off (default).
Export Panel Results
Generate a structured report of all panel results in Markdown format.
Endpoint: POST /api/v1/panels/{panelId}/export
Headers:
Authorization: Bearer minds_your_api_key
Content-Type: application/json
Request Body
{
"format": "md"
}
Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
format | string | No | Export format. Currently only "md" (Markdown) is supported. Default: "md" |
Response
{
"data": {
"format": "md",
"content": "# Panel Report: Consumer Research Panel\n\n## Executive Summary\n\nThis panel survey gathered insights from 15 participants across 3 consumer groups...\n\n## Methodology\n\n- 3 groups, 15 participants\n- 5 questions asked\n\n## Results by Question\n\n### Q1: How important is sustainability when choosing products?\n\n**Type:** scale\n\n#### Gen Z Consumers (dominant: Very Important)\n\n..."
}
}
Report Structure
The generated report includes:
- Executive Summary - 2-3 paragraph overview of key findings
- Methodology - Groups, participants, and structure
- Results by Question - Cross-group comparison with key insights and quotes
- Cross-Group Analysis - Patterns and trends across groups
- Conclusions & Recommendations - Actionable insights
Example Request
curl -X POST "https://getminds.ai/api/v1/panels/550e8400-e29b-41d4-a716-446655440000/export" \
-H "Authorization: Bearer minds_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"format": "md"
}'
Error Responses
403 Forbidden - Not authorized to access this panel
404 Not Found - Panel does not exist
Check Export Status
Check the status of a panel export job. If no jobId is provided, returns the status of the most recent export.
Endpoint: GET /api/v1/panels/{panelId}/export-status
Headers:
Authorization: Bearer minds_your_api_key
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
jobId | string | No | Specific job ID. If omitted, returns the most recent export job |
Response
{
"data": {
"status": "completed",
"downloadUrl": "/api/v1/panels/{panelId}/export-download?jobId=job-123"
}
}
Status Values
| Status | Description |
|---|---|
queued | Export job is waiting to be processed |
processing | Export is being generated (includes progress field, 0-100) |
completed | Export is ready for download (includes downloadUrl field) |
failed | Export failed (includes error field with reason) |
Example Request
curl -X GET "https://getminds.ai/api/v1/panels/{panelId}/export-status?jobId=job-123" \
-H "Authorization: Bearer minds_your_api_key"
Error Responses
403 Forbidden - Not authorized to access this panel
404 Not Found - Panel or job does not exist
Download Export
Download the exported panel report as a PDF file.
Endpoint: GET /api/v1/panels/{panelId}/export-download
Headers:
Authorization: Bearer minds_your_api_key
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
jobId | string | Yes | The export job ID (from export-status response) |
Response
Returns a PDF file with appropriate headers:
Content-Type: application/pdfContent-Disposition: attachment; filename="Panel-Report.pdf"
Example Request
curl -X GET "https://getminds.ai/api/v1/panels/{panelId}/export-download?jobId=job-123" \
-H "Authorization: Bearer minds_your_api_key" \
-o panel-report.pdf
Error Responses
400 Bad Request - Missing jobId parameter or job is not yet completed
403 Forbidden - Not authorized to access this panel
404 Not Found - Panel or job does not exist
Workflow Example
Here is a complete workflow for creating and using a panel:
# 1. Create spark groups first (using Sparks API)
# Assume you have created groups with IDs: group-genz, group-millennials
# 2. Create a panel with those groups
curl -X POST "https://getminds.ai/api/v1/panels" \
-H "Authorization: Bearer minds_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"name": "Product Pricing Research",
"groupIds": ["group-genz", "group-millennials"]
}'
# Response: { "data": { "id": "panel-123", ... } }
# 3. Ask questions to the panel
curl -X POST "https://getminds.ai/api/v1/panels/panel-123/ask" \
-H "Authorization: Bearer minds_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"question": "What price point would you consider fair for this product?"
}'
# 4. Ask another question
curl -X POST "https://getminds.ai/api/v1/panels/panel-123/ask" \
-H "Authorization: Bearer minds_your_api_key" \
-H "Content-Type: application/json" \
-d '{
"question": "How does this compare to competitor pricing?"
}'
# 5. Export the results as a report
curl -X POST "https://getminds.ai/api/v1/panels/panel-123/export" \
-H "Authorization: Bearer minds_your_api_key" \
-H "Content-Type: application/json" \
-d '{"format": "md"}'
# 6. Check export status (poll until completed)
curl -X GET "https://getminds.ai/api/v1/panels/panel-123/export-status" \
-H "Authorization: Bearer minds_your_api_key"
# Response: { "data": { "status": "completed", "downloadUrl": "/api/v1/panels/panel-123/export-download?jobId=..." } }
# 7. Download the PDF
curl -X GET "https://getminds.ai/api/v1/panels/panel-123/export-download?jobId=job-123" \
-H "Authorization: Bearer minds_your_api_key" \
-o panel-report.pdf
Error Codes Summary
| Code | Description |
|---|---|
| 400 | Bad Request - Missing required fields or invalid data |
| 401 | Unauthorized - Invalid or missing API key |
| 403 | Forbidden - Not authorized to access this panel |
| 404 | Not Found - Panel does not exist |
| 500 | Internal Server Error - Server-side error |
Next Steps
- Create minds to populate your panel groups
- Learn about authentication
- Review errors and limits