Order groceries from Zepto in seconds. Just say what you need, get a payment link on WhatsApp, pay on your phone, done. Remembers your usual items. Works across India where Zepto delivers.
Order groceries from Zepto in 30 seconds. From chat to checkout.
Tell your AI what you need. It shops, generates a payment link, sends it to WhatsApp. You pay on your phone. Groceries arrive in 10 minutes.
Quick orders:
"Order milk and bread from Zepto"
"Add vegetables - tomatoes, onions, potatoes"
"Get me Amul butter and cheese"
Your usuals:
"Add my usual milk" → AI picks the brand you always order
"Order the usual groceries" → AI suggests your frequent items
Full shopping list:
"Add milk, bread, eggs, coriander, ginger, and tea bags"
→ AI adds everything, shows total: ₹X
→ Sends payment link to WhatsApp
→ You pay, groceries arrive
What this skill does:
~/.openclaw/skills/zepto/order-history.json (local file, not shared)What this skill does NOT do:
Data Storage:
~/.openclaw/skills/zepto/order-history.json (local only, helps with "usuals" feature)User Control:
ALWAYS follow this order when building an order:
# Before adding ANY items, ALWAYS check cart state
node zepto-agent.js get-cart
Why: Cart may have items from previous sessions. Adding duplicates is wasteful.
# This handles everything: clears unwanted, checks duplicates, adds missing
node zepto-agent.js smart-shop "milk, bread, eggs"
What it does:
{ added: [], skipped: [], failed: [] }When you see in snapshot:
"Decrease quantity 1 Increase quantity" → Item is IN CART
button "Remove" [ref=eXX] → Item is IN CART
DO NOT click "ADD" when you see these signals!
Your order history is tracked in: {SKILL_DIR}/order-history.json
(Where {SKILL_DIR} is your skill directory, typically ~/.openclaw/skills/zepto/)
Smart Selection Logic:
order-history.json for that categoryWhen to run: User says "update my zepto history" or "refresh order history"
Process:
order-history.jsonImplementation:
# Step 1: Navigate to account page
browser navigate url=https://www.zepto.com/account profile=openclaw
# Step 2: Extract order URLs
browser act profile=openclaw request='{"fn":"() => { const orders = []; document.querySelectorAll(\"a[href*=\\\"/order/\\\"]\").forEach(link => { if (link.href.includes(\"isArchived=false\") && link.textContent.includes(\"delivered\")) { orders.push(link.href); } }); return [...new Set(orders)]; }", "kind":"evaluate"}'
# Returns array of order URLs
# Step 3: For each order URL:
browser navigate url={order_url} profile=openclaw
# Step 4: Extract items from order page
browser act profile=openclaw request='{"fn":"() => { const items = []; document.querySelectorAll(\"*\").forEach(el => { const text = el.textContent; if (text.match(/\\d+\\s*unit/i)) { const parent = el.closest(\"div\"); if (parent) { const lines = parent.textContent.split(\"\\n\").map(l => l.trim()).filter(l => l && l.length > 5 && l.length < 100); if (lines[0]) { const qtyMatch = text.match(/(\\d+)\\s*unit/i); items.push({ name: lines[0], quantity: qtyMatch ? parseInt(qtyMatch[1]) : 1 }); } } } }); const uniqueItems = {}; items.forEach(item => { if (!uniqueItems[item.name]) uniqueItems[item.name] = item; }); return Object.values(uniqueItems); }", "kind":"evaluate"}'
# Returns array of {name, quantity}
# Step 5: Aggregate all items into frequency map
# Build JSON structure with counts
# Step 6: Write to file
write path={SKILL_DIR}/order-history.json content={json_data}
Automated scraper advantages:
Example:
User: "Update my Zepto order history"
Response:
"🔍 Scanning your Zepto orders...
📦 Found 6 delivered orders
🔄 Extracting items...
✅ Updated! Found:
- Coriander: 4 orders
- Milk: 3 orders
- Bread: 2 orders
- Potato: 2 orders
+ 15 other items
Your usuals are ready!"
Smart Selection Logic (Using History):
Example:
User: "Add milk"
[Check order-history.json]
→ "Amul Taaza Toned Fresh Milk | Pouch (500ml)" ordered 3x
Response:
"🥛 Adding your usual milk!
Amul Taaza Toned Fresh Milk (500ml) - ₹29
📊 You've ordered this 3 times
✅ Added to cart"
If only ordered once or never:
User: "Add milk"
[Check order-history.json]
→ "Amul Taaza" ordered 1x only
Response:
"🥛 Found some milk options:
1. Amul Taaza Toned (500ml) - ₹29 ⭐ 4.8 (100k) - You've ordered this once
2. Amul Gold (1L) - ₹68 ⭐ 4.9 (80k) - Most popular
3. Mother Dairy (500ml) - ₹30 ⭐ 4.7 (60k)
Which one? (or tell me a number)"
Update order history: After each successful order, update the JSON file with new items.
Check if already logged in:
browser open url=https://www.zepto.com profile=openclaw
browser snapshot --interactive profile=openclaw
# Look for "login" button vs "profile" link
If NOT logged in, start auth flow:
Ask user: "What's your phone number for Zepto? (10 digits)"
# Click login button
browser act profile=openclaw request='{"kind":"click","ref":"{login_button_ref}"}'
# Type phone number
browser act profile=openclaw request='{"kind":"type","ref":"{phone_input_ref}","text":"{phone}"}'
# Click Continue
browser act profile=openclaw request='{"kind":"click","ref":"{continue_button_ref}"}'
Ask user: "I've sent the OTP to {phone}. What's the OTP you received?"
browser snapshot --interactive profile=openclaw # Get OTP input refs
browser act profile=openclaw request='{"kind":"type","ref":"{otp_input_ref}","text":"{otp}"}'
# OTP auto-submits after 6 digits
Result: User is now logged in! Session persists across browser restarts.
🚨 CRITICAL: ALWAYS CHECK ADDRESS BEFORE PROCEEDING WITH ANY SHOPPING!
Default behavior:
On homepage, address is visible in the header:
browser snapshot --interactive profile=openclaw
# Look for button with heading level=3 containing the address
# Example ref: e16 with text like "Home - [Address Details]..."
# Delivery time shown nearby (e.g., "10 minutes")
ALWAYS ask user to confirm before shopping:
📍 I see your delivery address is set to:
{Address Name} - {Full Address}
⏱️ Delivery in ~{X} minutes
Is this correct? Should I proceed with this address?
Use the zepto-agent.js select-address command:
node zepto-agent.js select-address "Home"
node zepto-agent.js select-address "sanskar" # Fuzzy matching works!
node zepto-agent.js select-address "kundu blr"
How it works:
Example:
# Current address: "Kundu Blr"
node zepto-agent.js select-address "sanskar"
# Output:
# ℹ️ Opening Zepto...
# ✅ Zepto opened
# ℹ️ 📍 Selecting address: "sanskar"
# ℹ️ Current: Kundu Blr
# ✅ Clicked: Sanskar BlrA-301, A, BLOCK-B...
# 🎉 Address changed to: Sanskar blr
When user says "change address to X" or "deliver to X":
# Just call the command with their address name/query
node zepto-agent.js select-address "{user_query}"
No manual modal navigation needed! The script handles:
Manual Selection (Fallback): If the programmatic method fails or address isn't found:
# Click the address button (ref e16 or similar)
browser act profile=openclaw request='{"kind":"click","ref":"e16"}'
# This opens address selection modal with all saved addresses
Select address using JavaScript:
# Replace {USER_ADDRESS_NAME} with the actual address name user selected
browser act profile=openclaw request='{"fn":"() => { const input = document.querySelector('input[placeholder*=\"address\"]'); if (!input) return { error: 'Modal not found' }; let modal = input; for (let i = 0; i < 15; i++) { if (!modal.parentElement) break; modal = modal.parentElement; if (window.getComputedStyle(modal).position === 'fixed') break; } const divs = Array.from(modal.querySelectorAll('div')); const match = divs.find(d => d.textContent && d.textContent.trim().startsWith('{USER_ADDRESS_NAME}')); if (!match) return { error: 'Address not found' }; let p = match; for (let i = 0; i < 10; i++) { if (!p) break; const s = window.getComputedStyle(p); if (p.onclick || p.getAttribute('onClick') || s.cursor === 'pointer') { p.scrollIntoView({ block: 'center' }); setTimeout(() => {}, 300); p.click(); return { clicked: true, text: match.textContent.substring(0, 100) }; } p = p.parentElement; } return { error: 'No clickable parent' }; }()","kind":"evaluate"}'
After address confirmed by user:
✅ Delivery address confirmed: {address_name}
📍 {full_address}
⏱️ ETA: {eta} mins
Ready to shop! What would you like to add to cart?
⚠️ Address is CRITICAL - never skip this step!
When user asks to "explore", "show me", "what's good", "find something", or "discover":
Common Discovery Patterns:
Browse Categories:
# Navigate to category pages
browser navigate url=https://www.zepto.com profile=openclaw
browser snapshot --interactive profile=openclaw
# Categories available on homepage:
# - Fruits & Vegetables
# - Dairy, Bread & Eggs
# - Munchies (snacks)
# - Cold Drinks & Juices
# - Breakfast & Sauces
# - Atta, Rice, Oil & Dals
# - Cleaning Essentials
# - Bath & Body
# - Makeup & Beauty
Filter & Sort:
# Example: Browse "Munchies" category
browser navigate url=https://www.zepto.com/pn/munchies profile=openclaw
browser snapshot --interactive profile=openclaw
# Take screenshot to show user the options
browser screenshot profile=openclaw
Discovery Response Format:
🔍 Found some great options in {category}:
1. **{Product Name}** - ₹{price} ({discount}% OFF)
⭐ {rating} ({review_count} reviews)
📦 {size/quantity}
2. **{Product Name}** - ₹{price}
⭐ {rating} ({review_count} reviews)
3. **{Product Name}** - ₹{price} ({discount}% OFF)
⭐ {rating} ({review_count} reviews)
Want me to add any of these? Just tell me the number(s)!
Smart Filtering Tips:
Interactive Discovery: After showing options, user can:
MANDATORY PRE-FLIGHT CHECK: Before adding ANY items:
Multi-Item Shopping Flow: When user gives a list (e.g., "add milk, butter, bread"):
CRITICAL: Never batch-add without verification! Page refs change after each add.
Item Selection Logic:
When UNCLEAR about variant:
🥛 Found multiple milk options:
1. Amul Taaza (500ml) - ₹29 ⭐ 4.8 (100k)
2. Amul Gold (1L) - ₹68 ⭐ 4.9 (80k)
3. Mother Dairy (500ml) - ₹30 ⭐ 4.7 (60k)
Which one? (or tell me a number)
Search Process:
browser navigate url=https://www.zepto.com/search?query={item} profile=openclaw
browser snapshot --interactive profile=openclaw
Rule: Pick product with highest review count (unless order history says otherwise).
Format: {rating} ({count}) where k=thousand, M=million.
Example: "4.8 (694.4k)" = 694,400 reviews = most popular.
browser act profile=openclaw request='{"kind":"click","ref":"{ADD_button_ref}"}'
browser navigate url=https://www.zepto.com/?cart=open profile=openclaw
browser snapshot profile=openclaw # Get cart summary
Cart Summary Format:
🛒 Added to cart:
1. Item 1 - ₹XX
2. Item 2 - ₹YY
3. Item 3 - ₹ZZ
💰 Total: ₹{total}
Ready to checkout? (say "yes" or "checkout" or "lessgo")
CRITICAL - Quantity Mapping: When user provides a shopping list with quantities (e.g., "3x jeera, 2x saffola oats"):
Example mapping:
{
"jeera": 3,
"saffola_oats": 2,
"milk": 1
}
Before removing duplicates or adjusting quantities:
If item not found or out of stock:
❌ {item} is currently unavailable.
🔍 Suggestions:
- {similar_item_1}
- {similar_item_2}
What would you like instead?
Don't auto-add alternatives - wait for user's next item or choice.
After all items added to cart and user confirms checkout:
# Open cart modal
browser act profile=openclaw request='{"kind":"click","ref":"{cart_button_ref}"}'
# Example ref from homepage: e44
# Wait for cart to open, take snapshot
browser snapshot --interactive profile=openclaw
# Click "Click to Pay ₹{amount}" button
browser act profile=openclaw request='{"kind":"click","ref":"{click_to_pay_button_ref}"}'
# Example ref: e3579
# Wait 2 seconds for navigation to complete
browser act profile=openclaw request='{"fn":"async () => { await new Promise(r => setTimeout(r, 2000)); return window.location.href; }","kind":"evaluate"}'
URL Format: