---
title: "Audiences API"
description: "Create, ground, validate, manage, and export reusable Audiences of Minds, then attach them to cohesive research Studies through the Minds v1 API."
---

# Audiences API

An **Audience** is a reusable collection of Minds. Build it once, ground it with research sources when needed, and attach it to one or more Studies.

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

## Create an Audience

```bash
curl -X POST "https://api.getminds.ai/v1/audiences" \
  -H "Authorization: Bearer minds_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "German SaaS buyers",
    "mindIds": ["mind-id-1", "mind-id-2"]
  }'
```

Audiences are private by default. Set `isLinkSharingEnabled: true` only when the user explicitly requests a public link.

## Sharing and access

An Audience carries its own sharing settings, editable through `PUT /audiences/{audienceId}`:

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

<tbody>
  <tr>
    <td>
      <code>
        isSharedWithTeam
      </code>
    </td>
    
    <td>
      Every member of the owner's team can open the Audience and read and chat with every Mind in it, even when those Minds are not shared individually.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isPublic
      </code>
    </td>
    
    <td>
      Anyone can open the Audience and its Minds (marketplace listing).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        isLinkSharingEnabled
      </code>
    </td>
    
    <td>
      Anyone with the link can open the Audience. Set at creation; not editable through <code>
        PUT
      </code>
      
      .
    </td>
  </tr>
</tbody>
</table>

Explicit Audience members get the same read access as team members. Access gained through an Audience is read-and-chat only: it never grants edit or delete rights on the Minds inside. This is how curated Minds are typically delivered to a partner team — share one Audience with the team instead of sharing each Mind, and let the team discover the Minds through `GET /minds/library`.

## 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>
        /audiences
      </code>
    </td>
    
    <td>
      List visible Audiences
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /audiences
      </code>
    </td>
    
    <td>
      Create an Audience from existing Mind IDs
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}
      </code>
    </td>
    
    <td>
      Read members, access, grounding, and Formations
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        PUT
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}
      </code>
    </td>
    
    <td>
      Update supported fields and sharing settings
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DELETE
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}
      </code>
    </td>
    
    <td>
      Delete a confirmed Audience
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/members
      </code>
    </td>
    
    <td>
      Add a Mind
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        DELETE
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/members/{mindId}
      </code>
    </td>
    
    <td>
      Remove a Mind
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/progress
      </code>
    </td>
    
    <td>
      Read creation, training, and background research progress
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/recalibrate
      </code>
    </td>
    
    <td>
      Refresh grounding and recalibrate the members; <code>
        deepen
      </code>
      
       researches what is still missing
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/recalibrations/{operationId}
      </code>
    </td>
    
    <td>
      <code>
        operation.statusUrl
      </code>
      
      : <code>
        status
      </code>
      
      , <code>
        result
      </code>
      
      , <code>
        error
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/duplicate
      </code>
    </td>
    
    <td>
      Copy the Audience with new, independent copies of its Minds and research
    </td>
  </tr>
</tbody>
</table>

Only the owner can add members with `POST /audiences/{audienceId}/members`. Send `mindId` or `mindIds`; if both are supplied, `mindId` wins. UUID case variants are deduplicated. The response `data.added` counts distinct requested IDs, including existing members. Ownership, Mind access, and capacity are checked in one transaction, so competing additions cannot both take the last slot. A capacity refusal (`403`) or inaccessible Mind (`404`) adds nothing. Responses are private and non-cacheable; unexpected storage or plan-resolution failures return a generic `500`.

Member-add requests require a JSON object of at most 64 KiB, including ignored fields. When using `mindIds`, supply at most 1,000 IDs before deduplication; send larger additions in separate requests. Invalid JSON or too many IDs returns `400`; an oversized body returns `413`. These limits also apply to legacy Group URLs.

`DELETE /audiences/{audienceId}/members/{mindId}` is owner-only and returns an empty `204`, including when membership is already absent. It atomically removes that membership and the Mind’s subgroup assignments on this Audience; it does not delete the Mind or affect other Audiences. Ownership is checked as part of the write, and a failed transaction preserves both membership and assignments. Missing or unowned Audiences return `404`; unexpected storage failures return a generic `500`. All responses are private and non-cacheable.

## Create a grounded Audience from a brief

