---
title: "Studies API"
description: "Create Studies, run cohesive multi-question blocks with Audiences, inspect results, and export artifacts through the Minds v1 API."
---

# Studies API

A **Study** is the research workspace. It contains one or more Audiences, questions, evidence, results, analysis, and exports.

**Base URL:** `https://api.getminds.ai/v1`

Study list and detail responses include `isPublic`, `isLinkSharingEnabled`, and `publicShareId`. A retained `publicShareId` alone does not establish public access: the share route requires `isPublic: true` or `isLinkSharingEnabled: true`. Recheck visibility before handing a link to a recipient.

`GET /studies/{studyId}/runs` returns newest-first pages: `limit` defaults to 20 and is capped at 100. Continue with `before` set to `pagination.nextCursor` while `pagination.hasMore` is true. `clientRequestId` filters by the original request UUID; preserve that filter on later pages. Invalid, foreign, or mismatched cursors return 400.

Public shared-Study views exclude guest conversations and retired subthreads, including direct answer reads. Shared transcript metadata contains presentation and research fields; model-connection settings, internal guided context, generation diagnostics, and language-review drafts are omitted. Answers and citations remain available for public messages. Authenticated workspace readers still require Study access.

Shared transcript endpoints (`/api/app/studies/shared/{shareId}` and `/api/studies/shared/{shareId}`) page messages with `limit` (default 100, maximum 500) and `after`. Follow `data.pagination.nextCursor` while `data.pagination.hasMore` is true, even when a page contains no public messages. Each page checks current sharing permissions; invalid or foreign cursors return 400. Transcripts contain aggregate results; load respondent answers separately. The shared page follows all pages automatically.

## Create a Study

```bash
curl -X POST "https://api.getminds.ai/v1/studies" \
  -H "Authorization: Bearer minds_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Launch positioning",
    "audienceIds": ["audience-id-1", "audience-id-2"]
  }'
```

Canonical Study responses use `studyMode: "study"` and an `audiences` collection. Inline `audienceConfigs` can create and attach Audiences atomically; if any inline Audience fails, the entire composite creation is rolled back.

## Run questions as a cohesive block

For a broader objective or more than one question, use the guided lifecycle. Do not submit a questionnaire one question at a time.

1. `POST /studies/{studyId}/research-plans/preview` creates or revises a versioned plan containing the cohesive question modules.
2. Review the exact plan and answer any confirmation questions.
3. `POST /studies/{studyId}/research-runs` confirms one precise revision and queues all questions together.
4. `GET /studies/{studyId}/research-runs/{runId}` returns durable progress, response contracts, artifacts, calculations, and next actions.

The planner may use multiple named modules or sections when that improves the research design. Each module should contain related questions with a clear purpose; the executor preserves the reviewed order and shared context.

Use `POST /studies/{studyId}/ask` only for a genuinely standalone question. Use `POST /studies/{studyId}/runs` for a durable direct run that needs polling, replay, cancellation, or knowledge-only source policy.

A research run can finish collecting with `status: "partial"` when a question or respondent answer is missing. Stop polling this terminal state and explain the gap; do not describe it as complete. `progress.pct` measures settled questions, not respondent coverage. New question outputs retain `outputData.responseCoverage` with `expected`, `received`, and missing Audience/Mind memberships. `progress.partial` counts questions with recorded incomplete coverage. Missing coverage on older artifacts is unknown, not proof that everyone answered. Keep valid raw answers and their outcomes unchanged; review missing work before starting a separate retry.

### Observe pending plan preparation

Send a stable `Idempotency-Key` (an opaque nonblank string of at most 200 characters) when preparing a plan. `POST /studies/{studyId}/research-plans/preview` can return HTTP `202` with an owned preparation receipt:

```json
{
  "data": {
    "status": "pending",
    "accepted": true,
    "operationId": "87b41085-4a73-4b04-88af-d9a2370ecdee",
    "requestId": "plan-reusable-cup-01"
  }
}
```

Repeat the same POST with the exact original JSON body and the same `Idempotency-Key`. Keep the returned `requestId` in `x-minds-study-plan-request-id`; the returned `operationId` can also be sent in `x-minds-study-plan-operation-id`. The operation ID is an owned observation hint, not permission to access another operation. `stage` may be included as optional progress. Reconnecting or aborting observation does not cancel accepted preparation. Do not use a fresh key merely to check progress.

`accepted: true` confirms durable preparation, not a completed plan or a started research run. Continue observing until HTTP `200` returns the original completed preview DTO. Review that exact saved revision before confirming a research run. Existing authorization, malformed-input and revision-control 4xx responses remain authoritative.

`loadLatest` and completed-plan reads return `200`. Fixed instruments in `questions` and deterministic `edits` keep their existing no-planner meaning; they can still return `202` while required downstream source or preparation verification finishes.

## Questionnaire runs: order, skip logic, and consistency

### Each Mind answers as one respondent

