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

# Batch Actions

> Run Normalize (Silver) or Analyze (Gold) on multiple products at once — with scope selector, progress tracking, and result review.

## Overview

**Batch Actions** let you run pipeline stages — Silver normalization or Gold scoring — on multiple products simultaneously. Instead of processing products one by one, you select a scope (individual products, a selection, or the entire catalog) and trigger the operation once.

***

## Selecting products

### Single product

From the product detail page, use the **Normalize** or **Analyze** button in the pipeline panel on the right sidebar. This runs the operation on that product only.

### Selection

1. In the catalog product list, check the boxes next to products you want to process
2. A floating action bar appears at the bottom: **X products selected**
3. Click **Normalize** or **Analyze** from the action bar

### Full catalog

1. In the catalog product list, click **Select All** (selects all products in the catalog, not just the current page)
2. Click **Normalize** or **Analyze** from the action bar
3. Alternatively, use the **Batch Actions** dropdown → **Normalize All** or **Analyze All**

***

## Scope selector

The scope selector lets you define the batch target in API calls:

| Scope         | Behavior                                |
| ------------- | --------------------------------------- |
| `"selection"` | Process only the specified `productIds` |
| `"all"`       | Process every product in the catalog    |

When `scope: "all"`, the `productIds` field is ignored.

***

## Running Silver (Normalize)

Silver normalizes fields: standardizes casing, validates URLs, detects duplicates, maps categories and brands.

### Via UI

1. Select products (or use Select All)
2. Click **Normalize**
3. A progress bar shows: `Normalized X / Y products`
4. When complete, a results panel shows:
   * Fields normalized count
   * Duplicates detected
   * Broken image URLs found

### Via API

