Products & Ads Endpoints
Product is a single table backing both store products (seller-listed for catalog/checkout) and ads (consumer-posted for resale). The endpoint you call depends on which one you're managing.
/api/products — store catalog browse
GET /api/products
Browse all products with filters. Used by Shop tab.
Query params:
| Param | Type | Default | Notes |
|---|---|---|---|
status | available | sold | out_of_stock | (all) | Filter by status |
productType | resale,rental (csv) | (all) | Restricts to ad-type products |
category | string | (all) | Category slug |
search | string | — | Searches title + description |
sort | newest | price_asc | price_desc | popular | newest | |
storeId | int | — | Filter to a specific store |
cursor / limit | int | 20 | Pagination |
Response (200): { products: Product[], nextCursor }
GET /api/products/[id]
Single product detail (used by product detail page on web + mobile).
Includes: images, category, store + store.allowWhatsApp + store.seller.whatsappNumber, seller, _count.
/api/ads — consumer's own ads
GET /api/ads 🔒 auth required
List the authenticated consumer's own ads.
Response (200): { ads: Product[] } ← NOT { products } — this is a known foot-gun. See Shop gotchas.
POST /api/ads 🔒 auth required
Create a new ad.
Body: { title, description, price, currency?, category, condition?, productType?, images?, location?, ... }
Response (201): { ad: Product }
Limits: max MAX_ADS_PER_CONSUMER=20, rate limit 3 per 5 min
PUT /api/ads/[id] 🔒 auth required
Update own ad (title, price, status available → sold/out_of_stock).
DELETE /api/ads/[id] 🔒 auth required
Soft delete (sets status='deleted').
/api/stores — store browse
GET /api/stores
List all live stores.
Query: category, search, lat/lng (for distance sort)
Response: { stores: Store[] }
GET /api/stores/[id]
Store detail with products.
Response: { store: Store, products: Product[] }
/api/categories
GET /api/categories
Static list of product categories (electronics, household, furniture, etc.).
Response: { categories: Category[] }
Common shapes
Product
{
id: number;
title: string;
description: string | null;
price: string; // Decimal stringified
currency: string; // ISO code (most are 'INR')
status: "available" | "sold" | "out_of_stock" | "deleted";
productType: "resale" | "rental" | "store";
category: string;
condition?: "new" | "used" | null;
storeId: number | null; // null = ad
consumerId: number | null; // set for ads
sellerId: number | null;
images: { url: string; order: number }[];
store?: Store;
consumer?: Consumer;
seller?: Seller;
_count?: { likes: number };
createdAt: string;
updatedAt: string;
}
Store
{
id: number;
name: string;
storeType: string;
description: string | null;
imageUrl: string | null;
address: string | null;
phone: string | null;
isActive: boolean;
status: "live" | "coming_soon" | "paused";
commissionRate: number; // default 10
openingTime: string; // "HH:MM"
closingTime: string;
supportsDelivery: boolean;
supportsPickup: boolean;
selfDelivery: boolean;
allowWhatsApp: boolean; // admin-controlled per-store toggle
deliveryTimeMinutes: number | null;
pickupPrepTimeMinutes: number | null;
lastOrderBufferMinutes: number | null;
sellerId: number;
seller?: Seller;
}
Related
- Shop feature — how these endpoints are consumed
- Cart · Checkout
tests/api-shape-contracts.test.ts— locks the response shape contracts