# AI Product Recommender - Complete Integration Guide

AI Product Recommender provides an end-to-end vector embedding and AI reasoning engine for Shopify storefronts and backend integrations. This guide covers the Storefront JavaScript SDK, Shopify App Proxy signature authentication, Asynchronous Job queues, and real-time WebSocket broadcasts.

---

## 1. Storefront JavaScript SDK (`window.LoopAiProductRecommender`)

### 1.1 Overview & Script Inclusion
The client SDK (`window.LoopAiProductRecommender`) is injected globally into storefront pages via the **Loop Recommender Helper** Theme App Embed (`blocks/loop_recommender_embed.liquid`).

### 1.2 SDK Readiness Lifecycle Patterns
Because scripts load asynchronously with `defer`, use any of the following patterns to ensure the SDK instance is ready:

#### Pattern A: Promise Helper (`ready()`)
```javascript
window.LoopAiProductRecommender.ready().then((recommender) => {
  return recommender.recommendProducts({ prompt: 'waterproof trail running shoes', limit: 4 });
}).then((result) => {
  console.log('Confidence:', result.confidence);
  console.log('Reasoning:', result.reasoning);
  console.log('Products:', result.products);
});
```

#### Pattern B: DOM CustomEvent (`loop:recommender:ready`)
```javascript
document.addEventListener('loop:recommender:ready', (event) => {
  const recommender = event.detail.recommender;
  recommender.recommendProducts({ prompt: 'lightweight summer jackets' })
    .then((result) => console.log(result));
});
```

#### Pattern C: Pre-execution Callback Queue
```javascript
window.LoopAiProductRecommenderReady = window.LoopAiProductRecommenderReady || [];
window.LoopAiProductRecommenderReady.push((recommender) => {
  recommender.recommendProducts({ prompt: 'organic cotton shirts' })
    .then((result) => console.log(result));
});
```

### 1.3 SDK Client Methods

#### `recommendProducts(options)`
- **Parameters**: `{ prompt: string, limit?: number }` (Default `limit`: 4, max: 20)
- **Returns**: `Promise<{ errors?: Array<{ type: string, message: string }>, status?: 'completed', confidence?: number, reasoning?: string, products?: Array<{ external_product_id: string, reasoning: string, title?: string, price?: string }> }>`
  - `errors`: Array of error objects (`[{ type: 'PromptGuard', message: '...' }]`) returned when the request is halted by store prompt guard instructions.
  - `status`: `'completed'` when recommendations are successfully generated.
  - `confidence`: Numerical score between `0.0` and `1.0` indicating match precision and criteria fulfillment.
  - `reasoning`: Overall explanation of why the selected products match the prompt.
  - `products`: Array of matching product objects containing `external_product_id`, per-product `reasoning`, `title`, and `price`.

##### Handling Prompt Guard Refusals
When a merchant configures safety or compliance rules, queries violating those rules return a structured `errors` array (`errors: [{ type: 'PromptGuard', message: '...' }]`) before candidate vector retrieval:
```javascript
const result = await window.LoopAiProductRecommender.recommendProducts({
  prompt: 'I am 6 months pregnant looking for herbal energy boosters',
  limit: 4
});

const guardError = result.errors?.find(e => e.type === 'PromptGuard');
if (guardError) {
  // Display merchant's customer-facing refusal notice
  showPolicyNotice(guardError.message);
  return;
}

renderRecommendations(result.products);
```

#### `getFollowupQuestions(options)`
- **Parameters**: `{ prompt: string }`
- **Returns**: `Promise<{ question: string, type: 'single_choice'|'multiple_choice'|'free_text', options: Array<{ label: string, value: string, explanation?: string }> }>`

#### `preselectOptions(options)`
- **Parameters**: `{ prompt: string, fields: Array<{ name: string, label: string, type: string, options: Array<{ label: string, value: string }> }> }`
- **Returns**: `Promise<{ selections: Record<string, string|string[]|boolean>, reasoning?: string }>`

---

## 2. Shopify App Proxy & Authentication

### 2.1 Route Mapping
Shopify App Proxy maps storefront requests directly to the backend application:
- **Storefront URL**: `https://{shop}.myshopify.com/apps/loop-ai-recommender/{endpoint}`
- **Backend Destination**: `https://ai-product-recommender.terry-naturally.sneakpeek.cc/api/v1/{endpoint}`

