> ## 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.

# Add a New Understanding to Existing Content

> Run a new extractor over an already-ingested corpus — scoped, cost-safe, and priced before you run — without re-uploading or re-paying for existing work

You've ingested a corpus and built search over it. Now you want to add another
understanding — a new extractor, a new model version, a classifier — over the
**same content**, without re-uploading anything and without re-paying for the
extraction you already ran.

This is a first-class workflow. Three pieces make it cost-safe by default:

1. **Batch scoping** — `collection_ids` on a batch runs *only* that collection's
   extractor over the bucket.
2. **Dedup protection** — `dedup_strategy: "skip"` (the default) never re-runs
   extraction for content a collection has already processed, even if you
   forget to scope.
3. **Pre-flight pricing** — `POST /batches/{id}/estimate-cost` is a true dry
   run: it tells you exactly what would run and what it costs *before* you
   commit, including an `already_extracted_count` showing the work you will
   NOT be re-billed for.

<Note>
  Everything below operates on content already in a bucket. Buckets hold your
  source objects once; collections are independent processing pipelines over
  them. That separation is what makes iterating on models cheap.
</Note>

## 1. Create a second collection over the same bucket

Your existing collection keeps serving queries untouched. The new
understanding gets its own collection pointed at the same bucket:

<CodeGroup>
  ```python Python theme={null}
  new_col = client.collections.create(
      collection_name="scripts-v2-understanding",
      source={"type": "bucket", "bucket_id": bucket_id},
      features=["text_search"],  # or a feature key from GET /v1/collections/features
  )
  ```

  ```javascript JavaScript theme={null}
  const newCol = await client.collections.create({
    collection_name: "scripts-v2-understanding",
    source: { type: "bucket", bucket_id: bucketId },
    features: ["text_search"],
  });
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.mixpeek.com/v1/collections" \
    -H "Authorization: Bearer $API_KEY" \
    -H "X-Namespace: $NAMESPACE" \
    -H "Content-Type: application/json" \
    -d '{
      "collection_name": "scripts-v2-understanding",
      "source": {"type": "bucket", "bucket_id": "'$BUCKET_ID'"},
      "features": ["text_search"]
    }'
  ```
</CodeGroup>

If the extractor behind your chosen feature isn't registered on the namespace
yet, the platform registers it for you at create time. (On older deployments a
422 tells you the exact `PATCH /v1/namespaces` body to run first.)

## 2. Create a batch scoped to the new collection

Scope the batch with `collection_ids` so only the new collection's pipeline
runs:

<CodeGroup>
  ```python Python theme={null}
  batch = client.batches.create(
      bucket_id=bucket_id,
      object_ids=object_ids,           # or filters selecting the slice you want
      collection_ids=[new_col.collection_id],
  )
  ```

  ```javascript JavaScript theme={null}
  const batch = await client.batches.create({
    bucket_id: bucketId,
    object_ids: objectIds,
    collection_ids: [newCol.collection_id],
  });
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.mixpeek.com/v1/buckets/$BUCKET_ID/batches" \
    -H "Authorization: Bearer $API_KEY" \
    -H "X-Namespace: $NAMESPACE" \
    -H "Content-Type: application/json" \
    -d '{"object_ids": ["obj_..."], "collection_ids": ["'$NEW_COLLECTION_ID'"]}'
  ```
</CodeGroup>

## 3. Price it before you run it

`estimate-cost` is a dry run — nothing processes, nothing is charged. (For quoting *planned* ingestion in dollars by modality and feature before you even build the batch, use [`POST /v1/organizations/billing/estimate`](/platform/billing#estimate-before-you-run) — same rating engine that bills you.)

<CodeGroup>
  ```python Python theme={null}
  estimate = client.batches.estimate_cost(bucket_id=bucket_id, batch_id=batch.batch_id)
  print(estimate)
  # {
  #   "object_count": 8,
  #   "already_extracted_count": 0,   # nothing skipped — all new work
  #   "estimated_credits": 10,        # legacy field: internal ledger unit (1 credit = $0.001 → $0.01)
  #   "extractors": ["text_extractor"]
  # }
  ```

  ```javascript JavaScript theme={null}
  const estimate = await client.batches.estimateCost(bucketId, batch.batch_id);
  ```

  ```bash cURL theme={null}
  curl -X POST "https://api.mixpeek.com/v1/buckets/$BUCKET_ID/batches/$BATCH_ID/estimate-cost" \
    -H "Authorization: Bearer $API_KEY" \
    -H "X-Namespace: $NAMESPACE"
  ```
</CodeGroup>

**The trap probe worth knowing:** if you estimate an *unscoped* batch over a
bucket that an existing collection already processed, the estimate shows that
prior work under `already_extracted_count` and prices it at zero — the default
`dedup_strategy: "skip"` refuses to re-run (and re-bill) extraction whose
inputs haven't changed. Your existing collection's GPU spend is protected even
when you forget to scope.

## 4. Submit, then verify only new work ran

Submit the batch and let it complete. Two checks confirm the cost boundary
held:

* The **new** collection has documents; the **old** collection's documents show
  no changed `updated_at`.
* The batch record carries its own cost:
  `batch.cost → {"credits_consumed": 10, "cost_usd": 0.01}` — `cost_usd` is the
  number that matters (the `credits_consumed` field is the internal ledger unit,
  1 credit = $0.001). Batches bill at least the $0.01 minimum.

For a multi-batch rollout, sum the `cost_usd` of the rollout's batches —
each batch is its own attribution record.

## 5. Compare the two understandings side by side

Both collections index the same source objects, so retrieval comparisons need
no second corpus copy: run the same query against each collection (or one
retriever spanning both) and compare. For systematic comparison, build a small
[evaluation](/retrieval/evaluations) dataset once and run it against both.

When a new *version* of an extractor ships (versions coexist in the catalog —
e.g. v1 and v2 side by side), the same pattern applies: pin the new version in
a new collection, roll a scoped batch, compare, and cut over when the numbers
say so. For whole-namespace model swaps, use
[model migration](/processing/model-migration) instead.

## 6. Re-run only failures

If some objects fail, you don't resubmit the batch:

* `GET /v1/buckets/{bucket_id}/batches/{batch_id}/failed-documents` lists
  failures with per-tier detail and what's retryable.
* `POST /v1/buckets/{bucket_id}/batches/{batch_id}/retry` re-runs just those.

## Next steps

<CardGroup cols={2}>
  <Card title="Feature Extractors" icon="microchip" href="/processing/feature-extractors">
    How a single extractor is configured — inputs, outputs, inference cache.
  </Card>

  <Card title="Model Migration" icon="arrows-rotate" href="/processing/model-migration">
    Swap a namespace's embedding model wholesale with validation and dry-run.
  </Card>

  <Card title="Multi-Tier Extraction" icon="diagram-project" href="/processing/multi-tier-extractors">
    Chain collections into a DAG — transcribe, then embed, then classify.
  </Card>

  <Card title="Custom Extractor Quickstart" icon="rocket" href="/tutorials/custom-extractor-quickstart">
    Build and test your own extractor end-to-end.
  </Card>
</CardGroup>
