Products API

The Products API enables semantic search and retrieval of products from the catalog.


Endpoints

MethodEndpointDescription
GET/acp/productsSearch products with filters
GET/acp/products/searchSearch products (alias)
GET/acp/products/{product_id}Get single product

Search Products

Search for products using natural language queries and filters.

GET /acp/products
GET /acp/products/search

Parameters

ParameterTypeRequiredDescription
qstringNoNatural language search query (uses semantic search)
categorystringNoFilter by category (case-insensitive partial match)
tagstringNoFilter by tag
min_pricenumberNoMinimum price (inclusive)
max_pricenumberNoMaximum price (inclusive)
pageintegerNoPage number (default: 1)
page_sizeintegerNoResults per page (default: 10, max: 100)

Response

{
  "items": [
    {
      "id": "string",
      "title": "string",
      "description": "string",
      "vendor": "string",
      "category": "string",
      "price": 0.00,
      "currency": "USD",
      "availability": {
        "status": "in_stock",
        "quantity": 0
      },
      "tags": ["string"],
      "attributes": {
        "Material": ["Cotton", "Polyester"]
      },
      "images": [
        {
          "url": "string",
          "alt_text": "string",
          "position": 0
        }
      ],
      "variants": [
        {
          "sku": "string",
          "title": "string",
          "price": 0.00,
          "currency": "USD",
          "attributes": {
            "Size": "M",
            "Color": "Black"
          },
          "inventory": {
            "status": "in_stock",
            "quantity": 0
          }
        }
      ],
      "similarity_score": 0.00
    }
  ],
  "page": 1,
  "page_size": 10,
  "total": 0,
  "version": "string",
  "generated_at": "2024-01-15T10:30:00Z"
}

Examples

cURL

# Basic semantic search
curl "https://{{YOUR_STORE_URL}}/acp/products?q=comfortable+running+shoes" \
  -H "Accept: application/json" \
  -H "X-ACP-API-Key: {{YOUR_API_KEY}}"
 
# With price filter
curl "https://{{YOUR_STORE_URL}}/acp/products?q=wireless+headphones&min_price=50&max_price=200" \
  -H "Accept: application/json" \
  -H "X-ACP-API-Key: {{YOUR_API_KEY}}"
 
# Category filter
curl "https://{{YOUR_STORE_URL}}/acp/products?category=Electronics&page_size=20" \
  -H "Accept: application/json" \
  -H "X-ACP-API-Key: {{YOUR_API_KEY}}"

Python

import requests
 
BASE_URL = "https://{{YOUR_STORE_URL}}"
HEADERS = {
    "Accept": "application/json",
    "X-ACP-API-Key": "{{YOUR_API_KEY}}"
}
 
# Semantic search
response = requests.get(
    f"{BASE_URL}/acp/products",
    params={
        "q": "comfortable running shoes",
        "max_price": 150
    },
    headers=HEADERS
)
 
products = response.json()
 
for product in products["items"]:
    print(f"{product['title']} - ${product['price']}")
    print(f"  Relevance: {product.get('similarity_score', 'N/A')}")

TypeScript

const BASE_URL = "https://{{YOUR_STORE_URL}}";
const API_KEY = "{{YOUR_API_KEY}}";
 
interface Product {
  id: string;
  title: string;
  price: number;
  similarity_score?: number;
}
 
interface SearchResponse {
  items: Product[];
  page: number;
  page_size: number;
  total: number;
}
 
async function searchProducts(query: string, maxPrice?: number): Promise<SearchResponse> {
  const params = new URLSearchParams({ q: query });
  if (maxPrice) params.append("max_price", maxPrice.toString());
 
  const response = await fetch(`${BASE_URL}/acp/products?${params}`, {
    headers: {
      "Accept": "application/json",
      "X-ACP-API-Key": API_KEY
    }
  });
 
  return response.json();
}
 
// Usage
const results = await searchProducts("wireless headphones", 200);
console.log(`Found ${results.total} products`);

Java

import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
 