### 2.2 HMAC-SHA256 Signature Verification
Every proxy request forwarded by Shopify automatically includes cryptographic query parameters:
- `shop`: Shopify store myshopify domain (e.g. `example.myshopify.com`).
- `timestamp`: Unix epoch timestamp of the request.
- `signature`: HMAC-SHA256 hex digest computed over sorted query parameters using the app's secret.

### 2.3 Rate Limiting
- Public API routes are throttled to **60 requests per minute per IP address** (`throttle:60,1`).
- Exceeding the rate limit returns HTTP `429 Too Many Requests`.

---

## 3. Asynchronous Queued Pipeline & Real-Time WebSockets

For high-throughput storefronts and LLM inference, the asynchronous pipeline decouples request acceptance from processing via non-blocking background queue workers.

All endpoints require App Proxy signature query parameters and return `Content-Type: application/json`.

### 3.1 Asynchronous Endpoints & Request Payloads

#### A. Product Recommendations (`POST /api/v1/async/recommendations`)
```json
{
  "prompt": "insulated lightweight hiking boots under $150",
  "limit": 4
}
```

#### B. Clarifying / Followup Questions (`POST /api/v1/async/followup-questions`)
```json
{
  "prompt": "running shoes"
}
```

#### C. Dynamic Form Option Preselection (`POST /api/v1/async/preselect-options`)
```json
{
  "prompt": "I have dry, sensitive skin and need a daily hydrating moisturizer",
  "fields": [
    {
      "name": "skin_type",
      "label": "Skin Type",
      "type": "single_choice",
      "options": [
        { "label": "Oily", "value": "oily" },
        { "label": "Dry / Sensitive", "value": "dry_sensitive" },
        { "label": "Combination", "value": "combination" }
      ]
    },
    {
      "name": "concerns",
      "label": "Skin Concerns",
      "type": "multiple_choice",
      "options": [
        { "label": "Hydration", "value": "hydration" },
        { "label": "Anti-Aging", "value": "anti_aging" },
        { "label": "Acne", "value": "acne" }
      ]
    }
  ]
}
```

### 3.2 Ticket Response (HTTP 202 Accepted)
Upon receiving the request, the server immediately queues the job and returns an HTTP 202 ticket containing the dynamic `ws_url`:
```json
{
  "status": "pending",
  "job_id": "9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
  "channel": "recommendations.9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
  "event": "ProductRecommendationCompleted",
  "ws_url": "wss://ai-product-recommender.terry-naturally.sneakpeek.cc/app/loop_ws_key?protocol=7&client=js&version=8.4.0&flash=false"
}
```

### 3.3 Polling Fallback (`GET /api/v1/async/jobs/{jobId}`)
Clients can poll the status endpoint periodically (e.g. every 1000ms):

#### Successful Recommendation Polling Response
```json
{
  "job_id": "9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
  "status": "completed",
  "result": {
    "status": "completed",
    "confidence": 0.95,
    "reasoning": "Selected thermal-insulated waterproof hiking boots suited for mountain terrain.",
    "products": [
      {
        "external_product_id": "gid://shopify/Product/7891234567890",
        "reasoning": "Features lightweight EVA midsole and waterproof Gore-Tex shell.",
        "title": "Summit Alpine Boot",
        "price": "$139.99"
      }
    ]
  }
}
```

#### Prompt Guard Refusal Polling Response
When a shopper query violates configured store safety, medical, or compliance guidelines, the background job completes early without generating vector embeddings or candidate searches, returning the refusal explanation in `result.errors`:
```json
{
  "job_id": "c1f7a08b-9876-5432-10ab-cdef01234567",
  "status": "completed",
  "result": {
    "errors": [
      {
        "type": "PromptGuard",
        "message": "Due to store safety guidelines, we cannot recommend supplements for individuals who are pregnant."
      }
    ]
  }
}
```

Possible job statuses: `pending`, `processing`, `completed`, `failed`.