`POST /audiences/from-brief` accepts a research brief plus source files or URLs and creates a grounded synthetic Audience. PDF, DOCX, CSV, XLS, and XLSX inputs enter the same classification and extraction pipeline used by the webapp. The REST `text` field and MCP `brief` (or legacy `text` alias) accept up to 200,000 characters after trimming whitespace.

For respondent spreadsheets, preview segmentation first:

```text
POST /audiences/from-brief/segmentation-preview
```

The preview distinguishes completed respondent data from screeners and questionnaire programming grids. It excludes identifiers, weights, free text, and derived statistics from representative cohort allocation by default, and preserves the valid base for conditional or multi-select questions.

For the complete profile review, send `groundingPreview: true` and `cohortAllocation: { includeProfilesInResponse: true }` to `POST /audiences/from-brief`. Inspect the proposed profiles, applied composition, source findings, and intended study use. To create that reviewed Audience, resend the same inputs and `memberCount`, omit `groundingPreview`, and return `reviewedGroundingJson` and `reviewedGroundingSha256` exactly as received. An altered snapshot is rejected.

Fresh public API previews and creation return **HTTP 202** with `data.jobId` and a status URL. This also applies when `async` is omitted or `false`: research runs in a durable job rather than holding the request open through the edge timeout. Poll `GET /audiences/from-brief/jobs/{jobId}` until `status` is `completed` or `failed`; a completed preview contains the same reviewed grounding and allocation result. The MCP tool uses the same durable operations. If it returns a pending operation, call it again with `operationId` to retrieve the result without starting another preview.

To change existing targets without researching again, send the unchanged `reviewedGroundingJson` and `reviewedGroundingSha256`, `groundingPreview: true`, and revised `cohortAllocation.targetOverrides` or `excludeDistributions`. This saved-snapshot preview can return **HTTP 200** directly. Set `async: true` or `Prefer: respond-async` to queue it. Handle both 200 and 202; do not treat a 202 response as a completed preview.

Named multi-select options remain independent incidences. Supplied percentages retain their provenance; any modelled shares are labelled assumptions. An applicable questionnaire rule such as “channel A or channel B” is checked against each allocated profile, not just a separate 100% eligibility card. Unresolved rules or quotas that cannot be reconciled with every required rule return **422** before creation. Review the reported rule and rework the preview. A zero marginal allocation error alone does not prove eligibility.

Requested research categories are searched alongside population evidence. A broader consumer source remains a proxy when the audience is a particular profession; requesting a category does not make that source an observation of the profession.

## Two-phase creation

A new Audience is usable within seconds. Its first phase stands on what is already known: verified surveys of these people from the evidence pool, the requester's own files, and a stated assumption for every standard dimension that nothing covers yet. Age and gender are never assumed; they stay missing until evidence exists.

Web research continues in the background and is applied automatically. Sourced evidence replaces assumptions, earlier charts are kept, and the Minds are recalibrated between runs, never during a running Study run or validation. A run keeps the Minds it started with, and each run records which evidence version it answered from. Automatic validation waits until the background research is done. Background research is never a reason to wait before running a Study.

The `POST /audiences/from-brief` result includes `research`, with `phase: "researching"` when background research was started. `GET /audiences/{audienceId}` returns `research` and `grounding.coverage`. `GET /audiences/{audienceId}/progress` returns `research` and `coverage`; `readiness` is unchanged. All three fields are additive.

### Research state

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

<tbody>
  <tr>
    <td>
      <code>
        phase
      </code>
    </td>
    
    <td>
      <code>
        researching
      </code>
      
      : usable now, background research running or waiting for a run to finish before updating the Minds. <code>
        researched
      </code>
      
      : done, also for Audiences whose research finished at creation. <code>
        usable
      </code>
      
      : background research failed and the Audience stays on its first phase
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      <code>
        queued
      </code>
      
      , <code>
        running
      </code>
      
      , <code>
        completed
      </code>
      
      , <code>
        failed
      </code>
      
      , or <code>
        null
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        stage
      </code>
    </td>
    
    <td>
      <code>
        researching
      </code>
      
      , <code>
        waiting_for_runs
      </code>
      
      , <code>
        recalibrating
      </code>
      
      , <code>
        done
      </code>
      
      , or <code>
        null
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        progress
      </code>
    </td>
    
    <td>
      0 to 100
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        startedAt
      </code>
      
      , <code>
        completedAt
      </code>
    </td>
    
    <td>
      When the background research started and finished
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        outcome
      </code>
    </td>
    
    <td>
      <code>
        null
      </code>
      
       until the research ends; then <code>
        chartsAdded
      </code>
      
      , <code>
        sourcesAdded
      </code>
      
      , <code>
        dimensionsImproved
      </code>
      
       (<code>
        [{ dimension, from, to }]
      </code>
      
      ), <code>
        membersRecalibrated
      </code>
      
      , <code>
        coverage
      </code>
      
       counts, <code>
        applied
      </code>
      
      , and <code>
        skippedReason
      </code>
      
       when nothing was applied
    </td>
  </tr>