A confirmed run (`POST /studies/{studyId}/research-runs`) is answered the way one respondent answers a questionnaire. Items run in the reviewed order. Every Mind answers an item in parallel with the other Minds, and each Mind sees its own earlier answers in this run: never another Mind's answers and never an aggregate. A later item can therefore build on an earlier one, and a Mind stays consistent with what it already said about itself. No request field turns this off.

Items in a confirmed run are not independent measurements. Order effects can arise as they do in a fielded survey, so question order is part of the design. When the design calls for it, control order effects by rotating item order across separate runs; a separate run does not see the answers of another run. A direct run (`POST /studies/{studyId}/runs`) is different: its `historyMode` defaults to `none`, which treats every question as an independent measurement.

### Skip logic with askIf

Give a question `askIf` to ask it only of Minds whose own answer to an earlier choice question matched. Use this instead of respondent-visible wording such as "if not, answer N/A". Set it on a fixed-instrument item in `questions[]` of `POST /studies/{studyId}/research-plans/preview`, or on an item in `edits.questions[]` when revising a saved draft. Edits are authoritative: an edited item sent without `askIf` has no condition. The saved plan snapshot keeps the condition on its question.

```json
{
  "questions": [
    {
      "id": "owns_car",
      "text": "Do you own a car?",
      "response": { "type": "categorical", "categoricalOptions": ["Yes", "No"] }
    },
    {
      "id": "car_brand",
      "text": "Which brand is your main car?",
      "response": { "type": "qualitative" },
      "askIf": { "questionId": "owns_car", "answerIn": ["Yes"] }
    }
  ]
}
```

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        questionId
      </code>
    </td>
    
    <td>
      ID of an earlier question in the same plan (up to 80 characters, normalized to the saved ID slug). It must be a <code>
        categorical
      </code>
      
       or <code>
        multiselect
      </code>
      
       question.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        answerIn
      </code>
    </td>
    
    <td>
      Ask when the Mind's answer includes any of these options.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        answerNotIn
      </code>
    </td>
    
    <td>
      Ask when the Mind's answer includes none of these options.
    </td>
  </tr>
</tbody>
</table>

Give exactly one of `answerIn` or `answerNotIn`, with 1 to 100 of the earlier question's option labels exactly as the plan stores them. Labels are matched as written, not interpreted, so a condition means the same thing in every language. An empty multiselect selection is an answer: it matches no `answerIn`, so `answerNotIn` items are asked. A Mind with no answer to the earlier question (it was not asked it, or its answer failed) is not asked. If the earlier question recorded no answers at all, every Mind is asked and the item's `routing.gateUnavailable` is `true`.

A condition that cannot run is rejected with `400`, naming the question: both or neither of `answerIn`/`answerNotIn`, an unknown `questionId`, a question that is not earlier, a question that is not `categorical` or `multiselect`, or an option the earlier question does not offer. A fixed instrument returns `Fixed instrument is not executable as given: …`; an edit returns the issues directly; confirming a run revalidates the plan and returns `Invalid question conditions: …`.

### Items and answers not asked

Run status (`GET /studies/{studyId}/research-runs/{runId}`) reports routing on each routed item's response artifact: `routing` carries `questionId`, `gateQuestionId`, the `answerIn` or `answerNotIn` condition, `askedCount`, `notAskedCount` and `notAskedMindIds`. An item that no Mind was asked settles as an artifact with `kind: "not_asked"` and no `outputData`. It is finished, not failed, and it does not make the run `partial`.

Minds that were not asked are outside the item's results. The item's answers, response counts, percentages, distributions and `responseCoverage.expected` cover the asked Minds only, so a share on a routed item is a share of those asked. Not asked is different from did not answer: a Mind listed in `notAskedMindIds` has no answer to read, while a missing asked answer shows up in `responseCoverage`. Individual-answer reads (`GET /studies/{studyId}/messages/{messageId}/answers`) return only the asked Minds' answers; a `not_asked` item has no answer data and returns `404`.

### Answer consistency flags

After a confirmed run of two or more items finishes, each Mind's answers are checked against its own other answers in the run. The check never changes an answer; it only records flags beside the answers for review. It runs after the run's summary is written, so it never delays the summary and can arrive shortly after `status` becomes `completed`. It does not run for a cancelled or plan-limited run, and a failed or cut-short check never fails the run.

The run status then carries `answerConsistency`:

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      <code>
        checked
      </code>
      
      ; <code>
        incomplete
      </code>
      
       when the time budget or a cancellation stopped the check before every Mind was checked; <code>
        failed
      </code>
      
       when the check failed or no flag could be saved
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        checkedMinds
      </code>
    </td>
    
    <td>
      Minds whose answers were checked
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        flaggedMinds
      </code>
    </td>
    
    <td>
      Minds with at least one flag
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        flagCount
      </code>
    </td>
    
    <td>
      Contradicting answer pairs found
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        uncheckedMinds
      </code>
    </td>
    
    <td>
      Minds whose check could not run; their answers are unflagged, not cleared
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        unsavedItems
      </code>
    </td>
    
    <td>
      Present only when some flags could not be saved: the run item indexes whose flags are missing; other items keep theirs
    </td>
  </tr>