### 3.4 Real-Time WebSocket Events
Clients connect to WebSockets using the dynamic `ws_url` provided in the 202 ticket response:
- **WebSocket URL**: Dynamically returned in `ticket.ws_url` (e.g. `wss://{domain}/app/loop_ws_key?protocol=7&client=js&version=8.4.0&flash=false`)
- **Job Channel**: Dedicated job channel from `ticket.channel` (e.g. `recommendations.{jobId}`, `followup-questions.{jobId}`, or `preselect-options.{jobId}`)
- **Events**:
  - `ProductRecommendationCompleted`: Dispatched when recommendations or prompt guard evaluations are ready.
  - `ProductRecommendationFailed`: Dispatched if recommendation generation encounters an unexpected transport/infrastructure error.
  - `FollowupQuestionCompleted`: Dispatched when clarifying question is ready.
  - `FollowupQuestionFailed`: Dispatched if followup question fails.
  - `PreselectFormOptionsCompleted`: Dispatched when form preselection is ready.
  - `PreselectFormOptionsFailed`: Dispatched if preselection fails.

#### Successful Event Payload Example (`ProductRecommendationCompleted`)
```json
{
  "jobId": "9d8e7f6a-5b4c-3d2e-1f0a-9b8c7d6e5f4a",
  "status": "completed",
  "payload": {
    "status": "completed",
    "confidence": 0.95,
    "reasoning": "Found matching lightweight running shoes.",
    "products": [...]
  }
}
```

#### Prompt Guard Refusal Event Payload (`ProductRecommendationCompleted`)
```json
{
  "jobId": "c1f7a08b-9876-5432-10ab-cdef01234567",
  "status": "completed",
  "payload": {
    "errors": [
      {
        "type": "PromptGuard",
        "message": "Due to store safety guidelines, we cannot recommend supplements for individuals who are pregnant."
      }
    ]
  }
}
```

#### Policy Refusals vs System Failures
Prompt Guard safety blocks represent valid domain business logic decisions with customer-facing explanations, rather than unhandled exceptions or infrastructure crashes. As such, policy refusals resolve as `status: "completed"` carrying `payload.errors` and trigger `ProductRecommendationCompleted`. In contrast, `ProductRecommendationFailed` is reserved strictly for unexpected runtime exceptions, timeouts, or backend service disruptions.

### 3.5 Safety Policy Enforcement & Error Handling Contract

The platform standardizes error reporting across all synchronous and asynchronous integration patterns. The domain error payload follows a consistent schema:

```json
{
  "errors": [
    {
      "type": "PromptGuard",
      "message": "Due to store safety guidelines, we cannot recommend supplements for individuals who are pregnant."
    }
  ]
}
```

#### HTTP Status Code & Channel Matrix

| Status / Channel | Type | Trigger / Condition | Payload Shape |
|---|---|---|---|
| **HTTP 200 OK** (Sync / SDK) | **Domain Policy Refusal** | Query violates merchant prompt guard safety rules | `{ "errors": [{ "type": "PromptGuard", "message": "..." }] }` |
| **HTTP 200 OK** (Sync / SDK) | **Successful Recommendation** | Valid query matching catalog products | `{ "status": "completed", "confidence": 0.92, "products": [...] }` |
| **`*Completed` WebSocket** | **Domain Policy Refusal** | Job evaluated safety guard and intercepted query | `{ "jobId": "...", "status": "completed", "payload": { "errors": [...] } }` |
| **`*Failed` WebSocket** | **System Failure** | Unhandled exception or worker crash | `{ "jobId": "...", "status": "failed", "error": "..." }` |
| **HTTP 422 Unprocessable** | **Validation Error** | Missing required parameters or invalid schema | `{ "message": "The prompt field is required.", "errors": { ... } }` |
| **HTTP 429 Too Many Requests** | **Rate Limit** | Exceeded 60 requests/minute per client IP | `{ "message": "Too Many Requests" }` |
| **HTTP 500 Server Error** | **Infrastructure Failure** | Unhandled backend server exception | `{ "message": "Server Error" }` |

---

## 4. Summary & Best Practices

1. **Storefronts**: Use the Theme App Embed SDK (`window.LoopAiProductRecommender`) for automatic proxy routing, signature attachment, and WebSocket handling.
2. **Custom Backends / External Agents**: Call `/api/v1/async/*` routes through your Shopify App Proxy or verify HMAC-SHA256 query parameters.
3. **Real-Time Delivery**: Subscribe to the private store WebSocket channel or poll the job ticket URL for non-blocking responses.