SKU Detection Predict API

Retail shelf image analysis — detect every product and classify it to its SKU

The SKU Detection API analyses retail shelf images using GPU-accelerated machine learning. Send a single shelf photo and receive per-product bounding boxes, SKU labels, confidence scores, share-of-shelf analytics, and an annotated image — all in one call.

🎯

Product Detection

Detect every product on a retail shelf using YOLOv8

🏷️

SKU Classification

Classify each detection to a specific SKU with confidence scores

📊

Shelf Analytics

Share-of-shelf (SOS) and on-shelf availability (OSA) per SKU

🖼️

Annotated Image

Receive the image back with bounding boxes and labels drawn on it

🧠 How it works

Each image goes through a three-stage pipeline: YOLOv8 object detection → DINOv2 + CLIP feature embedding → Logistic Regression SKU classification. The entire pipeline runs on GPU and typically completes in 1–5 seconds.

Authentication

All API requests require an API key passed in the X-API-Key header. Each API key is scoped to a single tenant and carries a monthly request quota.

Required Header
X-API-Key: your-api-key-here
⚠️ Keep your key secret

Your API key grants access to GPU resources and counts against your monthly quota. Never expose it in client-side code or public repositories.

🔑 Getting an API Key

Contact the Recognition Alpha team to obtain your API key and configure your tenant account.

Base URL

https://api.iralpha.core88.ai

All endpoints use HTTPS. HTTP requests are not accepted.

Predict Endpoint

POST /api/v1/predict

Submit a shelf image for SKU detection and classification. The API detects every product in the image, classifies each detection to its SKU, computes share-of-shelf analytics, and returns an annotated image with bounding boxes.

Request Body

Send a JSON body with Content-Type: application/json.

Parameter Type Description
image Required string Image data — accepts a raw base64-encoded string, a data:image/...;base64,... data URI, or an HTTP(S) URL pointing to an image file. Supported formats: JPEG, PNG, WebP.
category Required string Product category to classify against (e.g., "DRINKS", "CHIPS", "LIQUID_HAND_SOAP"). Must match a category with a trained model.
brand Required string Brand name within the category (e.g., "JORDINA", "HIGEEN"). Must match a brand with a trained model.
sku_conf Optional float Minimum SKU classification confidence threshold (0.0–1.0). Detections below this threshold are excluded from results (or marked as unknown when show_unknown is true). Default: 0.5
det_conf Optional float Minimum YOLO detection confidence threshold (0.0–1.0). Lower values detect more objects but may include false positives. Default: 0.25
show_unknown Optional boolean When true, include detections that fall below the sku_conf threshold in the response (labeled as unknown). They appear with red bounding boxes in the annotated image. Default: false
return_annotated Optional boolean When true, include the annotated_image_base64 field in the response — a JPEG image with bounding boxes and labels drawn. Set to false to reduce response size. Default: true

Response

Success Response (200)

JSON Response
{
  "success": true,
  "detections": [
    {
      "sku": "JORDINA_APPLE_500ML",
      "sku_conf": 0.94,
      "yolo_conf": 0.89,
      "bbox": [120, 45, 280, 320]
    },
    {
      "sku": "JORDINA_ORANGE_500ML",
      "sku_conf": 0.91,
      "yolo_conf": 0.87,
      "bbox": [300, 50, 460, 315]
    }
  ],
  "analysis": {
    "total": 2,
    "counts": {
      "JORDINA_APPLE_500ML": 1,
      "JORDINA_ORANGE_500ML": 1
    },
    "sos": {
      "JORDINA_APPLE_500ML": 50.0,
      "JORDINA_ORANGE_500ML": 50.0
    },
    "osa": {
      "JORDINA_APPLE_500ML": 1,
      "JORDINA_ORANGE_500ML": 1
    }
  },
  "annotated_image_base64": "/9j/4AAQSkZJRg..."
}

Top-Level Fields

Field Type Description
success boolean true when the pipeline completed successfully
detections array Array of detected and classified products (empty array if none found)
analysis object Shelf analytics — counts, share-of-shelf, and on-shelf availability
annotated_image_base64 string Base64-encoded JPEG of the input image with bounding boxes and SKU labels drawn. Omitted when return_annotated is false.

