# Product API Guide ## Product API Usage Guide ## Overview The Product API v1 provides a comprehensive RESTful interface for accessing product information. All endpoints are public and do not require authentication for viewing products. **Base URL:** `http://localhost:3000/api/v1` (development) **API Version:** v1 **Content Type:** `application/json` ## Table of Contents 1. [Getting Started](#getting-started) 2. [Endpoints](#endpoints) 3. [Filtering & Sorting](#filtering--sorting) 4. [Pagination](#pagination) 5. [Code Examples](#code-examples) 6. [Error Handling](#error-handling) 7. [Rate Limiting](#rate-limiting) 8. [Best Practices](#best-practices) ## Getting Started ### Base URL ``` Development: http://localhost:3000/api/v1 Production: https://www.example.com/api/v1 ``` ### Response Format All responses follow a consistent format: **Success Response:** ```json { "status": "success", "data": { // Response data here } } ``` **Error Response:** ```json { "status": "error", "message": "Error description", "error_code": "ERROR_CODE", "errors": ["Detailed error 1", "Detailed error 2"] // Optional, for validation errors } ``` ## Endpoints ### 1. List Products **GET** `/api/v1/products` Returns a paginated list of published products with filtering, sorting, and pagination support. **Query Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `page` | integer | No | 1 | Page number (minimum: 1) | | `per_page` | integer | No | 20 | Items per page (1-100, max: 100) | | `search` | string | No | - | Search query (searches in name, description, SKU) | | `category_id` | integer | No | - | Filter by category ID | | `min_price` | float | No | - | Minimum price filter (inclusive) | | `max_price` | float | No | - | Maximum price filter (inclusive) | | `inventory_status` | string | No | - | Filter by inventory: `in_stock`, `low_stock`, `out_of_stock` | | `featured` | boolean | No | - | Filter featured products (`true`/`false`) | | `min_rating` | float | No | - | Minimum rating filter (0.0 to 5.0) | | `order_by` | string | No | `updated_at` | Sort field: `price`, `name`, `created_at`, `rating`, `sales`, `updated_at` | | `direction` | string | No | `desc` | Sort direction: `asc` or `desc` | **Example Request:** ```bash GET /api/v1/products?page=1&per_page=20&min_price=100&max_price=500&featured=true&order_by=price&direction=asc ``` **Example Response:** ```json { "status": "success", "data": { "products": [ { "id": 123, "name": "Wireless Bluetooth Headphones", "slug": "wireless-bluetooth-headphones", "price": 199.99, "compare_at_price": 249.99, "on_sale": true, "discount_percentage": 20.0, "in_stock": true, "featured": false, "category": { "id": 5, "name": "Electronics", "slug": "electronics" }, "tags": [ { "id": 1, "name": "wireless", "slug": "wireless" } ], "primary_image_url": "https://example.com/images/product-123.jpg", "wishlist_count": 25, "views_count": 150, "sales_count": 75, "rating_average": 4.5, "created_at": "2025-01-01T10:00:00Z", "updated_at": "2025-01-15T14:30:00Z" } ], "pagination": { "current_page": 1, "per_page": 20, "total_pages": 5, "total_count": 100, "has_next_page": true, "has_prev_page": false } } } ``` ### 2. Get Product Details **GET** `/api/v1/products/{id}` Returns detailed information about a specific product by ID. **Path Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `id` | integer | Yes | Product ID | **Query Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `include_related` | boolean | No | `false` | Include related products (up to 4) | | `include_reviews` | boolean | No | `false` | Include product reviews (up to 10 most recent) | | `include_variants` | boolean | No | `false` | Include product variants | **Example Request:** ```bash GET /api/v1/products/123?include_related=true&include_reviews=true&include_variants=true ``` **Example Response:** ```json { "status": "success", "data": { "product": { "id": 123, "name": "Wireless Bluetooth Headphones", "slug": "wireless-bluetooth-headphones", "description": "High-quality wireless headphones with noise cancellation...", "price": 199.99, "on_sale": true, "in_stock": true, "category": { "id": 5, "name": "Electronics" }, "related_products": [ { "id": 124, "name": "Wired Headphones", "price": 99.99 } ], "reviews": [ { "id": 1, "rating": 5, "title": "Great product!", "content": "Really happy with this purchase.", "author_name": "John Doe", "created_at": "2025-01-10T12:00:00Z" } ], "reviews_count": 15, "variants": [ { "id": 1, "name": "Large", "price": 219.99, "in_stock": true } ], "has_variants": true } } } ``` ### 3. Search Products **GET** `/api/v1/products/search` Search products by query string. Searches in product name, description, and SKU. **Query Parameters:** | Parameter | Type | Required | Description | |-----------|------|----------|-------------| | `q` | string | Yes | Search query (max 255 characters) | | `page` | integer | No | Page number | | `per_page` | integer | No | Items per page (1-100) | | All other filters from index endpoint | - | No | Same as index endpoint | **Example Request:** ```bash GET /api/v1/products/search?q=wireless&min_price=100&max_price=500&per_page=10 ``` **Example Response:** ```json { "status": "success", "data": { "products": [...], "pagination": {...}, "query": "wireless", "total_results": 15 } } ``` ### 4. List Categories **GET** `/api/v1/products/categories` Returns a list of all active product categories. **Query Parameters:** | Parameter | Type | Required | Default | Description | |-----------|------|----------|---------|-------------| | `include_product_count` | boolean | No | `false` | Include product count per category | | `only_with_products` | boolean | No | `false` | Only return categories that have products | **Example Request:** ```bash GET /api/v1/products/categories?include_product_count=true&only_with_products=true ``` **Example Response:** ```json { "status": "success", "data": { "categories": [ { "id": 5, "name": "Electronics", "slug": "electronics", "description": "Electronic devices and accessories", "product_count": 25 } ], "total": 10 } } ``` ## Filtering & Sorting ### Filtering Options **By Price Range:** ``` GET /api/v1/products?min_price=100&max_price=500 ``` **By Category:** ``` GET /api/v1/products?category_id=5 ``` **By Inventory Status:** ``` GET /api/v1/products?inventory_status=in_stock ``` **By Featured Status:** ``` GET /api/v1/products?featured=true ``` **By Rating:** ``` GET /api/v1/products?min_rating=4.0 ``` **Combined Filters:** ``` GET /api/v1/products?category_id=5&min_price=100&max_price=500&featured=true&inventory_status=in_stock&min_rating=4.0 ``` ### Sorting Options **Sort by Price (Ascending):** ``` GET /api/v1/products?order_by=price&direction=asc ``` **Sort by Name (Descending):** ``` GET /api/v1/products?order_by=name&direction=desc ``` **Sort by Rating:** ``` GET /api/v1/products?order_by=rating&direction=desc ``` **Sort by Sales:** ``` GET /api/v1/products?order_by=sales&direction=desc ``` **Available Sort Fields:** - `price` - Product price - `name` - Product name - `created_at` - Creation date - `rating` - Average rating - `sales` - Sales count - `updated_at` - Last update (default) ## Pagination All list endpoints support pagination: **Basic Pagination:** ``` GET /api/v1/products?page=1&per_page=20 ``` **Navigate Pages:** ``` GET /api/v1/products?page=2&per_page=20 ``` **Pagination Metadata:** ```json { "pagination": { "current_page": 1, "per_page": 20, "total_pages": 5, "total_count": 100, "has_next_page": true, "has_prev_page": false } } ``` **Best Practices:** - Use `has_next_page` and `has_prev_page` to determine navigation - Maximum `per_page` is 100 - Default `per_page` is 20 ## Code Examples ### JavaScript (Fetch API) ```javascript // List products with filters async function getProducts(filters = {}) { const params = new URLSearchParams({ page: filters.page || 1, per_page: filters.perPage || 20, ...filters }); const response = await fetch(`/api/v1/products?${params}`); const data = await response.json(); if (data.status === 'success') { return data.data; } else { throw new Error(data.message); } } // Get product details async function getProduct(id, options = {}) { const params = new URLSearchParams(); if (options.includeRelated) params.append('include_related', 'true'); if (options.includeReviews) params.append('include_reviews', 'true'); if (options.includeVariants) params.append('include_variants', 'true'); const response = await fetch(`/api/v1/products/${id}?${params}`); const data = await response.json(); return data.data.product; } // Search products async function searchProducts(query, filters = {}) { const params = new URLSearchParams({ q: query, ...filters }); const response = await fetch(`/api/v1/products/search?${params}`); const data = await response.json(); return data.data; } // Usage examples const products = await getProducts({ category_id: 5, min_price: 100, max_price: 500, featured: true, order_by: 'price', direction: 'asc' }); const product = await getProduct(123, { includeRelated: true, includeReviews: true, includeVariants: true }); const searchResults = await searchProducts('wireless', { min_price: 100, per_page: 10 }); ``` ### Python (Requests) ```python import requests BASE_URL = "http://localhost:3000/api/v1" def get_products(filters=None): """List products with optional filters""" params = { 'page': 1, 'per_page': 20 } if filters: params.update(filters) response = requests.get(f"{BASE_URL}/products", params=params) response.raise_for_status() data = response.json() if data['status'] == 'success': return data['data'] else: raise Exception(data['message']) def get_product(product_id, include_related=False, include_reviews=False, include_variants=False): """Get product details by ID""" params = {} if include_related: params['include_related'] = 'true' if include_reviews: params['include_reviews'] = 'true' if include_variants: params['include_variants'] = 'true' response = requests.get(f"{BASE_URL}/products/{product_id}", params=params) response.raise_for_status() data = response.json() return data['data']['product'] def search_products(query, filters=None): """Search products by query string""" params = {'q': query} if filters: params.update(filters) response = requests.get(f"{BASE_URL}/products/search", params=params) response.raise_for_status() data = response.json() return data['data'] def get_categories(include_product_count=False, only_with_products=False): """List product categories""" params = {} if include_product_count: params['include_product_count'] = 'true' if only_with_products: params['only_with_products'] = 'true' response = requests.get(f"{BASE_URL}/products/categories", params=params) response.raise_for_status() data = response.json() return data['data']['categories'] # Usage examples products = get_products({ 'category_id': 5, 'min_price': 100, 'max_price': 500, 'featured': 'true', 'order_by': 'price', 'direction': 'asc' }) product = get_product(123, include_related=True, include_reviews=True) search_results = search_products('wireless', {'min_price': 100}) categories = get_categories(include_product_count=True, only_with_products=True) ``` ### cURL ```bash # List products curl "http://localhost:3000/api/v1/products?page=1&per_page=20&min_price=100&max_price=500" # Get product details curl "http://localhost:3000/api/v1/products/123?include_related=true&include_reviews=true" # Search products curl "http://localhost:3000/api/v1/products/search?q=wireless&min_price=100" # List categories curl "http://localhost:3000/api/v1/products/categories?include_product_count=true" ``` ### Ruby (Net::HTTP) ```ruby require 'net/http' require 'json' require 'uri' BASE_URL = 'http://localhost:3000/api/v1' def get_products(filters = {}) params = { page: 1, per_page: 20 }.merge(filters) uri = URI("#{BASE_URL}/products") uri.query = URI.encode_www_form(params) response = Net::HTTP.get_response(uri) data = JSON.parse(response.body) raise data['message'] unless data['status'] == 'success' data['data'] end def get_product(id, options = {}) params = {} params['include_related'] = 'true' if options[:include_related] params['include_reviews'] = 'true' if options[:include_reviews] params['include_variants'] = 'true' if options[:include_variants] uri = URI("#{BASE_URL}/products/#{id}") uri.query = URI.encode_www_form(params) unless params.empty? response = Net::HTTP.get_response(uri) data = JSON.parse(response.body) data['data']['product'] end # Usage products = get_products( category_id: 5, min_price: 100, max_price: 500, featured: true, order_by: 'price', direction: 'asc' ) product = get_product(123, include_related: true, include_reviews: true) ``` ## Error Handling ### HTTP Status Codes | Code | Description | |------|-------------| | 200 | Success | | 400 | Bad Request (invalid parameters) | | 404 | Not Found (product not found) | | 429 | Too Many Requests (rate limit exceeded) | | 500 | Internal Server Error | ### Error Response Format ```json { "status": "error", "message": "Error description", "error_code": "ERROR_CODE", "errors": ["Detailed error 1", "Detailed error 2"] // Optional } ``` ### Common Error Codes - `NOT_FOUND` - Resource not found - `BAD_REQUEST` - Invalid request parameters - `VALIDATION_ERROR` - Validation failed (includes `errors` array) - `MISSING_PARAMETER` - Required parameter missing - `DATABASE_ERROR` - Database error occurred - `INTERNAL_ERROR` - Internal server error ### Error Handling Example ```javascript async function getProductSafely(id) { try { const response = await fetch(`/api/v1/products/${id}`); const data = await response.json(); if (data.status === 'success') { return data.data.product; } else { // Handle specific error codes switch (data.error_code) { case 'NOT_FOUND': console.error('Product not found'); break; case 'BAD_REQUEST': console.error('Invalid request:', data.message); break; default: console.error('Error:', data.message); } throw new Error(data.message); } } catch (error) { console.error('Request failed:', error); throw error; } } ``` ## Rate Limiting The API implements rate limiting to prevent abuse: | Endpoint | Limit | Period | |----------|-------|--------| | Index | 100 requests | 1 minute | | Show | 200 requests | 1 minute | | Search | 30 requests | 1 minute | | Categories | 60 requests | 1 minute | **Rate Limit Response:** ```json { "error": "Too Many Requests", "message": "Rate limit exceeded. Please try again later." } ``` **HTTP Headers:** - `Retry-After: 60` - Seconds until retry is allowed ## Best Practices ### 1. Use Pagination Always use pagination for list endpoints to avoid loading too much data: ```javascript // Good: Paginated request const products = await getProducts({ page: 1, per_page: 20 }); // Bad: Requesting all products at once const products = await getProducts({ per_page: 1000 }); ``` ### 2. Cache Responses The API responses are cached. Use appropriate cache headers: ```javascript // Check cache headers const response = await fetch('/api/v1/products'); const cacheControl = response.headers.get('Cache-Control'); ``` ### 3. Handle Errors Gracefully Always check response status and handle errors: ```javascript const response = await fetch('/api/v1/products'); if (!response.ok) { const error = await response.json(); // Handle error appropriately } ``` ### 4. Use Appropriate Filters Use filters to reduce response size and improve performance: ```javascript // Good: Filtered request const products = await getProducts({ category_id: 5, min_price: 100, max_price: 500 }); // Bad: Fetch all then filter client-side const allProducts = await getProducts(); const filtered = allProducts.filter(p => p.category.id === 5); ``` ### 5. Request Only Needed Data Use include parameters only when needed: ```javascript // Good: Request only what you need const product = await getProduct(123, { includeReviews: true }); // Bad: Request everything const product = await getProduct(123, { includeRelated: true, includeReviews: true, includeVariants: true }); ``` ### 6. Respect Rate Limits Implement exponential backoff for rate limit errors: ```javascript async function fetchWithRetry(url, options = {}, retries = 3) { for (let i = 0; i < retries; i++) { const response = await fetch(url, options); if (response.status === 429) { const retryAfter = parseInt(response.headers.get('Retry-After') || '60'); await new Promise(resolve => setTimeout(resolve, retryAfter * 1000)); continue; } return response; } throw new Error('Max retries exceeded'); } ``` ## Additional Resources - [Swagger Documentation](../swagger/v1/swagger.yaml) - [API Implementation Summary](./PRODUCT_API_COMPLETION_SUMMARY.md) - [Testing Guide](./PRODUCT_API_TESTING_SUMMARY.md) - [Edge Cases Testing](./PRODUCT_API_EDGE_CASES_TESTING.md)