</tbody>
</table>

### Coverage

`coverage` is `{ dimensions: [{ dimension, status, distribution? }], counts: { sourced, proxy, assumed, missing } }`, or `null`. Each dimension has one status:

<table>
<thead>
  <tr>
    <th>
      <code>
        status
      </code>
    </th>
    
    <th>
      Meaning
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        sourced
      </code>
    </td>
    
    <td>
      Published evidence about these people
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        proxy
      </code>
    </td>
    
    <td>
      Evidence about a wider or related population
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        assumed
      </code>
    </td>
    
    <td>
      The model's stated assumption, until research finds evidence
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        missing
      </code>
    </td>
    
    <td>
      Not covered
    </td>
  </tr>
</tbody>
</table>

## Recalibrate and go deeper

`POST /audiences/{audienceId}/recalibrate` refreshes the stored grounding and recalibrates the members. To go deeper, send `deepen`:

Without `deepen`, the request returns `202` with `data.operation` immediately. Research and roster changes run in the background. Poll `GET /audiences/{audienceId}/recalibrations/{operationId}` at `operation.statusUrl`, or read `calibration.operation` on the Audience. Its `status` is `queued`, `running`, `completed`, or `failed`; `result` retains the grounding and member counts, and `error` retains a safe terminal error or refusal. Member training continues separately after the roster update. Send a stable `requestKey` (1–128 characters) or `Idempotency-Key` header for retries: identical input replays the same operation even after completion, with `200` for terminal receipts; changed input with that key returns `409`.

```json
{ "deepen": { "topics": ["streaming subscriptions", "price sensitivity"] } }
```

Go deeper researches what the Audience still lacks, every standard dimension its coverage reports as `missing` or `assumed`, plus the given `topics` (optional, at most 8, each up to 120 characters). The findings are added to the saved grounding: stronger evidence replaces weaker evidence for the same dimension, and every other chart stays. The members are then recalibrated like in any recalibration.

`deepen` cannot be combined with `distributions`, `query`, or `requestedDistribution` (`400`). While the Audience's background research is still running, the request is refused with `409` and `AUDIENCE_RESEARCH_RUNNING`.

## Duplicate an Audience

`POST /audiences/{audienceId}/duplicate` creates a private copy of an Audience you can edit. Every member Mind is copied as a new, independent Mind with its profile, knowledge items, patterns and embeddings, together with the Audience's grounding and snapshot history, sources, ready Formations and finished validations. Sharing, public links, the marketplace listing and running jobs are not copied, and changing or deleting the copy never affects the original.

The optional body `{ "name": "…" }` names the copy (default `<name> (copy)`). Send `x-minds-idempotency-key` to make retries safe: a repeated key returns the same copy with `200`; a new copy returns `201` with the `SavedAudienceResponse` shape. The copied Minds count toward your plan's Mind allowance (`403` with `PLAN_LIMIT` when they do not fit). Callers who can view but not edit the Audience get `403`. While its Minds are still training or its research is still running, the request returns `409` with `AUDIENCE_BUILD_IN_PROGRESS`; retry once the Audience is ready.

## Formations

On the first page (`offset: 0`), recovery checks at most 100 pending builds, oldest first. One failed attempt does not stop attempts for the other selected builds or prevent the list from being returned. Check build status and reopen the first page to retry pending work.

Formation lists accept `limit` (default and maximum 100) and `offset` (default 0). The response includes `pagination: { total, limit, offset, hasMore, nextOffset }`; request `nextOffset` while `hasMore` is true. Auto-dimension winners are selected before paging, so totals count the visible de-duplicated list. Ordering is newest first, with ID breaking timestamp ties. Each page uses one database snapshot, but changes between requests can shift offsets; restart from 0 if the list changes. Only offset 0 seeds defaults and checks stalled builds.