public class LornClient {
    private static final String BASE_URL = "https://{{YOUR_STORE_URL}}";
    private static final String API_KEY = "{{YOUR_API_KEY}}";
    private final HttpClient client = HttpClient.newHttpClient();
 
    public String searchProducts(String query, Double maxPrice) throws Exception {
        StringBuilder url = new StringBuilder(BASE_URL + "/acp/products?");
        url.append("q=").append(URLEncoder.encode(query, StandardCharsets.UTF_8));
        
        if (maxPrice != null) {
            url.append("&max_price=").append(maxPrice);
        }
 
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url.toString()))
            .header("Accept", "application/json")
            .header("X-ACP-API-Key", API_KEY)
            .GET()
            .build();
 
        HttpResponse<String> response = client.send(request, 
            HttpResponse.BodyHandlers.ofString());
        
        return response.body();
    }
}
 
// Usage
LornClient client = new LornClient();
String results = client.searchProducts("running shoes", 150.0);
System.out.println(results);

Go

package main
 
import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "net/url"
)
 
const (
    baseURL = "https://{{YOUR_STORE_URL}}"
    apiKey  = "{{YOUR_API_KEY}}"
)
 
type Product struct {
    ID              string   `json:"id"`
    Title           string   `json:"title"`
    Price           float64  `json:"price"`
    SimilarityScore *float64 `json:"similarity_score,omitempty"`
}
 
type SearchResponse struct {
    Items    []Product `json:"items"`
    Page     int       `json:"page"`
    PageSize int       `json:"page_size"`
    Total    int       `json:"total"`
}
 
func searchProducts(query string, maxPrice *float64) (*SearchResponse, error) {
    params := url.Values{}
    params.Add("q", query)
    if maxPrice != nil {
        params.Add("max_price", fmt.Sprintf("%.2f", *maxPrice))
    }
 
    req, err := http.NewRequest("GET", baseURL+"/acp/products?"+params.Encode(), nil)
    if err != nil {
        return nil, err
    }
 
    req.Header.Set("Accept", "application/json")
    req.Header.Set("X-ACP-API-Key", apiKey)
 
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
 
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
 
    var result SearchResponse
    if err := json.Unmarshal(body, &result); err != nil {
        return nil, err
    }
 
    return &result, nil
}
 
func main() {
    maxPrice := 150.0
    results, err := searchProducts("comfortable running shoes", &maxPrice)
    if err != nil {
        panic(err)
    }
 
    fmt.Printf("Found %d products\n", results.Total)
    for _, p := range results.Items {
        fmt.Printf("- %s: $%.2f\n", p.Title, p.Price)
    }
}

Get Product

Retrieve a single product by ID.

GET /acp/products/{product_id}

Path Parameters

ParameterTypeRequiredDescription
product_idstringYesThe product ID (lorn_id)

Response

Returns a single Product object with all details.

{
  "id": "prod_abc123",
  "title": "Nike Air Zoom Pegasus 40",
  "description": "Responsive cushioning meets a breathable upper...",
  "vendor": "Nike",
  "category": "Footwear > Running",
  "price": 129.99,
  "currency": "USD",
  "availability": {
    "status": "in_stock",
    "quantity": 42
  },
  "tags": ["running", "athletic", "cushioned"],
  "attributes": {
    "Upper Material": ["Mesh"],
    "Cushioning": ["Zoom Air"]
  },
  "images": [
    {
      "url": "https://cdn.example.com/nike-pegasus-40-main.jpg",
      "alt_text": "Nike Air Zoom Pegasus 40 - Side View",
      "position": 1
    }
  ],
  "variants": [
    {
      "sku": "nike-pegasus-40-black-10",
      "title": "Black / Size 10",
      "price": 129.99,
      "currency": "USD",
      "requires_shipping": true,
      "taxable": true,
      "weight_grams": 280,
      "weight_unit": "g",
      "attributes": {
        "Color": "Black",
        "Size": "10"
      },
      "inventory": {
        "status": "in_stock",
        "quantity": 8
      }
    },
    {
      "sku": "nike-pegasus-40-white-10",
      "title": "White / Size 10",
      "price": 129.99,
      "currency": "USD",
      "attributes": {
        "Color": "White",
        "Size": "10"
      },
      "inventory": {
        "status": "in_stock",
        "quantity": 5
      }
    }
  ]
}

