> ## Documentation Index
> Fetch the complete documentation index at: https://docs.mixpeek.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Annotations

> Record human decisions on documents for review workflows, compliance, and model improvement

Annotations capture explicit human judgments on documents — approve, reject, defer, or any domain-specific label. Unlike [interaction signals](https://docs.mixpeek.com/docs/retrieval/interactions.md) which track implicit behavior (clicks, views, dwell time), annotations record deliberate decisions with optional confidence scores, reasoning, and structured payloads. They are the foundation for human-in-the-loop workflows where retriever results need expert review before action.

## When to Use Annotations

Annotations solve the problem of turning retriever output into verified decisions. Any workflow where a person reviews documents and records a judgment benefits from annotations:

| Use Case | Labels | Payload Example |
| - | - | - |
| Medical coding review | `approved`, `rejected`, `deferred` | `{"codes_approved": ["E11.40"], "raf_impact": 0.302}` |
| Brand infringement triage | `infringement`, `safe`, `needs_review` | `{"confidence_model": 0.91, "match_type": "logo"}` |
| Duplicate detection | `confirmed_dupe`, `false_positive` | `{"canonical_id": "doc_abc", "similarity": 0.97}` |
| Content moderation | `approved`, `flagged`, `removed` | `{"policy_violation": "copyright", "severity": "high"}` |
| Document classification | `correct`, `incorrect`, `ambiguous` | `{"predicted_class": "invoice", "true_class": "receipt"}` |

Annotations are domain-agnostic — labels are free-form strings, and the `payload` field accepts any structured JSON your workflow needs.

## Annotation Lifecycle

```
Retriever executes → Results returned → Human reviews → Annotation recorded → Audit trail preserved
```

Each annotation links back to a document and optionally to the retriever execution that surfaced it:

* **`document_id`** and **`collection_id`** — what was reviewed
* **`retriever_id`**, **`execution_id`**, **`stage_name`** — how the document was found (provenance)
* **`label`**, **`confidence`**, **`reasoning`** — the human decision
* **`payload`** — structured data specific to the workflow
* **`actor_id`** and **`actor_type`** — who made the decision (user, API key, or system)

All mutations emit [webhooks](https://docs.mixpeek.com/docs/operations/webhooks.md) (`annotation.created`, `annotation.updated`, `annotation.deleted`) and log to the [audit trail](https://docs.mixpeek.com/docs/api-reference/organization-audit/list-audit-logs.md).

## Create an Annotation

Record a decision after reviewing a document:

```bash cURL theme={null}
curl -sS -X POST "$MP_API_URL/v1/annotations" \
  -H "Authorization: Bearer $MP_API_KEY" \
  -H "X-Namespace: $MP_NAMESPACE" \
  -H "Content-Type: application/json" \
  -d '{
    "document_id": "doc_2e7650fa254b",
    "collection_id": "col_clinical_notes",
    "label": "approved",
    "confidence": 0.95,
    "reasoning": "Note clearly documents peripheral neuropathy with supporting lab values.",
    "payload": {
      "codes_approved": ["E11.40", "E11.65"],
      "raf_impact": 0.420,
      "annual_revenue": 2522
    },
    "retriever_id": "ret_hcc_review",
    "execution_id": "exec_abc123"
  }'
```

```python Python theme={null}
from mixpeek import Mixpeek

mp = Mixpeek(api_key="API_KEY")

annotation = mp.annotations.create(
    document_id="doc_2e7650fa254b",
    collection_id="col_clinical_notes",
    label="approved",
    confidence=0.95,
    reasoning="Note clearly documents peripheral neuropathy with supporting lab values.",
    payload={
        "codes_approved": ["E11.40", "E11.65"],
        "raf_impact": 0.420,
        "annual_revenue": 2522,
    },
    retriever_id="ret_hcc_review",
    execution_id="exec_abc123",
    namespace="ns_vitae",
)
```

```javascript JavaScript theme={null}
import Mixpeek from "mixpeek";

const mp = new Mixpeek({ apiKey: "API_KEY" });

const annotation = await mp.annotations.create({
  documentId: "doc_2e7650fa254b",
  collectionId: "col_clinical_notes",
  label: "approved",
  confidence: 0.95,
  reasoning:
    "Note clearly documents peripheral neuropathy with supporting lab values.",
  payload: {
    codes_approved: ["E11.40", "E11.65"],
    raf_impact: 0.42,
    annual_revenue: 2522,
  },
  retrieverId: "ret_hcc_review",
  executionId: "exec_abc123",
  namespace: "ns_vitae",
});
```

### `payload` and `metadata`

Two free-form fields, with different jobs.

`payload` holds the structured content of the judgment: the codes approved, the score, whatever the workflow decides. `metadata` holds anything the caller wants carried alongside it, stored and returned as-is.

```json theme={null}
{
  "document_id": "doc_2e7650fa254b",
  "label": "approved",
  "payload": { "codes_approved": ["E11.40"], "raf_impact": 0.420 },
  "metadata": { "reviewer_team": "hcc-west", "queue": "priority" }
}
```

On create and on patch, `metadata` is mirrored onto the annotation's row in the [`mxp_document_annotations`](https://docs.mixpeek.com/docs/vector-store/system-collections) system collection, under `metadata` on that document. The mirror is best-effort: if it fails, the annotation write still succeeds, so treat the annotation record as the source of truth.

`PATCH /v1/annotations/{annotation_id}` replaces `metadata` whole rather than merging keys. Send the full object you want to keep. Omitting the field leaves the stored value untouched, and so does sending `null`; send `{}` to clear it.

## Query Annotations

List annotations with filters to build review queues or dashboards:

```bash cURL theme={null}
# All rejected annotations for a collection
curl -sS -X POST "$MP_API_URL/v1/annotations/list" \
  -H "Authorization: Bearer $MP_API_KEY" \
  -H "X-Namespace: $MP_NAMESPACE" \
  -H "Content-Type: application/json" \
  -d '{
    "collection_id": "col_clinical_notes",
    "label": "rejected"
  }'

# All annotations on a specific document
curl -sS -X POST "$MP_API_URL/v1/annotations/list" \
  -H "Authorization: Bearer $MP_API_KEY" \
  -H "X-Namespace: $MP_NAMESPACE" \
  -H "Content-Type: application/json" \
  -d '{
    "document_id": "doc_2e7650fa254b"
  }'
```

```python Python theme={null}
# All rejected annotations
rejected = mp.annotations.list(
    collection_id="col_clinical_notes",
    label="rejected",
    namespace="ns_vitae",
)

# All annotations on a specific document
doc_annotations = mp.annotations.list(
    document_id="doc_2e7650fa254b",
    namespace="ns_vitae",
)
```

```javascript JavaScript theme={null}
// All rejected annotations
const rejected = await mp.annotations.list({
  collectionId: "col_clinical_notes",
  label: "rejected",
  namespace: "ns_vitae",
});

// All annotations on a specific document
const docAnnotations = await mp.annotations.list({
  documentId: "doc_2e7650fa254b",
  namespace: "ns_vitae",
});
```

Available filters: `document_id`, `collection_id`, `label`, `actor_id`, `retriever_id`, `as_of`. All filters are optional and can be combined.

### Read the Judgment Set at a Past Instant

Pass `as_of` to get the annotations as they stood at one moment. Annotations created
after that instant are excluded, and every annotation still in the set is rewound to
its state at that time using its revision chain.

```bash cURL theme={null}
curl -sS -X POST "$MP_API_URL/v1/annotations/list" \
  -H "Authorization: Bearer $MP_API_KEY" \
  -H "X-Namespace: $MP_NAMESPACE" \
  -H "Content-Type: application/json" \
  -d '{
    "collection_id": "col_clinical_notes",
    "as_of": "2026-08-01T00:00:00Z"
  }'
```

```python Python theme={null}
frozen = mp.annotations.list(
    collection_id="col_clinical_notes",
    as_of="2026-08-01T00:00:00Z",
    namespace="ns_vitae",
)
```

```javascript JavaScript theme={null}
const frozen = await mp.annotations.list({
  collectionId: "col_clinical_notes",
  asOf: "2026-08-01T00:00:00Z",
  namespace: "ns_vitae",
});
```

This is the handle for reproducible evaluation. An evaluation pins one `as_of` and
re-reads the identical judgment set weeks later, after reviewers have added and
revised decisions.

> [!WARNING]
>
> **The boundary is inclusive on both sides.** An annotation created at exactly
> `as_of` is returned, and an edit applied at exactly `as_of` is kept rather than
> rewound. To exclude a record created at a known timestamp, pass an `as_of` strictly
> earlier than it.

A rewound annotation reports the `version` it held at that instant, and its
`revisions` array contains only the revisions below that version. Comparing `version`
across two `as_of` reads tells you which decisions moved between them.

## Aggregate Stats

Get label distribution counts for dashboards and progress tracking:

```bash cURL theme={null}
# Stats across all annotations
curl -sS "$MP_API_URL/v1/annotations/stats" \
  -H "Authorization: Bearer $MP_API_KEY" \
  -H "X-Namespace: $MP_NAMESPACE"

# Stats for a specific collection
curl -sS "$MP_API_URL/v1/annotations/stats?collection_id=col_clinical_notes" \
  -H "Authorization: Bearer $MP_API_KEY" \
  -H "X-Namespace: $MP_NAMESPACE"
```

```python Python theme={null}
stats = mp.annotations.stats(namespace="ns_vitae")
# {"total": 142, "by_label": {"approved": 89, "rejected": 31, "deferred": 22}}
```

```javascript JavaScript theme={null}
const stats = await mp.annotations.stats({ namespace: "ns_vitae" });
// {total: 142, byLabel: {approved: 89, rejected: 31, deferred: 22}}
```

## Update a Decision

When a review is revisited — for example, a deferred case gets a clinical consult and can now be approved:

```bash cURL theme={null}
curl -sS -X PATCH "$MP_API_URL/v1/annotations/ann_3cefcdaf7536a19a" \
  -H "Authorization: Bearer $MP_API_KEY" \
  -H "X-Namespace: $MP_NAMESPACE" \
  -H "Content-Type: application/json" \
  -d '{
    "label": "approved",
    "confidence": 0.88,
    "reasoning": "Clinical review completed — peripheral neuropathy confirmed."
  }'
```

```python Python theme={null}
updated = mp.annotations.update(
    annotation_id="ann_3cefcdaf7536a19a",
    label="approved",
    confidence=0.88,
    reasoning="Clinical review completed — peripheral neuropathy confirmed.",
    namespace="ns_vitae",
)
```

```javascript JavaScript theme={null}
const updated = await mp.annotations.update({
  annotationId: "ann_3cefcdaf7536a19a",
  label: "approved",
  confidence: 0.88,
  reasoning: "Clinical review completed — peripheral neuropathy confirmed.",
  namespace: "ns_vitae",
});
```

The audit trail records both the original and updated values, preserving the full decision history.

## Bulk Operations

Process up to 1000 creates, updates, and deletes in a single call. Each operation is independent — a failure in one does not roll back the others.

```bash cURL theme={null}
curl -sS -X POST "$MP_API_URL/v1/annotations/bulk" \
  -H "Authorization: Bearer $MP_API_KEY" \
  -H "X-Namespace: $MP_NAMESPACE" \
  -H "Content-Type: application/json" \
  -d '{
    "create": [
      {"document_id": "doc_001", "collection_id": "col_notes", "label": "approved", "confidence": 0.95},
      {"document_id": "doc_002", "collection_id": "col_notes", "label": "approved", "confidence": 0.91},
      {"document_id": "doc_003", "collection_id": "col_notes", "label": "rejected", "reasoning": "Insufficient documentation"}
    ],
    "update": [
      {"annotation_id": "ann_abc123", "label": "approved", "confidence": 0.88}
    ],
    "delete": ["ann_def456", "ann_ghi789"]
  }'
```

```python Python theme={null}
result = mp.annotations.bulk(
    create=[
        {"document_id": "doc_001", "collection_id": "col_notes", "label": "approved", "confidence": 0.95},
        {"document_id": "doc_002", "collection_id": "col_notes", "label": "approved", "confidence": 0.91},
        {"document_id": "doc_003", "collection_id": "col_notes", "label": "rejected", "reasoning": "Insufficient documentation"},
    ],
    update=[
        {"annotation_id": "ann_abc123", "label": "approved", "confidence": 0.88},
    ],
    delete=["ann_def456", "ann_ghi789"],
    namespace="ns_vitae",
)
# result.created_count=3, result.updated_count=1, result.deleted_count=2
```

```javascript JavaScript theme={null}
const result = await mp.annotations.bulk({
  create: [
    { documentId: "doc_001", collectionId: "col_notes", label: "approved", confidence: 0.95 },
    { documentId: "doc_002", collectionId: "col_notes", label: "approved", confidence: 0.91 },
    { documentId: "doc_003", collectionId: "col_notes", label: "rejected", reasoning: "Insufficient documentation" },
  ],
  update: [
    { annotationId: "ann_abc123", label: "approved", confidence: 0.88 },
  ],
  delete: ["ann_def456", "ann_ghi789"],
  namespace: "ns_vitae",
});
// result.createdCount=3, result.updatedCount=1, result.deletedCount=2
```

The response includes per-operation results so you can identify and retry individual failures.

## Example: Medical Coding Review Workflow

A healthcare organization uses Mixpeek to surface HCC suspect conditions from clinical notes. Coders review each result and record their decision:

1. **Retriever** runs `agent_search` across clinical notes, returning suspect HCC conditions with supporting evidence.
2. **Review queue** — the application calls `POST /v1/annotations/list?label=deferred` to show unresolved cases.
3. **Coder annotates** — for each document, the coder selects a label and the app calls `POST /v1/annotations` with the decision, ICD-10 codes, and RAF impact.
4. **Dashboard** — `GET /v1/annotations/stats?collection_id=col_notes` powers a progress bar showing approved/rejected/deferred counts.
5. **Audit** — compliance officers query annotations by `actor_id` to review individual coder decisions. The `reasoning` field provides the justification trail required for CMS audits.

> [!NOTE]
>
> Annotations are stored independently from documents — they don't modify the underlying document data. This separation ensures that the original clinical record remains untouched while the review layer captures all human decisions.

## Best Practices

* **Use consistent labels** within a workflow. Pick a label vocabulary (e.g., `approved`, `rejected`, `deferred`) and stick with it — the stats endpoint groups by exact string match.
* **Include reasoning** for audit-sensitive workflows. The `reasoning` field is indexed and retrievable, making it valuable for compliance reviews and dispute resolution.
* **Link provenance** when annotating retriever results. Setting `retriever_id`, `execution_id`, and `stage_name` lets you trace exactly how the document was surfaced, which is critical for evaluating retriever quality.
* **Use payload for structured data** rather than encoding it in the label. Labels should be human-readable categories; domain-specific fields (codes, scores, amounts) belong in `payload`.
* **Listen to webhooks** for real-time updates. Subscribe to `annotation.created` and `annotation.updated` events to trigger downstream workflows (e.g., auto-submit approved records, escalate rejected ones).

## References

* [Create Annotation](https://docs.mixpeek.com/docs/api-reference/annotations/create-annotation.md)
* [List Annotations](https://docs.mixpeek.com/docs/api-reference/annotations/list-annotations.md)
* [Annotation Stats](https://docs.mixpeek.com/docs/api-reference/annotations/annotation-stats.md)
* [Update Annotation](https://docs.mixpeek.com/docs/api-reference/annotations/update-annotation.md)
* [Delete Annotation](https://docs.mixpeek.com/docs/api-reference/annotations/delete-annotation.md)
* [Bulk Annotations](https://docs.mixpeek.com/docs/api-reference/annotations/bulk-annotations.md)
* [Interaction Signals](https://docs.mixpeek.com/docs/retrieval/interactions.md) — implicit behavioral signals (complementary to annotations)
* [Webhooks](https://docs.mixpeek.com/docs/operations/webhooks.md) — subscribe to annotation lifecycle events


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.