Overview

The Manva.ai Storefront API allows third-party developers to build custom storefronts, mobile apps, and integrations on top of any Manva-published site. All endpoints are scoped to a subdomain (the store identifier).

Key points:

  • All responses are JSON with Content-Type: application/json
  • Prices are returned as numbers (not in subunits)
  • Currency is typically "INR" but depends on the store's settings
  • Rate limiting is enforced per IP on most endpoints
  • The :subdomain parameter must be a lowercase alphanumeric string (hyphens allowed)

Authentication

Most storefront endpoints are public and require no authentication. Endpoints that access customer-specific data (wishlist, orders, loyalty) require a Bearer token obtained from the customer login or signup endpoints.

HTTP Header
Authorization: Bearer <customer_token>

Tokens are JWTs valid for 30 days. They are scoped to a single store and customer.

Products

GET /api/sites/:subdomain/products List all active products Public

Description

Returns all active products for a published site. Optionally filter by category. Products are sorted by display order, then by newest first. Maximum 100 products returned.

Query Parameters

NameTypeRequiredDescription
categorystringoptionalFilter products by category name

Response 200 OK

application/json
{
  "products": [
    {
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
      "name": "Organic Cotton T-Shirt",
      "slug": "organic-cotton-t-shirt",
      "description": "Soft, breathable organic cotton tee.",
      "price": 799,
      "sale_price": 599,
      "currency": "INR",
      "images": ["https://res.cloudinary.com/.../tshirt.jpg"],
      "category": "Clothing"
    }
  ]
}

Errors

StatusBodyReason
404{"error":"Site not found"}Subdomain does not exist or site is inactive
500{"error":"Failed to load products"}Internal server error

Cart & Checkout

POST /api/sites/:subdomain/checkout Create order + Razorpay payment Public

Description

Creates a new order and a Razorpay payment order. Prices are verified server-side (client-side prices are never trusted). Supports discount codes, loyalty point redemption, GST calculation, and shipping fees. If a customer Bearer token is provided, the order is linked to that customer account.

Request Body

application/json
{
  "items": [
    {
      "id": "product-uuid",
      "qty": 2,
      "variant": { "Size": "L", "Color": "Blue" }
    }
  ],
  "buyer": {
    "name": "Jane Doe",
    "phone": "9876543210",
    "email": "jane@example.com",
    "address": "123 Main St, Mumbai 400001",
    "notes": "Leave at door"
  },
  "discountCode": "SAVE10",
  "loyaltyPoints": 100
}
FieldTypeRequiredDescription
itemsarrayrequiredCart items with product id, qty (1-99), optional variant
buyer.namestringrequiredBuyer's full name
buyer.phonestringrequiredBuyer's phone number
buyer.emailstringoptionalBuyer's email
buyer.addressstringoptionalShipping address (max 1000 chars)
buyer.notesstringoptionalOrder notes (max 500 chars)
discountCodestringoptionalCoupon/discount code to apply
loyaltyPointsnumberoptionalLoyalty points to redeem (requires customer token)

Response 200 OK

application/json
{
  "orderId": "uuid",
  "orderNo": "ORD1A2B3C",
  "razorpayOrderId": "order_abc123",
  "amount": 119800,          // in paise (INR subunits)
  "currency": "INR",
  "keyId": "rzp_live_XXXXXX",  // Razorpay public key
  "businessName": "My Store",
  "breakdown": {
    "subtotal": 1398,
    "discount": 100,
    "loyaltyDiscount": 50,
    "loyaltyPointsUsed": 100,
    "loyaltyPointsEarned": 12,
    "shipping": 50,
    "tax": 0,
    "gstInclusive": false,
    "total": 1198
  }
}

Errors

StatusBodyReason
400{"error":"Cart is empty"}No items provided
400{"error":"Name and phone required"}Missing buyer details
400{"error":"No valid items in cart"}All items are invalid or unavailable
404{"error":"Site not found"}Subdomain invalid or inactive
429{"error":"Too many checkout attempts"}Rate limited (10/min per IP)
503{"error":"Payments not configured"}Razorpay not set up for this store
Rate limit: 10 requests per minute per IP
POST /api/sites/:subdomain/checkout/verify Verify Razorpay payment signature Public

Description

Verifies the Razorpay payment signature after the customer completes the payment flow. On success, the order status is updated to "confirmed" and "paid". Inventory is deducted, discount usage incremented, and loyalty points are adjusted.