</tbody>
</table>

A flagged response artifact carries `answerConsistency.flags`, one entry per contradiction involving that item: `mindId`, `withItem` (the run item index of the other answer) and `reason`. Each pair is flagged on both items. Only Minds that answered at least two items are checked. Treat a flag as a prompt to review both answers, not as proof that either is wrong.

## Core endpoints

<table>
<thead>
  <tr>
    <th>
      Method
    </th>
    
    <th>
      Path
    </th>
    
    <th>
      Purpose
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /studies
      </code>
    </td>
    
    <td>
      List Studies
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /studies
      </code>
    </td>
    
    <td>
      Create a Study from Audiences
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /studies/{studyId}
      </code>
    </td>
    
    <td>
      Read Study composition and history
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DELETE
      </code>
    </td>
    
    <td>
      <code>
        /studies/{studyId}
      </code>
    </td>
    
    <td>
      Delete a confirmed Study
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /studies/{studyId}/ask
      </code>
    </td>
    
    <td>
      Ask one standalone question
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /studies/{studyId}/runs
      </code>
    </td>
    
    <td>
      Start a durable direct run
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /studies/{studyId}/analytics
      </code>
    </td>
    
    <td>
      Compute categorical, scale, multiselect, and qualitative analytics
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /studies/{studyId}/summary
      </code>
    </td>
    
    <td>
      Read the persisted semantic summary
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /studies/{studyId}/summary
      </code>
    </td>
    
    <td>
      Generate or refresh the semantic summary
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PUT
      </code>
    </td>
    
    <td>
      <code>
        /studies/{studyId}/sharing
      </code>
    </td>
    
    <td>
      Change public link sharing after explicit approval
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /studies/{studyId}/duplicate
      </code>
    </td>
    
    <td>
      Copy the Study with all of its results over the same Audiences
    </td>
  </tr>
</tbody>
</table>

To have your team's own model write the Mind answers, pass `modelConnection: { "id", "revision" }` on `POST /studies/{studyId}/runs` or `POST /studies/{studyId}/research-runs` (see [your own model](/docs/api/chat#your-own-model)). The connection needs text and structured output. Supporting analysis still uses Minds models, and each answer still counts toward your plan's Study responses. `POST /studies/{studyId}/ask` does not accept a connection.

For `POST /studies/{studyId}/summary`, send a JSON object up to 4 KiB with only `force` (boolean; default `false`) and `length` (`short`, `standard`, or `detailed`; default `standard`). Invalid JSON, types, options, or extra fields return `400`; larger bodies return `413`. Summary reads and refreshes require Study access and return `Cache-Control: private, no-store`. A refresh reuses the saved summary only when its length, covered message count and final covered message still match.

Summary reads return a monotonic `revision` and an `isGenerating` flag alongside `summary`; generation responses also include `revision`. A generation superseded by a newer generation or deletion returns `409` and cannot overwrite the newer state. Reload the summary before retrying.

## Duplicate a Study

`POST /studies/{studyId}/duplicate` creates a private copy of a Study you can open, over the same Audiences and Minds. The copy keeps every question, answer, chart and heatmap, the semantic summary and finished runs; a schedule is copied paused. Sharing, exports and shared-link guest threads are not copied. Audiences are referenced, not copied: duplicate an Audience separately to copy its Minds.

The optional body takes `name` (default `<name> (copy)`) and `idempotencyKey`; the `Idempotency-Key` header wins. A repeated key returns the same copy with `200`; a new copy returns `201` with the `StudyCreated` shape. Attached Audiences and your plan are checked as for `POST /studies`. A Study with a live run returns `409` with `STUDY_RUN_IN_PROGRESS`; retry when it has finished, or cancel the run first.

## Exports

Start an artifact with `POST /studies/{studyId}/export`, poll `/export-status`, then download from `/export-download`. Supported outputs include the full report, executive summary, raw data, and presentation formats exposed by the export endpoint.

## Compatibility

The former `/panels` routes remain callable during the migration window. They return `Deprecation: true` and a `Link` header naming the canonical `/studies` successor. Deprecated routes retain legacy wire fields such as `groupIds`, `groups`, and `flowMode: "panel"`; new integrations should use `audienceIds`, `audiences`, and `studyMode: "study"`.

See the exhaustive [v1 endpoint reference](/docs/api/reference), the [agent integration guide](/docs/api/agents), and the live [OpenAPI document](/docs/api/openapi).

## n8n workflows

The [n8n Study workflow guide](/guide/integration-n8n) shows how to pass the ID returned by Create into Preview Research Plan, then retrieve the same Study later. Version 0.1.0 prepares a draft; approving and starting its research is a separate step in Minds.