A **Formation** is a persisted segmentation inside one Audience. Use `/audiences/{audienceId}/formations` to list, create, preview, recompute, or delete Formations without creating another Audience.

For previews, `Accept` selects UTF-8 JSON or NDJSON using quality weights, wildcards and media parameters. `q=0` rejects a format, and an explicit media range overrides a wildcard. Missing `Accept` defaults to NDJSON. If neither representation is acceptable, the endpoint returns a private `406` before generation. MCP requests `application/json` explicitly.

Before saving a successful build, the worker verifies the complete Audience membership against the population it classified. A changed population requests a retry instead of publishing an incomplete split. The final save briefly serializes member insertion; model generation runs outside that transaction. Empty-population and unsplittable-population failures require the same snapshot check; over-cap failures recheck whether the current population still exceeds the limit.

Queued builds stay `building` after an execution error while configured attempts remain; exhausted attempts record a safe `failed` status. Without Redis, the synchronous fallback makes one attempt and records a safe failure if it cannot finish.

A successful incremental classification replaces that Mind’s previous assignments within the same Formation, including previous overlap. A classifier outage preserves existing assignments. Numeric-balanced splits still require an explicit recompute to include added Minds.

Preview accepts an optional JSON body of at most 64 KiB. `userInput` is optional and limited to 2000 characters after trimming; omitted or blank input uses the default split request. Omit `priorHypothesis` or use `null` for a new proposal; supplied proposals must satisfy the creation contract. Invalid input returns `400`, oversized bodies `413`, and storage failures a generic `500` before streaming starts. Both JSON and NDJSON responses are private and non-cacheable. Model failures or invalid generated proposals return `500` in JSON mode, or an `error` event followed by `done` once NDJSON starts. Successful hypotheses contain only creation-compatible fields and fit the creation request limit. Disconnecting or cancelling the stream signals cancellation to model generation.

Formation detail reads are private and non-cacheable. They expose only documented proposal, progress, subgroup, and membership fields; stored diagnostics are omitted. Unknown failure text becomes a safe retry message. Invalid legacy proposals appear as `{}`, malformed building progress as `null`, and ready Formations always have `progress: null`. Invalid persisted detail metadata or storage failures return a generic `500`. During recompute, subgroups can still show the previous completed buckets until replacement succeeds; their presence does not mean the rebuild is ready.

Creating a Formation accepts a JSON body of at most 64 KiB. Supply a non-empty `intent` and 2–15 subgroups, each with non-empty `id`, `label`, and `definition`. Text is trimmed; subgroup IDs and labels must be unique after trimming. The IDs `unanswered`, `__unanswered`, and `__other` are reserved. Optional `name` must be a string; an empty name uses the intent. Invalid input returns `400`, oversized bodies return `413`, and unexpected storage failures return a generic `500`. Creation responses are private and non-cacheable. User-created splits need at least two populated primary buckets to become `ready`; otherwise they become `failed`. A Formation deleted before acknowledgement returns `404`.

Deletion and recomputation use private, non-cacheable responses. Missing or non-mutable Formations return `404`; unexpected storage or queue failures return a generic `500`. If a recompute response is uncertain, check the saved status before retrying.

## Validate an Audience against real surveys

Audience Validation checks an Audience against real, published surveys. It finds surveys whose respondents best match the Audience, asks the Audience's Minds the same questions, and scores how close their answers are to the published answers. The same check runs from the Audience's **Validation** tab in the app and through the MCP tools `validate_audience` and `get_audience_validation`. The endpoints below use the same authentication as the rest of the API.

One validation is a *batch*: a single run that can ask several surveys at once.

### Validation endpoints

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

<tbody>
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/validations
      </code>
    </td>
    
    <td>
      Start a validation
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/validations
      </code>
    </td>
    
    <td>
      List the Audience's validations with its overall validity, included validations, responses, and Mind readiness
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/validations/batches/{batchId}
      </code>
    </td>
    
    <td>
      Read one validation: status, progress, survey scores, question selection, and failure
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/validations/{validationId}
      </code>
    </td>
    
    <td>
      Read one survey's scoring, with every KPI and question
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        POST
      </code>
    </td>
    
    <td>
      <code>
        /audiences/{audienceId}/validations/batches/{batchId}/cancel
      </code>
    </td>
    
    <td>
      Stop a validation while <code>
        canCancel
      </code>
      
       is true
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        GET
      </code>
    </td>
    
    <td>
      <code>
        /audiences/validation-benchmarks?audienceId={audienceId}
      </code>
    </td>
    
    <td>
      Preview the surveys that would be used and how they fit the Audience
    </td>
  </tr>
</tbody>
</table>

### Start a validation

You can validate any Audience you can edit, at any time and as often as you like. The Audience needs at least 10 ready Minds.

```bash
curl -X POST "https://api.getminds.ai/v1/audiences/{audienceId}/validations" \
  -H "Authorization: Bearer minds_your_api_key" \
  -H "Content-Type: application/json" \
  -d '{
    "findSurveys": true,
    "requestKey": "3f6c1e0a-6c1b-4a53-9a3e-2d1f0b7c9e11"
  }'
```

Choose what to validate against with one of these body fields:

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      Validates against
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        findSurveys: true
      </code>
    </td>
    
    <td>
      The published surveys that best fit the Audience, found by Minds
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        benchmarkIds
      </code>
    </td>
    
    <td>
      1 to 5 listed surveys: curated ids, <code>
        discovered:<id>
      </code>
      
      , or <code>
        saved:<validationId>
      </code>
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        files
      </code>
    </td>
    
    <td>
      Your own survey files: the questionnaire and its results as PDF, XLS/XLSX/CSV, DOCX, or TXT
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        dataset
      </code>
    </td>
    
    <td>
      A respondent-level dataset (Pro plan)
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        run: { studyId, runId }
      </code>
    </td>
    
    <td>
      A Study run you already did. Scoring it is free and asks nothing new
    </td>
  </tr>
</tbody>
</table>

Send an optional `requestKey`, or an `Idempotency-Key` header, so that a retried request returns the same batch instead of starting another one.

A successful start returns **HTTP 202** with:

- `data`: the validation batch
- `included`: your included validations, as `perMonth`, `used`, `remaining`, `renewsAt`, and `thisRun`
- `responses`: the synthetic responses a validation beyond the included ones spends

An Audience with fewer than 10 ready Minds is refused with `422` and `AUDIENCE_TOO_SMALL`. An Audience larger than your plan allows in a Study is refused with the standard [plan-limit error](/docs/api/errors).

### Follow a validation

All chosen surveys are asked in one run, in one Study named `Validation run: <Audience name>`. That Study is the platform's working copy: it does not appear in your Study list and does not count toward Study limits. Validation runs queue behind Studies that people start, so they never slow your own research. A run usually takes 10 to 60 minutes.

Poll `GET /audiences/{audienceId}/validations/batches/{batchId}` until `status` is `completed`, `failed`, or `cancelled`. The statuses are `queued`, `preparing`, `running`, `completed`, `failed`, and `cancelled`; while the batch is running, `progress` reports how far it has got.

A validation can be cancelled before any survey is asked. Call `POST /audiences/{audienceId}/validations/batches/{batchId}/cancel` while the batch reports `canCancel: true`.

### Read the results

A batch contains:

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

<tbody>
  <tr>
    <td>
      <code>
        surveys
      </code>
    </td>
    
    <td>
      Each survey with its <code>
        score
      </code>
      
      , its 95% <code>
        range
      </code>
      
       when available, its <code>
        relation
      </code>
      
       to the Audience, its <code>
        sourceUrl
      </code>
      
      , and its question counts
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        notComparable
      </code>
    </td>
    
    <td>
      Surveys that could not be compared with the Audience
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        combined
      </code>
    </td>
    
    <td>
      The run's combined score, with its <code>
        basis
      </code>
      
      : whose answers it compares the Audience with
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        outcome
      </code>
    </td>
    
    <td>
      Details of the run, including <code>
        questionSelection
      </code>
      
      : the questions judged, kept, and left out, each left-out question with its reason
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        failure
      </code>
    </td>
    
    <td>
      The failure code when the batch ends without a result
    </td>
  </tr>
</tbody>
</table>

The score is 100 minus the mean absolute difference, in percentage points, between the Minds' answers and the published answer shares; 100 means identical answers. When available, the reported 95% range is calculated from the modelled sampling of Minds and survey respondents, widened by the measured population adjustment. It does not establish representativeness or account for every survey-design effect.

When a scored question has no stated respondent count (`baseSource: "assumed"`), its point comparison and existing half weight remain usable, but its range and noise-floor estimate are `null`. The headline range, band bounds and confidence level, and affected KPI intervals are also `null`. A combined or overall score retains all eligible point comparisons, but its range is `null` if any contributing survey lacks a range. Known-base questions and KPI families with no unknown-base contributor can still carry ranges.

This rule applies when reading older saved results as well as newly scored results. A legacy placeholder such as `n: 100` on an assumed-base question is not an observed respondent count and must not be used to reconstruct uncertainty. Display the point score with an unavailable-uncertainty explanation; never turn a missing range into a zero-width range or average only the ranges that are present.

`relation` states who the survey asked relative to the people who define the Audience: the Audience's own people (`same`), a part of them, a neighbouring population (including the same kind of people surveyed in another country), or the wider population they belong to.

Each left-out question carries one of these reasons:

- `not_applicable`: the Audience's people cannot answer the question, for example because it presupposes something they don't have.
- `answer_differs`: the published answers are not a fair target for this Audience, because the survey's respondents differ in a way that would change the answer.
- `asked_in_another_study`: the same question is already asked from a better-fitting survey.

`GET /audiences/{audienceId}/validations` returns every validation of the Audience in `batches`, newest first, together with `overall`, `included`, `responses`, and `minds` (`ready` and `minimum`). `overall` is the Audience's validity across whole-Audience surveys it was checked against: each survey counts once, by its latest check, weighted by the number of questions scored. `overall` carries the same `basis` as `combined`. For one survey's full scoring, with every KPI and question, read `GET /audiences/{audienceId}/validations/{validationId}`.

### How surveys and questions are chosen

Surveys are ranked by who answered them relative to the people who define the Audience. Surveys of the Audience's own people come first; evidence about the wider population supports a result but never outweighs more specific surveys.

### What a score is compared with

`combined` and `overall` carry `basis`, which says whose answers the score compares the Audience with:

<table>
<thead>
  <tr>
    <th>
      <code>
        basis.kind
      </code>
    </th>
    
    <th>
      When
    </th>
    
    <th>
      How to read it
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        audience
      </code>
    </td>
    
    <td>
      At least 8 of the questions asked came from surveys of the Audience's own people or a part of them
    </td>
    
    <td>
      A comparison with these people. Wider-population surveys may add questions, never more than the specific surveys hold
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        wider_population
      </code>
    </td>
    
    <td>
      Fewer did, but together with surveys of the wider population they belong to (or of the same kind of people in another place) at least 8 questions were asked
    </td>
    
    <td>
      A comparison with that wider population, not with the Audience's own people. <code>
        confidence
      </code>
      
       is <code>
        lower
      </code>
      
      : these comparisons have tended to score higher than comparisons with an Audience's own people
    </td>
  </tr>
</tbody>
</table>

`audienceQuestions` counts the questions from surveys of the Audience's own people (0 means none was found), `widerPopulationQuestions` the questions from wider-population surveys, and `comparedWith` names who those surveys asked. The two kinds are never mixed in `overall`: while any validation of the Audience's own people exists, `overall` rests on those alone. Upload a survey of the Audience's own people to turn a wider-population comparison into a direct one.

Surveys of a neighbouring population never carry a score. A validation that would rest on them fails with `NOT_ENOUGH_SPECIFIC_EVIDENCE` before anything is asked or spent.

Every question is then checked for this Audience, one by one, before any Mind answers. A question is left out if the Audience's people cannot answer it, if the published answers are not a fair target for this Audience, or if the same question is already asked from a better-fitting survey. Where a survey publishes a question's answers by age or region, the target is narrowed to the Audience's own mix. Every left-out question is recorded with its reason.

One validation asks at most 60 questions, from at most 12 published studies, and at most 30,000 answers in total. It needs at least 8 fitting questions; below that, nothing is asked and nothing is spent.

### Included validations and costs