Request Body

application/json
{
  "razorpay_order_id": "order_abc123",
  "razorpay_payment_id": "pay_xyz789",
  "razorpay_signature": "hmac_sha256_hex_string"
}
FieldTypeRequiredDescription
razorpay_order_idstringrequiredThe Razorpay order ID from the checkout response
razorpay_payment_idstringrequiredThe payment ID from Razorpay checkout callback
razorpay_signaturestringrequiredHMAC-SHA256 signature from Razorpay

Response 200 OK

application/json
{
  "success": true
}

Errors

StatusBodyReason
400{"error":"Missing fields"}One or more required fields missing
400{"error":"Invalid signature"}Signature verification failed (order marked as failed)
500{"error":"Verification failed"}Internal server error

Customer Authentication

POST /api/sites/:subdomain/customer/signup Register new customer Public

Description

Creates a new customer account for the storefront. Returns a JWT token for subsequent authenticated requests. Supports referral codes -- if provided, the referrer earns 50 loyalty points.

Request Body

application/json
{
  "name": "Jane Doe",
  "phone": "9876543210",
  "email": "jane@example.com",
  "password": "securepass123",
  "address": "123 Main St, Mumbai",
  "refCode": "REF1A2B3C"
}
FieldTypeRequiredDescription
namestringrequiredCustomer's full name (max 255)
phonestringrequiredPhone number (digits only, max 50)
passwordstringrequiredMinimum 6 characters
emailstringoptionalEmail address (max 255)
addressstringoptionalShipping address (max 1000)
refCodestringoptionalReferral code from another customer

Response 200 OK

application/json
{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "customer": {
    "name": "Jane Doe",
    "phone": "9876543210",
    "email": "jane@example.com",
    "address": "123 Main St, Mumbai",
    "referral_code": "REF4D5E6F"
  }
}

Errors

StatusBodyReason
400{"error":"Name, phone, and password required"}Missing required fields
400{"error":"Password must be 6+ characters"}Password too short
400{"error":"An account with this phone already exists..."}Duplicate phone number
429{"error":"Too many requests"}Rate limited (20/min per IP)
Rate limit: 20 requests per minute per IP
POST /api/sites/:subdomain/customer/login Customer login Public

Description

Authenticates a customer using their phone number and password. Returns a JWT token valid for 30 days.

Request Body

application/json
{
  "phone": "9876543210",
  "password": "securepass123"
}

Response 200 OK

application/json
{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "customer": {
    "name": "Jane Doe",
    "phone": "9876543210",
    "email": "jane@example.com",
    "address": "123 Main St, Mumbai"
  }
}

Errors

StatusBodyReason
400{"error":"Phone and password required"}Missing fields
401{"error":"Invalid phone or password"}Wrong credentials
429{"error":"Too many requests"}Rate limited
GET /api/sites/:subdomain/customer/me Get current customer profile Auth

Description

Returns the authenticated customer's profile. Requires a valid Bearer token from login or signup.

Response 200 OK

application/json
{
  "customer": {
    "id": "uuid",
    "name": "Jane Doe",
    "email": "jane@example.com",
    "phone": "9876543210",
    "address": "123 Main St, Mumbai",
    "referral_code": "REF4D5E6F"
  }
}

Errors

StatusBodyReason
401{"error":"Not logged in"}Missing or invalid Bearer token
GET /api/sites/:subdomain/customer/orders List customer's orders Auth

Description

Returns the authenticated customer's order history, sorted by newest first. Maximum 100 orders returned.

Response 200 OK

application/json
{
  "orders": [
    {
      "order_no": "ORD1A2B3C",
      "items": [{ "name": "T-Shirt", "price": 599, "qty": 1 }],
      "subtotal": 599,
      "shipping_fee": 50,
      "discount_amount": 0,
      "total": 649,
      "currency": "INR",
      "status": "confirmed",
      "payment_status": "paid",
      "created_at": "2026-04-10T09:15:00.000Z"
    }
  ]
}

Errors

StatusBodyReason
401{"error":"Not logged in"}Missing or invalid Bearer token
500{"error":"Failed to load orders"}Internal server error

Reviews

GET /api/sites/:subdomain/reviews/:productId List approved reviews Public

Description

Returns all approved reviews for a product, sorted by newest first. Includes the review count and average rating. Maximum 100 reviews.

Response 200 OK

