> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.resemble.ai/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.resemble.ai/_mcp/server.

# Detection Feedback

Tell Resemble whether a detection verdict was right — and when it wasn't, what it should have been.

Every answer is stored alongside a snapshot of what the model said at the time it was submitted (verdict, score, media type, and the raw per-modality metrics). That snapshot is what makes the data usable as training signal later: it stays interpretable even if the detect is re-run, re-finalized, or deleted.

| Method   | Path                      | Purpose                      |
| -------- | ------------------------- | ---------------------------- |
| `POST`   | `/detect/{uuid}/feedback` | Submit or revise your answer |
| `GET`    | `/detect/{uuid}/feedback` | Read your answer             |
| `DELETE` | `/detect/{uuid}/feedback` | Retract your answer          |

## Access and scoping

* The authenticated user needs Detect access. Without it, every call returns `400` — not `403`. Resemble's v2 API surfaces entitlement errors as `400`, so don't branch on `403`.
* Calls are scoped to your team. A detect belonging to another team returns `404`, the same as one that doesn't exist — the endpoint never confirms whether a UUID you can't reach is real.
* Feedback is per **user**, not per team. `GET` and `DELETE` only ever see your own row; a teammate's answer on the same detect is invisible to you.
* Requests are rate-limited to 50 per second per IP, across all Resemble APIs.

## Submit feedback

`POST https://app.resemble.ai/api/v2/detect/{uuid}/feedback`

Upserts on `(detect, user)`: re-posting edits your existing answer rather than adding a second one. There is no separate update call.

| Field     | Type   | Required | Description                                                                                     |
| --------- | ------ | -------- | ----------------------------------------------------------------------------------------------- |
| `type`    | string | Yes      | What the verdict should have been. See below.                                                   |
| `comment` | string | No       | Free text, up to 1000 characters. Trimmed; blank becomes `null`.                                |
| `source`  | string | No       | Where the answer came from. Defaults to `api`. Truncated to 64 characters rather than rejected. |

### `type` values

| Value                    | Meaning                                                                                |
| ------------------------ | -------------------------------------------------------------------------------------- |
| `correct`                | The verdict was right.                                                                 |
| `should_be_ai_generated` | We said authentic; it is AI-generated.                                                 |
| `should_be_authentic`    | We said AI-generated; it is genuine **and unaltered**.                                 |
| `should_be_non_ai`       | No model generated it — but it may still be edited, spliced, or a human impersonation. |
| `should_be_neutral`      | Neither call is right; this one is genuinely ambiguous.                                |
| `skipped`                | The verdict was wrong, but the reviewer didn't say how.                                |

`correct` maps to a thumbs-up; every other value, including `skipped`, maps to a thumbs-down.

Use `skipped` rather than withholding feedback when a reviewer knows the verdict is wrong but can't say what it should be — "wrong, unspecified" is still signal, and it's distinguishable from the four specific corrections.

### `should_be_authentic` vs `should_be_non_ai`

These are different claims, and the distinction matters — sending the stronger one when you mean the weaker teaches the model that "not synthetic" implies "not manipulated".

* Use **`should_be_authentic`** when you can vouch for the media: it is a genuine recording of what it appears to be.
* Use **`should_be_non_ai`** when you only know that no model generated it. A human impersonator, a spliced-together real recording, and a heavily edited but human-performed clip are all non-AI without being authentic.

When in doubt, `should_be_non_ai` is the safer answer.

### Example

```bash
curl --request POST 'https://app.resemble.ai/api/v2/detect/DETECT_UUID/feedback' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' \
  -H 'Content-Type: application/json' \
  --data '{
    "type": "should_be_authentic",
    "comment": "Verified with the speaker — this is their real voice.",
    "source": "acme-moderation"
  }'
```

## Response

```json
{
  "success": true,
  "item": {
    "detect_uuid": "3f1a9c4e7b2d5081",
    "type": "should_be_authentic",
    "vote": "down",
    "verdict": "ai_generated",
    "score": 0.92,
    "media_type": "audio",
    "comment": "Verified with the speaker — this is their real voice.",
    "credit": 0,
    "source": "acme-moderation",
    "created_at": "2026-08-06T18:22:04.000Z",
    "updated_at": "2026-08-06T18:22:04.000Z"
  }
}
```

### Response fields