<CodeGroup>
  ```bash curl theme={null}
  # Normalize a selection
  curl -X POST "https://app.alana.shopping/api/workspace/WORKSPACE_ID/catalogs/CATALOG_ID/batch/silver" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "productIds": ["prod_abc", "prod_def", "prod_ghi"],
      "scope": "selection"
    }'

  # Normalize the entire catalog
  curl -X POST "https://app.alana.shopping/api/workspace/WORKSPACE_ID/catalogs/CATALOG_ID/batch/silver" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"scope": "all"}'
  ```

  ```javascript JavaScript theme={null}
  // Normalize a selection
  const response = await fetch(
    `https://app.alana.shopping/api/workspace/${workspaceId}/catalogs/${catalogId}/batch/silver`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        productIds: ['prod_abc', 'prod_def', 'prod_ghi'],
        scope: 'selection',
      }),
    }
  );
  const { results, processed, duration_ms } = await response.json();
  console.log(`Processed ${processed} products in ${duration_ms}ms`);
  ```

  ```python Python theme={null}
  import requests

  # Normalize entire catalog
  response = requests.post(
      f"https://app.alana.shopping/api/workspace/{workspace_id}/catalogs/{catalog_id}/batch/silver",
      headers={"Authorization": f"Bearer {api_key}"},
      json={"scope": "all"}
  )
  data = response.json()
  print(f"Processed {data['processed']} products in {data['duration_ms']}ms")
  ```
</CodeGroup>

### Silver result structure

```json theme={null}
{
  "results": [
    {
      "productId": "prod_abc",
      "status": "success",
      "fieldsNormalized": 4,
      "duplicateOf": null,
      "urlsValidated": 3
    },
    {
      "productId": "prod_def",
      "status": "success",
      "fieldsNormalized": 2,
      "duplicateOf": "prod_xyz",
      "urlsValidated": 1
    },
    {
      "productId": "prod_ghi",
      "status": "error",
      "error": "Brand not found: 'UnknownBrand'"
    }
  ],
  "processed": 3,
  "duration_ms": 1240
}
```

| Field              | Description                                                 |
| ------------------ | ----------------------------------------------------------- |
| `status`           | `"success"` or `"error"`                                    |
| `fieldsNormalized` | Number of fields that were transformed                      |
| `duplicateOf`      | If a duplicate was detected, the ID of the original product |
| `urlsValidated`    | Number of image/media URLs checked for reachability         |

***

## Running Gold (Analyze)

Gold scores products on a 0–100 scale across 7 stages, and produces a gap list of fields that would most improve the score.

### Via UI

1. Select products (or use Select All)
2. Click **Analyze**
3. A progress bar shows: `Analyzed X / Y products`
4. When complete, each product card shows a score badge (0–100)
5. Click any product to see the full gap breakdown

### Via API

<CodeGroup>
  ```bash curl theme={null}
  # Analyze a selection
  curl -X POST "https://app.alana.shopping/api/workspace/WORKSPACE_ID/catalogs/CATALOG_ID/batch/gold" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
      "productIds": ["prod_abc", "prod_def"],
      "scope": "selection"
    }'

  # Analyze entire catalog
  curl -X POST "https://app.alana.shopping/api/workspace/WORKSPACE_ID/catalogs/CATALOG_ID/batch/gold" \
    -H "Authorization: Bearer YOUR_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{"scope": "all"}'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch(
    `https://app.alana.shopping/api/workspace/${workspaceId}/catalogs/${catalogId}/batch/gold`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ scope: 'all' }),
    }
  );
  const { results, catalogSummary } = await response.json();
  console.log(`Average score: ${catalogSummary.avgScore}`);
  console.log(`Top gaps: ${catalogSummary.topGaps.join(', ')}`);
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      f"https://app.alana.shopping/api/workspace/{workspace_id}/catalogs/{catalog_id}/batch/gold",
      headers={"Authorization": f"Bearer {api_key}"},
      json={"scope": "all"}
  )
  data = response.json()
  summary = data["catalogSummary"]
  print(f"Average score: {summary['avgScore']}")
  print(f"Score distribution: {summary['scoreDistribution']}")
  ```
</CodeGroup>

### Gold result structure

```json theme={null}
{
  "results": [
    {
      "productId": "prod_abc",
      "status": "success",
      "score": 82,
      "gaps": ["secondaryImages", "gtin"],
      "missingFields": ["gtin"]
    },
    {
      "productId": "prod_def",
      "status": "success",
      "score": 41,
      "gaps": ["description", "gtin", "brand", "images"],
      "missingFields": ["description", "gtin"]
    }
  ],
  "catalogSummary": {
    "avgScore": 61.5,
    "scoreDistribution": {
      "excellent": 12,
      "good": 34,
      "warning": 28,
      "poor": 8
    },
    "topGaps": ["gtin", "description", "secondaryImages"]
  }
}
```

| Field                              | Description                                           |
| ---------------------------------- | ----------------------------------------------------- |
| `score`                            | 0–100 optimization score                              |
| `gaps`                             | Fields ordered by score impact (highest impact first) |
| `missingFields`                    | Fields completely absent from the product             |
| `catalogSummary.topGaps`           | Most common gaps across all products                  |
| `catalogSummary.scoreDistribution` | Count per threshold band                              |

***

## Progress tracking

For large catalogs, batch operations run asynchronously. Track progress via:

* **UI** — live progress bar updates every 2 seconds
* **API** — poll the job status endpoint:

<CodeGroup>
  ```bash curl theme={null}
  curl "https://app.alana.shopping/api/workspace/WORKSPACE_ID/catalogs/CATALOG_ID/batch/JOB_ID/status" \
    -H "Authorization: Bearer YOUR_API_KEY"
  ```

  ```javascript JavaScript theme={null}
  const poll = async (jobId) => {
    const response = await fetch(
      `https://app.alana.shopping/api/workspace/${workspaceId}/catalogs/${catalogId}/batch/${jobId}/status`,
      { headers: { 'Authorization': `Bearer ${apiKey}` } }
    );
    const { status, progress, total } = await response.json();
    console.log(`${status}: ${progress}/${total}`);
    if (status === 'running') setTimeout(() => poll(jobId), 2000);
  };
  ```

  ```python Python theme={null}
  import requests, time

  def poll_job(job_id):
      response = requests.get(
          f"https://app.alana.shopping/api/workspace/{workspace_id}/catalogs/{catalog_id}/batch/{job_id}/status",
          headers={"Authorization": f"Bearer {api_key}"}
      )
      job = response.json()
      print(f"{job['status']}: {job['progress']}/{job['total']}")
      if job['status'] == 'running':
          time.sleep(2)
          poll_job(job_id)
  ```
</CodeGroup>

***

## Best practices

<AccordionGroup>
  <Accordion title="Run Silver before Gold">
    Gold scores rely on normalized data. Always run Silver first to ensure brands and categories are linked before scoring.
  </Accordion>

  <Accordion title="Use catalog summary to prioritize work">
    The `catalogSummary.topGaps` field tells you the most common gaps across your entire catalog. Address these systematically rather than product-by-product.
  </Accordion>

  <Accordion title="Schedule large batches for off-peak hours">
    Processing 10,000+ products can take several minutes. Use the API to trigger batch jobs from a scheduled task during low-traffic hours.
  </Accordion>

  <Accordion title="Re-run Gold selectively after edits">
    After filling gaps, re-run Gold only on the products you edited (use `scope: "selection"` with `productIds`) rather than the entire catalog.
  </Accordion>
</AccordionGroup>