Detection Object

Field Type Description
sku string Classified SKU identifier (e.g., "JORDINA_APPLE_500ML")
sku_conf float SKU classification confidence (0.0–1.0)
yolo_conf float YOLO object detection confidence (0.0–1.0)
bbox array Bounding box as [x1, y1, x2, y2] pixel coordinates (top-left to bottom-right)

Analysis Object

Field Type Description
total integer Total number of classified detections
counts object Map of SKU → count — how many of each SKU were detected
sos object Share of Shelf — map of SKU → percentage (0.0–100.0). Each SKU's facing count as a percentage of total detected facings.
osa object On-Shelf Availability — map of SKU → 0|1. 1 if the SKU was found on the shelf, 0 if absent.

Error Response

JSON Error Response
{
  "success": false,
  "error": "Failed to load model for category 'SNACKS' and brand 'UNKNOWN': Models directory not found"
}

When success is false, the error field contains a human-readable description of what went wrong.

Response Headers

Every response from the predict endpoint includes rate-limit headers so you can track your monthly usage.

Header Description
X-RateLimit-Limit Your total monthly request quota
X-RateLimit-Remaining Requests remaining this billing month
X-RateLimit-Used Requests used so far this billing month
💡 Billing Note

Only successful predictions (HTTP 2xx) count against your monthly quota. Failed requests and errors do not consume your limit.

Status Endpoint

GET /api/v1/status

Check whether the GPU inference backend is reachable. Useful for health checks before sending prediction requests. Requires the same X-API-Key header.

JSON Response
{
  "gpu_status": "online",
  "gpu_endpoint": "https://...",
  "response_time_ms": 124.5
}

cURL Example

bash
# Base64 encode your image
IMAGE_BASE64=$(base64 -i shelf_photo.jpg | tr -d '\n')

# Make the API request
curl -X POST 'https://api.iralpha.core88.ai/api/v1/predict' \
  -H 'Content-Type: application/json' \
  -H 'X-API-Key: your-api-key-here' \
  -d "{
    \"image\": \"$IMAGE_BASE64\",
    \"category\": \"DRINKS\",
    \"brand\": \"JORDINA\",
    \"sku_conf\": 0.5,
    \"det_conf\": 0.25,
    \"show_unknown\": false,
    \"return_annotated\": true
  }"

Python Example

python
import requests
import base64

API_URL = "https://api.iralpha.core88.ai/api/v1/predict"
API_KEY = "your-api-key-here"

# Read and encode the shelf image
with open("shelf_photo.jpg", "rb") as f:
    image_base64 = base64.b64encode(f.read()).decode("utf-8")

# Prepare request
headers = {
    "Content-Type": "application/json",
    "X-API-Key": API_KEY,
}

payload = {
    "image": image_base64,
    "category": "DRINKS",
    "brand": "JORDINA",
    "sku_conf": 0.5,
    "det_conf": 0.25,
    "show_unknown": False,
    "return_annotated": True,
}

# Send request
response = requests.post(API_URL, json=payload, headers=headers, timeout=60)
result = response.json()

if result["success"]:
    # Print detected SKUs
    print(f"Detected {result['analysis']['total']} products:")
    for det in result["detections"]:
        print(f"  {det['sku']}  conf={det['sku_conf']:.0%}  box={det['bbox']}")

    # Print share-of-shelf
    print("\nShare of Shelf:")
    for sku, pct in result["analysis"]["sos"].items():
        if pct > 0:
            print(f"  {sku}: {pct}%")

    # Save annotated image (optional)
    if "annotated_image_base64" in result:
        img_bytes = base64.b64decode(result["annotated_image_base64"])
        with open("annotated_shelf.jpg", "wb") as f:
            f.write(img_bytes)
        print("\nAnnotated image saved to annotated_shelf.jpg")

    # Check monthly usage from response headers
    print(f"\nQuota: {response.headers.get('X-RateLimit-Used')}"
          f" / {response.headers.get('X-RateLimit-Limit')} used this month")