application/json
{
  "reviews": [
    {
      "id": "uuid",
      "reviewer_name": "Jane D.",
      "rating": 5,
      "title": "Amazing quality!",
      "body": "Very soft fabric and great fit.",
      "verified": true,
      "created_at": "2026-04-08T14:30:00.000Z"
    }
  ],
  "count": 12,
  "average": 4.3
}

Errors

StatusBodyReason
404{"error":"Site not found"}Subdomain invalid or inactive
500{"error":"Failed to load reviews"}Internal server error
POST /api/sites/:subdomain/reviews/:productId Submit a product review Public

Description

Submits a new review for a product. Reviews require store owner approval before appearing publicly. If a customer Bearer token is provided and the customer has purchased the product, the review is marked as "verified".

Request Body

application/json
{
  "rating": 5,
  "title": "Excellent product!",
  "body": "Loved the quality and fast delivery.",
  "name": "John"
}
FieldTypeRequiredDescription
ratingintegerrequired1 to 5 stars
bodystringrequiredReview text (min 5 chars, max 2000)
titlestringoptionalReview title (max 200)
namestringoptionalReviewer name (ignored if logged in, defaults to "Anonymous")

Response 200 OK

application/json
{
  "success": true,
  "id": "uuid",
  "created_at": "2026-04-13T10:00:00.000Z",
  "verified": false
}

Errors

StatusBodyReason
400{"error":"Rating must be 1-5"}Invalid rating
400{"error":"Review too short"}Body less than 5 characters
404{"error":"Product not found"}Product ID invalid
429{"error":"Too many reviews submitted"}Rate limited (5/hour per IP)
Rate limit: 5 reviews per hour per IP

Wishlist

GET /api/sites/:subdomain/wishlist Get customer wishlist Auth

Description

Returns all products in the authenticated customer's wishlist. Maximum 200 items, sorted by newest first.

Response 200 OK

application/json
{
  "wishlist": [
    {
      "id": "product-uuid",
      "name": "Organic T-Shirt",
      "price": 799,
      "sale_price": 599,
      "currency": "INR",
      "images": ["https://..."]
    }
  ]
}

Errors

StatusBodyReason
401{"error":"Not logged in"}Missing or invalid Bearer token
POST /api/sites/:subdomain/wishlist/:productId Add product to wishlist Auth

Description

Adds a product to the customer's wishlist. If the product is already in the wishlist, the request succeeds silently (no duplicate created).

Response 200 OK

application/json
{ "success": true }

Errors

StatusBodyReason
401{"error":"Login required"}Missing or invalid Bearer token
404{"error":"Site not found"}Subdomain invalid
DELETE /api/sites/:subdomain/wishlist/:productId Remove from wishlist Auth

Description

Removes a product from the customer's wishlist.

Response 200 OK

application/json
{ "success": true }

Errors

StatusBodyReason
401{"error":"Login required"}Missing or invalid Bearer token

Loyalty Points

GET /api/sites/:subdomain/loyalty Get loyalty balance & config Auth

Description

Returns the customer's loyalty points balance and the store's loyalty program configuration (earn rate, redeem rate, enabled status).

Response 200 OK

application/json
{
  "enabled": true,
  "earn_rate": 5,          // 5% of order value earned as points
  "redeem_rate": 1,       // 1 point = 1 currency unit discount
  "points": 250
}

Errors

StatusBodyReason
401{"error":"Login required"}Missing or invalid Bearer token
404{"error":"Site not found"}Subdomain invalid

Newsletter

POST /api/sites/:subdomain/subscribe Subscribe to newsletter Public

Description

Subscribes an email address to the store's newsletter. Duplicate emails are silently ignored (no error).

Request Body

application/json
{
  "email": "subscriber@example.com",
  "name": "Jane",
  "source": "popup"
}
FieldTypeRequiredDescription
emailstringrequiredValid email address
namestringoptionalSubscriber name (max 100)
sourcestringoptionalSubscription source e.g. "popup", "footer" (max 50, defaults to "popup")

Response 200 OK

application/json
{ "success": true }

Errors

StatusBodyReason
400{"error":"Valid email required"}Missing or invalid email
404{"error":"Site not found"}Subdomain invalid
429Rate limit response10 requests/min per IP
Rate limit: 10 requests per minute per IP

Order Tracking

POST /api/sites/:subdomain/track-order Track order by number + phone Public

Description

Looks up an order by its order number and the buyer's phone number (last 4 digits matched for privacy). No authentication required -- useful for guest buyers.

Request Body