| Field              | Type           | Description                                                                                       |
| ------------------ | -------------- | ------------------------------------------------------------------------------------------------- |
| `item.detect_uuid` | string         | The detect this answer is attached to.                                                            |
| `item.type`        | string         | What you sent.                                                                                    |
| `item.vote`        | string         | `up` or `down`, derived from `type`. Never independent of it.                                     |
| `item.verdict`     | string \| null | What the **model** said: `ai_generated`, `authentic`, or `null`.                                  |
| `item.score`       | number \| null | The model's headline confidence, `0.0`–`1.0`, or `null`.                                          |
| `item.media_type`  | string         | `audio`, `video`, `image`, or `text`, copied from the detect.                                     |
| `item.comment`     | string \| null | Your comment, or `null`.                                                                          |
| `item.credit`      | integer        | Credits awarded for this answer. Written by Resemble, never by the submitter; `0` on a fresh row. |
| `item.source`      | string         | The attribution string recorded for this answer.                                                  |
| `item.created_at`  | string         | When the answer was first submitted.                                                              |
| `item.updated_at`  | string         | Moves when you revise an answer; `created_at` does not.                                           |

Resemble records more against each answer than it returns — a frozen copy of the raw per-modality metrics the verdict came from, plus internal review state. None of that is part of this response or of any forward contract. The interpreted form of the snapshot (`verdict`, `score`, `media_type`) is what you get.

**When `verdict` is `null`**, the detection landed somewhere indeterminate — a neutral or uncertain label, or a label we can't map to either class. You can still submit a correction; that's arguably when it's most useful.

**`score` for video** is the higher of the audio and video confidences, since the more confident modality is what drives the displayed verdict. For a detect where only one modality was requested, it's that modality's score.

**`should_be_neutral` and `should_be_non_ai` never appear in `verdict`.** That field records only the two decided *model* outcomes; those two assertions live solely in `type`.

## Read your feedback

`GET https://app.resemble.ai/api/v2/detect/{uuid}/feedback`

Returns the same `item` shape as `POST`.

```bash
curl --request GET 'https://app.resemble.ai/api/v2/detect/DETECT_UUID/feedback' \
  -H 'Authorization: Bearer YOUR_API_TOKEN'
```

Returns `404` when *you* haven't answered this detect, even if the detect exists and a teammate has answered it.

## Retract your feedback

`DELETE https://app.resemble.ai/api/v2/detect/{uuid}/feedback`

```bash
curl --request DELETE 'https://app.resemble.ai/api/v2/detect/DETECT_UUID/feedback' \
  -H 'Authorization: Bearer YOUR_API_TOKEN'
```

Idempotent: returns `200 { "success": true }` whether or not a row existed. Only your own row is removed; a teammate's answer on the same detect is untouched.

## Integration notes

### Wait for the detect to complete

The most common integration snag. A detect that is still processing — or that errored — rejects feedback with `400`. Poll [`GET /detect/{uuid}`](/detect/get) until `status` is `completed` before offering a reviewer the choice.

```bash
curl --request GET 'https://app.resemble.ai/api/v2/detect/DETECT_UUID' \
  -H 'Authorization: Bearer YOUR_API_TOKEN' | jq -r '.item.status'
```

`DELETE` is exempt: you can always retract, even from a detect that later failed.

### One row per reviewer

If several people in your organization review the same detect, give each of them their own Resemble user. Feedback is keyed on `(detect, user)`, so a shared service account collapses all of their answers into one row, each overwriting the last.

### Attribute your surface

Set `source` to something stable and specific — a product or queue name, not a per-request identifier. It's how we weigh a one-click reaction against a considered review, so its value is in being aggregatable.

### Deleted detects

Feedback outlives the detect it describes, but the endpoints don't: once a detect is deleted, all three calls return `404`. Retract before deleting if you need to.

## Errors

Every error uses the same envelope:

```json
{ "success": false, "message": "..." }
```

| Status | Reason                                                                                                                                              |
| ------ | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | Unknown or missing `type`; `comment` over 1000 characters; the detect hasn't finished processing or it failed; the team doesn't have Detect access. |
| 401    | Missing or invalid credentials.                                                                                                                     |
| 404    | No such detect for your team, or (on `GET`) you haven't answered it.                                                                                |

An explicitly supplied invalid `type` is always an error — it never silently falls back to anything else.