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

# Shelf Analysis

> Learn the core workflow for analyzing shelf images and extracting structured product data.

Shelf analysis is the core capability of the Shelfforce API. You submit an image of a retail shelf, and Shelfforce returns structured data for every product detected — including brand, description, category, facing count, price, promotional status, confidence score, and fixture type.

This guide walks through the full analysis workflow.

## How it works

Shelfforce uses a multi-step vision pipeline to analyze shelf images:

1. **Extraction** — Identifies unique SKUs visible on the shelf.
2. **Product Detail** — Determines facing counts and shelf positions for each product.
3. **Price Attribution** — Reads all price tags and matches them to the correct products using spatial reasoning.
4. **Summary** — Computes brand-level share-of-shelf metrics and totals.

The entire pipeline runs automatically when you submit an image. You receive the complete results when the analysis finishes.

## The analysis lifecycle

Analysis is **asynchronous**. Here is exactly what happens from submission to results:

```
┌──────────────────────────────────────────────────────────────┐
│  1. POST /api/v1/analyses                                    │
│     You submit an image URL                                  │
│     → Response: { status: "processing", id: "an_..." }       │
│                                                              │
│  2. Processing (10-30 seconds)                               │
│     Shelfforce runs extraction → detail → pricing → summary  │
│                                                              │
│  3. Results ready                                            │
│     ┌─ Option A: Poll GET /api/v1/analyses/:id               │
│     │  Loop every 3-5s until status = "completed"            │
│     │  Best for: quick testing, scripts, simple integrations │
│     │                                                        │
│     ├─ Option B: Webhook notification                        │
│     │  Register for analysis.completed event                 │
│     │  Best for: production systems, high volume             │
│     │                                                        │
│     └─ Option C: Callback URL                                │
│        Pass callbackUrl in the request                       │
│        Best for: per-request notifications, B2B integrations │
│                                                              │
│  4. Read the products array from the completed response      │
└──────────────────────────────────────────────────────────────┘
```

**Typical processing time:** 10-30 seconds, depending on image complexity (number of products, price tag legibility, image resolution). High-resolution images with many products may take up to 45 seconds.

<Tip>
  **Which result method should I use?**

  * **Polling** is simpler and great for scripts, CLI tools, and quick testing. Poll every 3-5 seconds.
  * **Webhooks** are more efficient for production. No wasted requests, instant notification. See [Webhooks](/webhooks).
</Tip>

## Step 1: Submit an image

Send a `POST` request to the analyses endpoint with the URL of a shelf image:

<CodeGroup>
  ```bash curl theme={null}
  curl -X POST https://shelfforce.ai/api/v1/analyses \
    -H "Authorization: Bearer sf_live_a1b2c3d4..." \
    -H "Content-Type: application/json" \
    -d '{
      "imageUrl": "https://example.com/shelf.jpg"
    }'
  ```

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

  response = requests.post(
      "https://shelfforce.ai/api/v1/analyses",
      headers={
          "Authorization": "Bearer sf_live_a1b2c3d4...",
          "Content-Type": "application/json",
      },
      json={
          "imageUrl": "https://example.com/shelf.jpg",
      },
  )

  analysis = response.json()["data"]
  analysis_id = analysis["id"]
  print(f"Analysis submitted: {analysis_id} (status: {analysis['status']})")
  ```

  ```typescript Node.js theme={null}
  const response = await fetch("https://shelfforce.ai/api/v1/analyses", {
    method: "POST",
    headers: {
      "Authorization": "Bearer sf_live_a1b2c3d4...",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      imageUrl: "https://example.com/shelf.jpg",
    }),
  });

  const { data: analysis } = await response.json();
  console.log(`Analysis submitted: ${analysis.id} (status: ${analysis.status})`);
  ```
</CodeGroup>

### Request parameters

| Parameter     | Type     | Required | Description                                                                                                                                                      |
| ------------- | -------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `imageUrl`    | `string` | Yes      | Publicly accessible URL of the shelf image. JPEG, PNG, and WebP are supported.                                                                                   |
| `placeId`     | `string` | No       | Shelfforce place ID to associate with this analysis.                                                                                                             |
| `taskId`      | `string` | No       | Shelfforce task ID to associate with this analysis.                                                                                                              |
| `externalId`  | `string` | No       | Your own reference ID (e.g., customer ID, campaign ID). Returned in responses and webhook payloads. Filterable via the [list analyses](#list-analyses) endpoint. |
| `metadata`    | `object` | No       | Arbitrary key-value pairs (max 10 keys, string values, 500 chars each). Returned in responses and webhook payloads.                                              |
| `callbackUrl` | `string` | No       | HTTPS URL to receive a POST when this analysis completes or fails. See [Callback URLs](#option-c-callback-url).                                                  |

### Response

The response comes back immediately. The `status` is `processing` while the analysis runs.

```json theme={null}
{
  "data": {
    "id": "an_g7h8j9k0",
    "status": "processing",
    "createdAt": "2026-02-23T10:30:00Z"
  }
}
```

<Note>
  One credit is consumed per analysis submission. See [Credits](/credits) for details.
</Note>

## Step 2: Get the results

### Option A: Polling

Poll the analysis endpoint until the status is `completed`:

<CodeGroup>
  ```bash curl theme={null}
  curl https://shelfforce.ai/api/v1/analyses/an_g7h8j9k0 \
    -H "Authorization: Bearer sf_live_a1b2c3d4..."
  ```

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

  analysis_id = "an_g7h8j9k0"

  while True:
      response = requests.get(
          f"https://shelfforce.ai/api/v1/analyses/{analysis_id}",
          headers={"Authorization": "Bearer sf_live_a1b2c3d4..."},
      )

      result = response.json()["data"]
      print(f"Status: {result['status']}")

      if result["status"] in ("completed", "failed"):
          break

      time.sleep(3)

  if result["status"] == "completed":
      print(f"Detected {result['productCount']} products")
      for product in result["products"]:
          print(f"  {product['brand']} — {product['description']} ({product['units']} units)")
  else:
      print(f"Analysis failed")
  ```

  ```typescript Node.js theme={null}
  const analysisId = "an_g7h8j9k0";

  let result;
  while (true) {
    const response = await fetch(
      `https://shelfforce.ai/api/v1/analyses/${analysisId}`,
      {
        headers: { "Authorization": "Bearer sf_live_a1b2c3d4..." },
      }
    );

    const { data } = await response.json();
    console.log(`Status: ${data.status}`);

    if (data.status === "completed" || data.status === "failed") {
      result = data;
      break;
    }

    await new Promise((resolve) => setTimeout(resolve, 3000));
  }

  if (result.status === "completed") {
    console.log(`Detected ${result.productCount} products`);
    for (const product of result.products) {
      console.log(`  ${product.brand} — ${product.description} (${product.units} units)`);
    }
  }
  ```
</CodeGroup>

### Option B: Webhooks

Register a webhook for `analysis.completed` and Shelfforce will POST the result to your URL as soon as processing finishes. No polling needed.

```bash theme={null}
# Register once
curl -X POST https://shelfforce.ai/api/v1/webhooks \
  -H "Authorization: Bearer sf_live_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/webhooks/shelfforce",
    "events": ["analysis.completed", "analysis.failed"]
  }'
```

When the analysis completes, you receive a POST like this:

```json theme={null}
{
  "event": "analysis.completed",
  "data": {
    "generationId": "an_g7h8j9k0",
    "status": "completed",
    "productCount": 12,
    "createdAt": "2026-02-23T10:30:00Z"
  },
  "timestamp": "2026-02-23T10:32:00Z",
  "webhookId": "wh_abc123"
}
```

Then fetch the full results using the `generationId`:

```bash theme={null}
curl https://shelfforce.ai/api/v1/analyses/an_g7h8j9k0 \
  -H "Authorization: Bearer sf_live_a1b2c3d4..."
```

See the [Webhooks guide](/webhooks) for full setup, payload examples, signature verification, and receiver code.

### Option C: Callback URL

Pass a `callbackUrl` when submitting an analysis to receive a one-off POST when it completes — no webhook registration needed.

```bash theme={null}
curl -X POST https://shelfforce.ai/api/v1/analyses \
  -H "Authorization: Bearer sf_live_a1b2c3d4..." \
  -H "Content-Type: application/json" \
  -d '{
    "imageUrl": "https://example.com/shelf.jpg",
    "externalId": "customer-123",
    "callbackUrl": "https://your-app.com/callbacks/shelfforce"
  }'