application/json
{
  "orderNo": "ORD1A2B3C",
  "phone": "9876543210"
}
FieldTypeRequiredDescription
orderNostringrequiredThe order number (e.g. "ORD1A2B3C")
phonestringrequiredBuyer's phone (last 4 digits used for matching)

Response 200 OK

application/json
{
  "order": {
    "order_no": "ORD1A2B3C",
    "buyer_name": "Jane Doe",
    "items": [...],
    "subtotal": 1198,
    "shipping_fee": 50,
    "discount_amount": 100,
    "total": 1148,
    "currency": "INR",
    "status": "shipped",
    "payment_status": "paid",
    "created_at": "2026-04-10T09:15:00.000Z",
    "updated_at": "2026-04-11T14:30:00.000Z"
  },
  "businessName": "My Store"
}

Errors

StatusBodyReason
400{"error":"Order number and phone required"}Missing fields
404{"error":"Order not found. Check your order number and phone."}No match found
429{"error":"Too many lookups"}Rate limited (20/min per IP)
Rate limit: 20 requests per minute per IP

Contact / Lead Form

POST /api/sites/:subdomain/lead Submit contact form Public

Description

Submits a contact form or lead capture from a published site. Accepts both JSON and URL-encoded form data. The site owner is notified asynchronously. Maximum 10,000 leads per site.

Request Body

application/json
{
  "name": "Jane Doe",
  "email": "jane@example.com",
  "phone": "9876543210",
  "message": "I am interested in your services."
}
FieldTypeRequiredDescription
namestringoptionalContact name (max 255)
emailstringoptionalEmail address (validated if provided)
phonestringoptionalPhone number (max 50)
messagestringoptionalMessage body (max 2000)

Response 200 OK

application/json (when Accept: application/json)
{
  "success": true,
  "message": "Thank you! We will get back to you soon."
}

Note: If the request does not include Accept: application/json, an HTML thank-you page is returned instead.

Errors

StatusBodyReason
400{"error":"Invalid email address."}Email format validation failed
404{"error":"Site not found."}Subdomain invalid or inactive
429{"error":"Too many submissions..."}Rate limited (10 per 15 min per IP)
429{"error":"This site has reached its lead storage limit."}10,000 leads cap reached
Rate limit: 10 submissions per 15 minutes per IP

AI Chat Widget

POST /api/sites/:subdomain/chat Send chat message to AI assistant Public

Description

Sends a visitor message to the site's AI-powered chat assistant. The assistant uses the store's configured context (business name, products, services) to answer. Chat must be enabled by the store owner. Daily cap of 200 AI messages per site.

Request Body

application/json
{
  "message": "What are your shipping options?",
  "history": [
    { "role": "user", "content": "Hi there" },
    { "role": "assistant", "content": "Hello! How can I help?" }
  ]
}
FieldTypeRequiredDescription
messagestringrequiredVisitor's message (1-500 characters)
historyarrayoptionalPrevious conversation turns (last 6 used)

Response 200 OK

application/json
{
  "reply": "We offer free shipping on orders above INR 999..."
}

Errors

StatusBodyReason
400{"error":"Message is required."}Empty message
400{"error":"Message too long (max 500 chars)."}Message exceeds limit
403{"error":"Chat is not enabled for this site."}Store owner has not enabled chat
429{"error":"Too many messages..."}Rate limited (10/min per IP)
429{"error":"This site has reached its daily chat limit..."}200 messages/day cap reached
Rate limit: 10 messages per minute per IP + 200 per site per day

Password-Protected Pages

POST /api/sites/:subdomain/unlock Unlock password-protected pages Public

Description

Verifies a password for a protected page and returns a time-limited access token (valid 24 hours). The token should be stored client-side and sent in subsequent page requests.

Request Body

application/json
{
  "password": "secret123"
}
FieldTypeRequiredDescription
passwordstringrequiredThe page password set by the store owner

Response 200 OK

application/json
{
  "success": true,
  "access_token": "hex_random_token_string"
}

Errors

StatusBodyReason
400{"error":"Password required"}No password provided
401{"error":"Incorrect password"}Wrong password
404{"error":"Site not found or not protected"}Site does not exist or has no password set
429{"error":"Too many attempts. Wait a minute."}Rate limited (10/min per IP)
Rate limit: 10 attempts per minute per IP

Manva.ai Storefront API Documentation · Built with Manva.ai

All endpoints are versioned at /api/sites/:subdomain/