Examples

cURL

curl "https://{{YOUR_STORE_URL}}/acp/products/{{PRODUCT_ID}}" \
  -H "Accept: application/json" \
  -H "X-ACP-API-Key: {{YOUR_API_KEY}}"

Python

import requests
 
def get_product(product_id: str) -> dict:
    response = requests.get(
        f"{BASE_URL}/acp/products/{product_id}",
        headers=HEADERS
    )
    response.raise_for_status()
    return response.json()
 
# Usage
product = get_product("prod_abc123")
print(f"Product: {product['title']}")
print(f"Price: ${product['price']}")
print(f"Variants: {len(product['variants'])}")

TypeScript

async function getProduct(productId: string): Promise<Product> {
  const response = await fetch(`${BASE_URL}/acp/products/${productId}`, {
    headers: {
      "Accept": "application/json",
      "X-ACP-API-Key": API_KEY
    }
  });
 
  if (!response.ok) {
    throw new Error(`Product not found: ${productId}`);
  }
 
  return response.json();
}
 
// Usage
const product = await getProduct("prod_abc123");
console.log(`${product.title} - $${product.price}`);

Java

public String getProduct(String productId) throws Exception {
    HttpRequest request = HttpRequest.newBuilder()
        .uri(URI.create(BASE_URL + "/acp/products/" + productId))
        .header("Accept", "application/json")
        .header("X-ACP-API-Key", API_KEY)
        .GET()
        .build();
 
    HttpResponse<String> response = client.send(request, 
        HttpResponse.BodyHandlers.ofString());
    
    if (response.statusCode() == 404) {
        throw new RuntimeException("Product not found: " + productId);
    }
    
    return response.body();
}

Go

func getProduct(productID string) (*Product, error) {
    req, err := http.NewRequest("GET", baseURL+"/acp/products/"+productID, nil)
    if err != nil {
        return nil, err
    }
 
    req.Header.Set("Accept", "application/json")
    req.Header.Set("X-ACP-API-Key", apiKey)
 
    resp, err := http.DefaultClient.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
 
    if resp.StatusCode == 404 {
        return nil, fmt.Errorf("product not found: %s", productID)
    }
 
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
 
    var product Product
    if err := json.Unmarshal(body, &product); err != nil {
        return nil, err
    }
 
    return &product, nil
}

Object Reference

Product

FieldTypeDescription
idstringUnique product identifier
titlestringProduct name
descriptionstringFull product description
vendorstringBrand/manufacturer
categorystringProduct category
pricenumberBase price
currencystringCurrency code (ISO 4217)
availabilityInventoryStock status
tagsstring[]Product tags
attributesobjectProduct attributes (key: values[])
imagesImage[]Product images
variantsVariant[]Product variants
similarity_scorenumberSemantic search relevance (0-1)

Variant

FieldTypeDescription
skustringUnique variant SKU
titlestringVariant title
pricenumberVariant price
currencystringCurrency code
requires_shippingbooleanNeeds shipping
taxablebooleanIs taxable
weight_gramsnumberWeight in grams
weight_unitstringWeight unit
attributesobjectVariant attributes (key: value)
inventoryInventoryVariant stock

Inventory

FieldTypeDescription
statusstringin_stock, limited, out_of_stock
quantityintegerStock count
policystringInventory policy

Image

FieldTypeDescription
urlstringImage URL
alt_textstringAlt text
positionintegerDisplay order

Errors

StatusErrorDescription
400invalid_requestInvalid query parameters
404not_foundProduct not found
503service_unavailableSupabase not configured

Example Error

{
  "detail": "Product not found"
}

See Also