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

# Anomaly Detection

> Identify unusual rows in your data.

Anomaly detection models learn what "normal" looks like from your training data and then flag rows in inference data that deviate from those patterns.

## Training

Training fits the model to your data, learning the distribution of normal rows. No `label_column` is needed -- anomaly detection is fully unsupervised. No validation metrics are computed for anomaly models.

<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 data
  with open("transactions.csv", "rb") as f:
      resp = requests.post(
          f"{base_url}/datasets",
          headers=headers,
          files={"file": ("transactions.csv", f, "text/csv")},
          data={"dataset_name": "transactions"},
      )
  dataset_id = resp.json()["dataset"]["id"]

  # Train an anomaly detection model
  resp = requests.post(
      f"{base_url}/models/train",
      headers=headers,
      json={
          "model_name": "fraud_detector",
          "model_type": "anomaly",
          "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)
  ```

  ```python Python (SDK) theme={null}
  import time
  from pathlib import Path
  from woodwide import WoodWide

  client = WoodWide()

  dataset = client.datasets.create(
      file=Path("transactions.csv"),
      dataset_name="transactions",
      override=True,
  )

  model = client.models.train(
      model_type="anomaly",
      dataset_id=dataset.id,
  )
  model_id = model.id

  while True:
      model = client.models.retrieve(model_id)
      if model.status in {"ready", "failed"}:
          break
      time.sleep(5)

  if model.status == "failed":
      raise RuntimeError("Training failed")
  ```

  ```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 data
  const uploadForm = new FormData();
  uploadForm.append("file", fs.createReadStream("transactions.csv"), "transactions.csv");
  uploadForm.append("dataset_name", "transactions");

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

  // Train an anomaly detection model
  const trainResp = await fetch(`${baseUrl}/models/train`, {
    method: "POST",
    headers: { ...headers, "Content-Type": "application/json" },
    body: JSON.stringify({
      model_name: "fraud_detector",
      model_type: "anomaly",
      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 data
  DATASET_ID=$(curl -s -X POST "$BASE_URL/datasets" \
    -H "Authorization: Bearer $WOODWIDE_API_KEY" \
    -F "file=@transactions.csv" \
    -F "dataset_name=transactions" | jq -r '.dataset.id')

  # Train an anomaly detection 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\": \"fraud_detector\",
      \"model_type\": \"anomaly\",
      \"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

Run inference on data you want to scan for anomalies. This can be the training data itself (to find outliers within it) or new data (to detect rows that deviate from the training distribution).

The output format depends on the `anomaly_format` parameter:

| Value                | Description                                                            |
| -------------------- | ---------------------------------------------------------------------- |
| `ids_only` (default) | Returns a compact list of row indices flagged as anomalous.            |
| `per_row`            | Returns a row for every input instance with an anomaly flag and score. |

<Note>
  Per-row anomaly output (`anomaly_format=per_row`) is available via the HTTP API only.
</Note>

<CodeGroup>
  ```python Python theme={null}
  # Detect anomalies -- compact format (default)
  with open("transactions.csv", "rb") as f:
      resp = requests.post(
          f"{base_url}/models/{model_id}/infer",
          headers=headers,
          files={"file": ("transactions.csv", f, "text/csv")},
          data={"output_type": "json", "anomaly_format": "ids_only"},
      )

  results = resp.json()["data"]
  print(results)  # {"anomalous_ids": [3, 17, 42]}
  ```

  ```python Python (SDK) theme={null}
  # Uses client and model_id from the training example above.
  from pathlib import Path

  result = client.models.infer(
      model_id,
      file=Path("transactions.csv"),
      anomaly_format="ids_only",
      output_type="json",
  )
  print(result["data"]["anomalous_ids"])
  ```

  ```javascript JavaScript theme={null}
  // Detect anomalies -- compact format (default)
  const form = new FormData();
  form.append("file", fs.createReadStream("transactions.csv"), "transactions.csv");
  form.append("output_type", "json");
  form.append("anomaly_format", "ids_only");

  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);  // { anomalous_ids: [3, 17, 42] }
  ```

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

  # Response data: { "anomalous_ids": [3, 17, 42] }
  ```
</CodeGroup>

<CodeGroup>
  ```python Python theme={null}
  # Detailed per-row output
  with open("transactions.csv", "rb") as f:
      resp = requests.post(
          f"{base_url}/models/{model_id}/infer",
          headers=headers,
          files={"file": ("transactions.csv", f, "text/csv")},
          data={"output_type": "json", "anomaly_format": "per_row"},
      )

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

  ```javascript JavaScript theme={null}
  // Detailed per-row output
  const form = new FormData();
  form.append("file", fs.createReadStream("transactions.csv"), "transactions.csv");
  form.append("output_type", "json");
  form.append("anomaly_format", "per_row");

  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=@transactions.csv" \
    -F "output_type=json" \
    -F "anomaly_format=per_row" | jq '.data'
  ```
</CodeGroup>

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