Pay as you go includes no free validation runs. A validation consumes responses like a Study: one response per Mind per question. Existing paid subscriptions and Enterprise contracts may include validation runs under their own terms; read the authenticated allowance instead of assuming a quota from a plan name. An included run counts once, however many surveys it asks. A run that ends without a score and without spending any included answers gives its slot back.

Without an included run, automatically chosen surveys are fitted to the responses you have left. Scoring an existing Study run asks no new questions and is free.

A new private Audience is validated automatically, once, as soon as all of its Minds have finished training, if it has at least 10 Minds and its owner has an included validation left that month. Automatic validations never spend synthetic responses and send no email. Audiences that existed before automatic validation shipped are validated only when someone asks.

A validation that someone started and that runs longer than a few minutes sends that person an email when it finishes. Its **View the validation** button opens the Audience on its Validation tab.

### Validation failure codes

A batch that ends without a result names the reason in `failure`, for example `NOT_ENOUGH_QUESTIONS` when fewer than 8 questions fit the Audience, `NO_FITTING_SURVEY`, or `SURVEY_FILES_UNREADABLE`. [Audience validation failures](/docs/api/errors) lists all twelve codes and what each one means.

## Exports

Start a branded Audience brief with `POST /audiences/{audienceId}/export`, poll `/export-status`, then download from `/export-download` when complete.

## Compatibility

The former `/groups` routes remain callable during the migration window. They return `Deprecation: true` and a `Link` header naming the canonical `/audiences` successor. New integrations should use Audience paths and terminology exclusively.

See the exhaustive [v1 endpoint reference](/docs/api/reference) and the live [OpenAPI document](/docs/api/openapi).

## Validate a subset of an Audience

When a survey applies to only part of an Audience, validation selects eligible Minds before asking any questions. The model considers the survey population, Audience distributions and cohort profiles, and each Mind’s profile. Missing cohort attributes are not automatic exclusions: clear individual profile evidence can support inclusion. Confirmed and supported Minds are selected; unknown or mismatching Minds are left out. At least 10 eligible, ready Minds are required; otherwise that survey is not asked.

Each survey’s `respondentScope` reports its population, selected and total Audience counts, and counts of confirmed, supported, unknown and excluded Minds. The match percentage compares only selected Minds’ answers with the survey results using the existing numeric scoring formula. It is not a model-assigned score or an estimate of how many people qualify. Subset scores remain attached to the Audience and are excluded from its overall and batch combined scores. Response costs use the selected Minds per question. A Mind shared by several Audiences is evaluated within each Audience’s validation context.

## Customer-authorized support

Owners authorize a verified Minds staff member to amend one Audience’s composition for a limited time. Access can be revoked, and the actual staff actor, requested change and outcome remain available in the support history.

Only the current Audience owner can grant or revoke access. Choose a different, confirmed `getminds.ai` staff account, a reason and an expiry within 30 days. Reuse the same UUID `requestKey` and identical grant inputs after a timeout; a replay does not renew expired or revoked access. Staff cannot issue their own grants.

Support permits composition amendments only, through the existing recalibration endpoint. It grants no workspace membership, deletion, ownership transfer, member growth, source upload, Go deeper, shared-Mind waiver or billing change. Existing member-ownership, shared-Mind and active-Study safeguards still apply. The customer remains the resource owner; the actual staff actor and requested instruction or selected percentages are recorded.

Revocation, expiry, ownership transfer or loss of verified staff status prevents subsequent work at checkpoints. An in-flight atomic step may finish; completed changes are retained. A 202 response accepts an operation and does not mean the amendment is complete. Poll its status URL until completed or failed; a failed status read must not trigger another amendment.

```text
POST   /audiences/{audienceId}/support
GET    /audiences/{audienceId}/support
DELETE /audiences/{audienceId}/support/{grantId}
GET    /audiences/{audienceId}/support/activity
GET    /support/audiences
```

```json
{
  "staffEmail": "support@getminds.ai",
  "reason": "Requested composition amendment",
  "expiresAt": "2026-10-20T12:00:00Z",
  "requestKey": "f5d4d4ca-52c7-4b4b-bb8f-15c05d35d231"
}
```

`GET` → `{ "data": { "grants": [], "nextCursor": null } }`

`activity` → `{ "data": { "operations": [], "nextCursor": null } }`

`nextCursor` → `?cursor={nextCursor}` (50 grants / 10 operations).
