Skip to main content

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:

ParamTypeDefaultNotes
statusavailable | sold | out_of_stock(all)Filter by status
productTyperesale,rental (csv)(all)Restricts to ad-type products
categorystring(all)Category slug
searchstringSearches title + description
sortnewest | price_asc | price_desc | popularnewest
storeIdintFilter to a specific store
cursor / limitint20Pagination

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;
}
  • Shop feature — how these endpoints are consumed
  • Cart · Checkout
  • tests/api-shape-contracts.test.ts — locks the response shape contracts