```

The response includes a `callbackSecret` you can use to verify the callback signature:

```json theme={null}
{
  "data": {
    "id": "an_g7h8j9k0",
    "status": "processing",
    "externalId": "customer-123",
    "callbackSecret": "a1b2c3d4e5f6...",
    "createdAt": "2026-02-23T10:30:00Z"
  }
}
```

<Warning>
  The `callbackSecret` is returned only once. Store it to verify the callback signature using the same HMAC-SHA256 scheme as [webhooks](/webhooks#signature-verification).
</Warning>

The callback URL must be HTTPS and cannot target private networks (localhost, 10.x, 192.168.x, etc.).

### List analyses

Query your analyses with filters using `GET /api/v1/analyses`:

```bash theme={null}
# Filter by externalId
curl "https://shelfforce.ai/api/v1/analyses?externalId=customer-123" \
  -H "Authorization: Bearer sf_live_a1b2c3d4..."

# Filter by status
curl "https://shelfforce.ai/api/v1/analyses?status=completed&limit=50" \
  -H "Authorization: Bearer sf_live_a1b2c3d4..."
```

| Parameter    | Type      | Description                                            |
| ------------ | --------- | ------------------------------------------------------ |
| `externalId` | `string`  | Filter by your external reference ID.                  |
| `status`     | `string`  | Filter by status: `processing`, `completed`, `failed`. |
| `placeId`    | `string`  | Filter by place ID.                                    |
| `cursor`     | `string`  | Pagination cursor from a previous response.            |
| `limit`      | `integer` | Max results per page (default 50, max 100).            |

### Completed response

```json theme={null}
{
  "data": {
    "id": "an_g7h8j9k0",
    "status": "completed",
    "totalRuns": 1,
    "completedRuns": 1,
    "failedRuns": 0,
    "productCount": 12,
    "createdAt": "2026-02-23T10:30:00Z",
    "completedAt": "2026-02-23T10:32:00Z",
    "products": [
      {
        "id": "prd_k1l2m3",
        "sku": "CC-330ML-CAN",
        "brand": "Coca-Cola",
        "description": "Coca-Cola Original 330ml Can",
        "category": "Beverages",
        "subcategory": "Carbonated Soft Drinks",
        "price": 1.49,
        "currency": "USD",
        "onSale": false,
        "units": 4,
        "confidence": 0.95,
        "shelfPosition": "eye-level",
        "fixtureType": "shelf"
      }
    ]
  }
}
```

## Step 3: Work with product data

### Product fields

Each product in the `products` array includes:

| Field             | Type              | Description                                                              |
| ----------------- | ----------------- | ------------------------------------------------------------------------ |
| `id`              | `string`          | Unique product detection identifier                                      |
| `sku`             | `string \| null`  | Matched SKU from your inventory, if identified                           |
| `brand`           | `string`          | Detected brand name                                                      |
| `description`     | `string`          | Full product description as detected on shelf                            |
| `category`        | `string \| null`  | Product category                                                         |
| `subcategory`     | `string \| null`  | Product subcategory                                                      |
| `price`           | `number \| null`  | Detected shelf price. Null if no price tag was found                     |
| `discountedPrice` | `number \| null`  | Promotional/sale price if on sale                                        |
| `currency`        | `string \| null`  | ISO 4217 currency code                                                   |
| `onSale`          | `boolean \| null` | Whether the product appears to be on promotion                           |
| `units`           | `number`          | Number of visible facings on the shelf                                   |
| `confidence`      | `number \| null`  | Detection confidence score (0.0 to 1.0)                                  |
| `shelfPosition`   | `string \| null`  | Vertical position: `top`, `eye-level`, `middle`, `bottom`                |
| `fixtureType`     | `string \| null`  | Where the product was found: `shelf`, `fridge`, `floor-display`, `other` |

### Query products across analyses

To retrieve products across multiple analyses, use the products endpoint with optional filters:

<CodeGroup>
  ```bash curl theme={null}
  # By analysis
  curl "https://shelfforce.ai/api/v1/products?analysisId=an_g7h8j9k0" \
    -H "Authorization: Bearer sf_live_a1b2c3d4..."

  # By brand
  curl "https://shelfforce.ai/api/v1/products?brand=Coca-Cola" \
    -H "Authorization: Bearer sf_live_a1b2c3d4..."

  # With pagination
  curl "https://shelfforce.ai/api/v1/products?limit=50" \
    -H "Authorization: Bearer sf_live_a1b2c3d4..."
  ```

  ```python Python theme={null}
  # By brand
  response = requests.get(
      "https://shelfforce.ai/api/v1/products",
      headers={"Authorization": "Bearer sf_live_a1b2c3d4..."},
      params={"brand": "Coca-Cola", "limit": 50},
  )

  data = response.json()
  products = data["data"]
  has_more = data["pagination"]["hasMore"]
  ```

  ```typescript Node.js theme={null}
  // By brand
  const response = await fetch(
    "https://shelfforce.ai/api/v1/products?brand=Coca-Cola&limit=50",
    {
      headers: { "Authorization": "Bearer sf_live_a1b2c3d4..." },
    }
  );

  const { data: products, pagination } = await response.json();
  console.log(`${products.length} products, hasMore: ${pagination.hasMore}`);
  ```
</CodeGroup>

## Step 4: View reports

Query aggregated share-of-shelf reports:

<CodeGroup>
  ```bash curl theme={null}
  curl "https://shelfforce.ai/api/v1/reports/share-of-shelf?days=30&brands=Coca-Cola,Pepsi" \
    -H "Authorization: Bearer sf_live_a1b2c3d4..."
  ```

  ```python Python theme={null}
  response = requests.get(
      "https://shelfforce.ai/api/v1/reports/share-of-shelf",
      headers={"Authorization": "Bearer sf_live_a1b2c3d4..."},
      params={"days": 30, "brands": "Coca-Cola,Pepsi"},
  )

  report = response.json()["data"]
  for brand in report["brands"]:
      print(f"{brand['brand']}: {brand['sharePercent']}% ({brand['units']} units)")
  ```

  ```typescript Node.js theme={null}
  const response = await fetch(
    "https://shelfforce.ai/api/v1/reports/share-of-shelf?days=30&brands=Coca-Cola,Pepsi",
    {
      headers: { "Authorization": "Bearer sf_live_a1b2c3d4..." },
    }
  );

  const { data: report } = await response.json();
  report.brands.forEach((brand) => {
    console.log(`${brand.brand}: ${brand.sharePercent}% (${brand.units} units)`);
  });
  ```
</CodeGroup>

```json theme={null}
{
  "data": {
    "brands": [
      {
        "brand": "Coca-Cola",
        "units": 342,
        "sharePercent": 28.5,
        "productCount": 8
      },
      {
        "brand": "Pepsi",
        "units": 288,
        "sharePercent": 24.0,
        "productCount": 6
      }
    ],
    "totalUnits": 1200,
    "periodDays": 30,
    "periodStart": "2026-01-24T00:00:00Z",
    "periodEnd": "2026-02-23T23:59:59Z"
  }
}
```

## Image requirements

For best results, follow these guidelines when capturing shelf images:

<CardGroup cols={2}>
  <Card title="Do" icon="check">
    * Capture the full shelf section in frame
    * Ensure products and price tags are legible
    * Use good lighting with minimal glare
    * Shoot from directly in front of the shelf
  </Card>

  <Card title="Avoid" icon="x">
    * Blurry or out-of-focus images
    * Extreme angles that distort product labels
    * Heavy shadows or reflections obscuring products
    * Partial shelf captures that cut off products
  </Card>
</CardGroup>

**Supported formats:** JPEG, PNG, WebP

**Recommended resolution:** 1920x1080 or higher. Higher resolution images produce more accurate results, especially for reading price tags.

## Analysis statuses

| Status       | Description                                                                  |
| ------------ | ---------------------------------------------------------------------------- |
| `processing` | The analysis is running. Poll again or wait for a webhook.                   |
| `completed`  | The analysis finished successfully. Products and summary are available.      |
| `failed`     | The analysis could not produce results. Check the `error` field for details. |

## Next steps

<CardGroup cols={2}>
  <Card title="Batch Analysis" icon="gallery-horizontal-end" href="/guides/batch-analysis">
    Process up to 20 images in a single request.
  </Card>

  <Card title="Webhooks" icon="bell" href="/webhooks">
    Get notified when analyses complete instead of polling.
  </Card>

  <Card title="Field Operations" icon="clipboard-list" href="/guides/field-operations">
    Assign shelf audits to field reps and track compliance.
  </Card>

  <Card title="Agent Integration" icon="bot" href="/guides/agent-integration">
    Connect Shelfforce to AI agents and automated pipelines.
  </Card>
</CardGroup>
