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

# Search

> Find the most similar training-set row for each query row.

Search models build an index over the training data at training time. At inference time, each row in the inference data is matched to the most semantically similar row in the training dataset.

This is useful for finding nearest-neighbor matches, deduplication, record linkage, or recommendation systems.

## Training

Training embeds the entire training dataset and builds a nearest-neighbor index over those embeddings. No `label_column` is needed. No validation metrics are computed for search models.

<Note>
  Search is not available in the Python SDK yet. Use the HTTP examples below.
</Note>

<CodeGroup>
  ```python Python theme={null}
  import os, time, requests

  api_key = os.getenv("WOODWIDE_API_KEY")
  base_url = "https://api.woodwide.ai"
  headers = {"Authorization": f"Bearer {api_key}"}

  # Upload the dataset to search against
  with open("catalog.csv", "rb") as f:
      resp = requests.post(
          f"{base_url}/datasets",
          headers=headers,
          files={"file": ("catalog.csv", f, "text/csv")},
          data={"dataset_name": "product_catalog"},
      )
  dataset_id = resp.json()["dataset"]["id"]

  # Train a search model
  resp = requests.post(
      f"{base_url}/models/train",
      headers=headers,
      json={
          "model_name": "catalog_search",
          "model_type": "search",
          "dataset_id": dataset_id,
      },
  )
  model_id = resp.json()["model"]["id"]

  # Wait for training
  while True:
      model = requests.get(
          f"{base_url}/models/{model_id}", headers=headers
      ).json()
      if model["status"] == "ready":
          break
      time.sleep(5)
  ```

  ```javascript JavaScript theme={null}
  const fs = require("fs");
  const FormData = require("form-data");

  const apiKey = process.env.WOODWIDE_API_KEY;
  const baseUrl = "https://api.woodwide.ai";
  const headers = { Authorization: `Bearer ${apiKey}` };

  // Upload the dataset to search against
  const uploadForm = new FormData();
  uploadForm.append("file", fs.createReadStream("catalog.csv"), "catalog.csv");
  uploadForm.append("dataset_name", "product_catalog");

  const uploadResp = await fetch(`${baseUrl}/datasets`, {
    method: "POST",
    headers: { ...headers, ...uploadForm.getHeaders() },
    body: uploadForm,
  });
  const { dataset: { id: datasetId } } = await uploadResp.json();

  // Train a search model
  const trainResp = await fetch(`${baseUrl}/models/train`, {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({
      model_name: "catalog_search",
      model_type: "search",
      dataset_id: datasetId,
    }),
  });
  const { model: { id: modelId } } = await trainResp.json();

  // Wait for training
  while (true) {
    const modelResp = await fetch(`${baseUrl}/models/${modelId}`, { headers });
    const model = await modelResp.json();
    if (model.status === "ready") break;
    await new Promise((r) => setTimeout(r, 5000));
  }
  ```

  ```bash curl theme={null}
  export WOODWIDE_API_KEY="your_api_key_here"
  export BASE_URL="https://api.woodwide.ai"

  # Upload the dataset to search against
  DATASET_ID=$(curl -s -X POST "$BASE_URL/datasets" \
    -H "Authorization: Bearer $WOODWIDE_API_KEY" \
    -F "file=@catalog.csv" \
    -F "dataset_name=product_catalog" | jq -r '.dataset.id')

  # Train a search model
  MODEL_ID=$(curl -s -X POST "$BASE_URL/models/train" \
    -H "Authorization: Bearer $WOODWIDE_API_KEY" \
    -H "Content-Type: application/json" \
    -d "{
      \"model_name\": \"catalog_search\",
      \"model_type\": \"search\",
      \"dataset_id\": \"$DATASET_ID\"
    }" | jq -r '.model.id')

  # Wait for training
  while true; do
    STATUS=$(curl -s "$BASE_URL/models/$MODEL_ID" \
      -H "Authorization: Bearer $WOODWIDE_API_KEY" | jq -r '.status')

    if [ "$STATUS" = "ready" ]; then break; fi
    if [ "$STATUS" = "failed" ]; then echo "Training failed."; exit 1; fi

    sleep 5
  done
  ```
</CodeGroup>

## Inference

Provide a CSV of query rows. For each query row, the model returns the row ID of the closest match in the training dataset.

<CodeGroup>
  ```python Python theme={null}
  # Find the closest catalog item for each query
  with open("queries.csv", "rb") as f:
      resp = requests.post(
          f"{base_url}/models/{model_id}/infer",
          headers=headers,
          files={"file": ("queries.csv", f, "text/csv")},
          data={"output_type": "json"},
      )

  results = resp.json()["data"]
  print(results)
  ```

  ```javascript JavaScript theme={null}
  // Find the closest catalog item for each query
  const form = new FormData();
  form.append("file", fs.createReadStream("queries.csv"), "queries.csv");
  form.append("output_type", "json");

  const resp = await fetch(`${baseUrl}/models/${modelId}/infer`, {
    method: "POST",
    headers: { ...headers, ...form.getHeaders() },
    body: form,
  });

  const { data: results } = await resp.json();
  console.log(results);
  ```

  ```bash curl theme={null}
  curl -s -X POST "$BASE_URL/models/$MODEL_ID/infer" \
    -H "Authorization: Bearer $WOODWIDE_API_KEY" \
    -F "file=@queries.csv" \
    -F "output_type=json" | jq '.data'
  ```
</CodeGroup>

See [Output Formats](/api-reference/inference#search) for the full output schema.