else:
    print(f"Error: {result['error']}")

JavaScript Example

javascript
const API_URL = "https://api.iralpha.core88.ai/api/v1/predict";
const API_KEY = "your-api-key-here";

async function classifyShelf(imageFile) {
  // Convert file to base64
  const base64 = await new Promise((resolve, reject) => {
    const reader = new FileReader();
    reader.onload = () => resolve(reader.result.split(",")[1]);
    reader.onerror = reject;
    reader.readAsDataURL(imageFile);
  });

  const response = await fetch(API_URL, {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": API_KEY,
    },
    body: JSON.stringify({
      image: base64,
      category: "DRINKS",
      brand: "JORDINA",
      sku_conf: 0.5,
      det_conf: 0.25,
      show_unknown: false,
      return_annotated: true,
    }),
  });

  const result = await response.json();

  if (!result.success) {
    throw new Error(result.error);
  }

  // Log detected products
  console.log(`Detected ${result.analysis.total} products`);
  result.detections.forEach((det) => {
    console.log(`  ${det.sku} — ${(det.sku_conf * 100).toFixed(0)}%`);
  });

  // Display annotated image
  if (result.annotated_image_base64) {
    const img = document.createElement("img");
    img.src = `data:image/jpeg;base64,${result.annotated_image_base64}`;
    document.body.appendChild(img);
  }

  // Check monthly quota from response headers
  console.log(
    `Quota: ${response.headers.get("X-RateLimit-Used")}` +
    ` / ${response.headers.get("X-RateLimit-Limit")} used`
  );

  return result;
}

// Usage with a file input
document.getElementById("imageInput").addEventListener("change", async (e) => {
  const result = await classifyShelf(e.target.files[0]);
  console.log("Analysis:", result.analysis);
});

Error Codes

Status Error Description
400 Bad Request Missing or invalid field in the request body (e.g., missing image, category, or brand)
401 Unauthorized Missing or invalid X-API-Key header
403 Forbidden API key is suspended or revoked, or tenant is inactive
404 Not Found No trained model found for the specified category / brand combination
429 Rate Limited Monthly request quota exceeded. Check X-RateLimit-Limit header.
502 Bad Gateway GPU backend returned a non-JSON or invalid response
503 Service Unavailable GPU backend is unavailable — instance may be starting up (cold start)
504 Gateway Timeout GPU backend did not respond within 60 seconds
⚠️ Cold Start Handling

If you receive a 503 error, the GPU instance may be warming up. Wait 30–60 seconds and retry. We recommend implementing retry logic with exponential backoff in production integrations.

Rate Limits & Constraints

Request Timeout

60 seconds

Max Image Size

5 MB

Typical Processing

1–5 seconds

Supported Formats

JPEG, PNG, WebP

Monthly Quota

Per tenant

Billing Period

Calendar month

💡 Performance Tips
  • Resize images to ~1024px max dimension before encoding to reduce payload and speed up processing
  • Use JPEG format for smaller base64 payloads
  • Set return_annotated: false if you don't need the visual output — saves bandwidth
  • Implement retry logic with exponential backoff for 503/504 errors
  • Use the GET /api/v1/status endpoint to check GPU availability before sending batches

Changelog

v2.0.0 April 2026
  • • New response format — detections[] + analysis{} replaces flat predictions[]
  • • Share-of-shelf (SOS) and on-shelf availability (OSA) analytics in every response
  • • Annotated image output — bounding boxes and labels drawn on the original image
  • return_annotated parameter to control annotated image inclusion
  • show_unknown parameter to include low-confidence detections
  • • Image input now accepts URLs and data URIs in addition to raw base64
  • • Monthly rate-limit headers (X-RateLimit-*) on every response
  • GET /api/v1/status endpoint for GPU health checks
  • • Persistent connection pooling for improved throughput
v1.0.0 February 2026
  • • Initial release
  • • POST /api/v1/predict endpoint
  • • Support for DRINKS, CHIPS, LIQUID_HAND_SOAP categories