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
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.
X-API-Key: your-api-key-here
Your API key grants access to GPU resources and counts against your monthly quota. Never expose it in client-side code or public repositories.
Contact the Recognition Alpha team to obtain your API key and configure your tenant account.
Base URL
All endpoints use HTTPS. HTTP requests are not accepted.
Predict Endpoint
/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)
{
"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
{
"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 |
Only successful predictions (HTTP 2xx) count against your monthly quota. Failed requests and errors do not consume your limit.
Status Endpoint
/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.
{
"gpu_status": "online",
"gpu_endpoint": "https://...",
"response_time_ms": 124.5
}
cURL Example
# 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
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
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 |
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
- 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: falseif you don't need the visual output — saves bandwidth - Implement retry logic with exponential backoff for 503/504 errors
- Use the
GET /api/v1/statusendpoint to check GPU availability before sending batches
Changelog
- • New response format —
detections[]+analysis{}replaces flatpredictions[] - • 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_annotatedparameter to control annotated image inclusion - •
show_unknownparameter 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/statusendpoint for GPU health checks - • Persistent connection pooling for improved throughput
- • Initial release
- • POST /api/v1/predict endpoint
- • Support for DRINKS, CHIPS, LIQUID_HAND_SOAP categories