# Swiggy Builders Club — Full Documentation > Swiggy Builders Club is the ecosystem program around Swiggy's MCP stack — Food, Instamart, and Dineout. ## Docs ### Multi-turn cart state Carrying cart identity across user turns on a stateless protocol. Swiggy's cart state lives server-side, keyed to the authenticated session. Tools like `update_food_cart` and `update_cart` mutate that server-side cart; subsequent `get_food_cart` / `get_cart` calls see the mutation. That means: your agent doesn't need to carry cart IDs or contents between turns. Just call `get_*_cart` at the top of any turn that might touch the cart, and you'll see the truth. ## Pattern: refresh at turn boundary ```ts // Every turn that might involve cart state, start with: const cart = await client.callTool({ name: "get_food_cart" }); // Decide next step based on what's actually in the cart server-side, // NOT based on what you remember from the last turn ``` This avoids drift between "what the agent thinks is in the cart" and "what Swiggy actually has". ## Pattern: confirm before mutating Because the cart is shared state, mutating it in a multi-turn conversation requires care: ``` Turn 1: User: "Add chicken biryani" Agent: [update_food_cart(add biryani)] "Added 1 chicken biryani (₹349). Anything else?" Turn 2: User: "Make it 2" Agent: [get_food_cart → sees 1 biryani] [update_food_cart(set biryani quantity to 2)] "Now 2 biryanis (₹698). Anything else?" Turn 3: User: "Actually, place the order" Agent: [get_food_cart → confirms current state] "Order: 2 chicken biryanis, total ₹698, COD. Place now?" ``` Call `get_food_cart` **before** `place_food_order` regardless of how confident you are - the user may have edited in the Swiggy app between turns. ## Restaurant switch (Food) A Food cart binds to one restaurant. If the user asks for something from a different restaurant, the cart flushes automatically. Surface this: ``` User: "Add butter chicken from Punjab Grill" Agent: [currently 2 biryanis from Biryani House in cart] "That will clear your Biryani House cart (2 chicken biryanis, ₹698). Continue?" ``` If you don't warn, the user loses work silently - bad for trust. ## Address switch (Instamart) An Instamart cart binds to the delivery address (different addresses may have different serviceability and stock). Changing address mid-cart risks serviceability and stock failures on the new address. Safer pattern: `clear_cart` before switching address. ## Across server boundaries If your agent uses Food + Instamart + Dineout in one session: - **Carts are per-server, not shared.** A Food cart doesn't affect an Instamart cart. - **Authentication is shared.** One OAuth token works across all three servers. - **Orders are per-server.** `get_food_orders` won't show Instamart orders. ## Abandoned carts Carts have a TTL. If the user walks away mid-conversation and returns later, a stale cart may return `CART_EXPIRED`. Re-fetch, rebuild if necessary, confirm with the user before re-placing items. ## Don't cache cart state in your agent's memory Tempting optimization: "I'll just remember what I added so I don't have to re-fetch". Don't. The authoritative copy is server-side, and: - The user may edit in the Swiggy app. - Items may go out of stock between turns. - Prices may change. - Coupons may become invalid. `get_*_cart` is cheap (milliseconds). Always read before you mutate or confirm. --- ### Voice vs chat The same Swiggy tool, different response contracts. Design for TTS and rich cards separately. A `search_restaurants` response that works great in Claude's chat UI (long list, rich cards, ratings, distances) is a disaster on a car's voice assistant - it'll read 18 restaurant names while the user tries to change lanes. Voice and chat surfaces want different things from the same tool. Your agent's job is to shape the answer for its surface. ## When to assume voice You're on voice if any of these are true: - The client is a car, TV, smart speaker, or ambient surface. - The response will be TTS-rendered without a screen. - The user typed no input (they spoke). - Your framework's surface metadata says voice (some frameworks expose `ctx.surface === "voice"`). Otherwise, assume chat: a visible screen, the user can scan, widgets help. ## Voice response contract Your system prompt should include something like: > **Note** > > You are on a voice surface. Your responses will be spoken, not shown. Follow these rules: > > - **Maximum 3 items** in any list you read aloud. Summarise "+ 5 more" if there are more. > - Say **prices in Indian rupees** aloud - "₹249" → "two hundred and forty-nine rupees". > - Spell out **delivery times** naturally - "35-40 MINS" → "about 40 minutes". > - **Confirm before acting**: always say back the cart total and delivery time before calling `place_food_order`. Wait for "yes". > - **Never read tool IDs aloud** - `addressId`, `restaurantId`, `spinId` are never voice-appropriate. > - Default to the user's **saved Home address** without asking, unless they said otherwise. ## Chat response contract ``` You are on a chat surface (Claude, ChatGPT, or similar). Your responses will be rendered with markdown and may include widgets. - Present up to 8 restaurants in a search result as a concise list with name, cuisine, rating, distance, delivery time. - Use the widget returned alongside search_restaurants (restaurant-card) when your client supports rendering it. - Show cart items as a markdown table with item, quantity, price, total. - Still confirm before place_food_order, but in text ("Confirm order? Reply yes to place."). ``` ## Sample voice exchange ``` User: Order biryani Agent: I found two options near your home address. The closest is Biryani House, about 30 minutes, forty-nine rupees off with a welcome coupon. Want that one? User: Yes Agent: One chicken biryani from Biryani House. Total four hundred and sixty nine rupees, cash on delivery. Confirm to place? User: Confirm Agent: Placed. You'll see delivery updates in the Swiggy app. ``` ## Sample chat exchange ```markdown **User**: Order biryani **Agent**: I found these near your home: | Restaurant | Rating | Distance | Delivery | |------------------|--------|----------|----------| | Biryani House | 4.5 ★ | 2.1 km | 30 min | | Paradise Biryani | 4.3 ★ | 3.8 km | 40 min | | Kebab Factory | 4.4 ★ | 5.2 km | 45 min | [Restaurant card widget for Biryani House] Which one? ``` ## Tool-selection differences Some tools are more useful on voice than chat: | Tool | Voice | Chat | | --- | --- | --- | | `your_go_to_items` (Instamart reorder) | **Perfect** - "reorder your usual?" one-shot | Also good, but search is fine on screen | | `search_menu` with many results | Compress to top 3 | Show up to 10 | | `fetch_food_coupons` | Read top 1 | Show whole list | | `track_food_order` | Say ETA only | Show full timeline | ## What Swiggy does for you Tool responses include fields optimized for both surfaces: - `shortDescription` (voice-friendly, 1 sentence) - `longDescription` (chat-friendly, includes structured data) - `deliveryTimeSpoken` (e.g. "about 30 minutes") vs `deliveryTimeRange` (e.g. "25-35 MIN") Use the right field for your surface. ## Guardrails common to both - **Never autonomously place an order** without user confirmation. Surfaces differ in the *shape* of the confirmation, not its necessity. - **Always surface distance** for far restaurants (>5 km on Food, >10 km on Dineout). - **Respect the ₹1000 cart cap** on Food; tell the user before they pick an 8th item they can't afford. - **Never read raw IDs, tokens, or internal codes** aloud or in screen UI. --- ### Build Recipes and patterns for shipping agents that use Swiggy MCP. You're past the quickstart - your agent can call `get_addresses`. Now build the thing. This tab is the practical half of the docs: end-to-end journeys, agent patterns, widgets, and go-live. ## Recipes End-to-end journeys you can paste into an agent today. - [Order food end-to-end](/docs/build/recipes/order-food.md) - 7-tool Food journey, COD payment, tracking. - [Order groceries end-to-end](/docs/build/recipes/order-groceries.md) - Instamart discover → cart → checkout → track. - [Book a table](/docs/build/recipes/book-a-table.md) - Dineout availability + reservation. - [Plan my evening (combined)](/docs/build/recipes/combined.md) - Food + Dineout in one agent turn. ## Agent patterns How to shape Swiggy MCP into agents that don't embarrass themselves in production. - [Voice vs chat](/docs/build/agent-patterns/voice-vs-chat.md) - different response contracts for TTS and rich-card surfaces. - [Multi-turn cart state](/docs/build/agent-patterns/multi-turn-state.md) - carrying cart identity across turns on a stateless protocol. ## Surfaces - [Widgets](/docs/build/widgets.md) - MCP-UI fragments (restaurant cards, menu items, cart widgets) that agents can hand back to chat clients for rendering. ## Ship - [Ship to production](/docs/build/ship-to-production.md) - retries, observability, the go-live checklist. --- ### Book a table Dineout journey - find a restaurant, check availability, reserve. Dineout is Swiggy's table-reservation surface across 50+ Indian cities. The flow is compact: find → check slots → book → confirm. ## The flow ``` get_saved_locations │ ▼ search_restaurants_dineout ──► get_restaurant_details │ ▼ get_available_slots ──► book_table ──► get_booking_status ``` ## Step 1 - Start with a location ```ts const locations = await client.callTool({ name: "get_saved_locations" }); // Unlike Food/Instamart, Dineout returns lat/lng explicitly for "near me" queries ``` If the user asks "restaurants near me for Friday dinner", use the first saved location's coordinates. ## Step 2 - Search ```ts const restaurants = await client.callTool({ name: "search_restaurants_dineout", arguments: { lat: locations.data[0].lat, lng: locations.data[0].lng, query: "italian", }, }); ``` Results include availability status, offers, cuisines, and price range. Filter to restaurants where availability is `"AVAILABLE"` before presenting. ## Step 3 - Dig into details ```ts const details = await client.callTool({ name: "get_restaurant_details", arguments: { restaurantId: restaurants.data.restaurants[0].id }, }); // details.data: ratings, amenities, menu images, exclusive Dineout deals ``` ## Step 4 - Check available slots ```ts const slots = await client.callTool({ name: "get_available_slots", arguments: { restaurantId: details.data.id, date: "2026-05-01", guestCount: 4, }, }); // slots.data has 7-day forward availability, broken into breakfast/lunch/dinner bands ``` Surface slot times in the user's timezone (all restaurants are in India; IST applies). ## Step 5 - Book ```ts const booking = await client.callTool({ name: "book_table", arguments: { restaurantId: details.data.id, slotId: slots.data.slots[0].slotId, guestCount: 4, }, }); ``` **Important**: `book_table` is **not idempotent**. On 5xx, call [`get_booking_status`](/docs/reference/dineout/get_booking_status.md) with the restaurant and slot before retrying. ## Step 6 - Confirm ```ts const status = await client.callTool({ name: "get_booking_status", arguments: { bookingId: booking.data.bookingId }, }); ``` Send the user the confirmation number and the restaurant's address. ## Agent prompt > **Note** > > You help users book restaurant tables on Swiggy Dineout. Resolve the user's location first (via `get_saved_locations` or lat/lng), then search. Always confirm slot date, time, and party size with the user before `book_table`. Show restaurant details (amenities, deals) before asking for slot confirmation. ## Common errors - `SLOT_UNAVAILABLE` → slot filled; refetch availability and offer alternatives. - `RESTAURANT_NOT_BOOKABLE` → restaurant isn't Dineout-enabled; offer dine-in walk-in guidance or a Food order instead. - `BOOKING_WINDOW_CLOSED` → outside booking hours; present next available day. Full catalogue: [errors](/docs/reference/errors.md). --- ### Plan my evening (combined) One user ask, two MCP servers - Food delivery and Dineout reservations composed in a single agent turn. A fun showcase of MCP's tool-composition strength: the user says "plan my evening for 4 - dinner out, dessert delivered later", and your agent fans out across two Swiggy servers. ## The ask > **Note** > > "Plan my evening for Friday. I want dinner out with 3 friends around 8pm, something Italian in Indiranagar. Then order dessert to my place for 10pm." This requires both [Dineout](/docs/reference/dineout.md) (for the reservation) and [Food](/docs/reference/food.md) (for the delivery). Both servers share the underlying Swiggy session - one OAuth, two MCP URLs. ## Connect both servers Most frameworks allow multiple MCP servers side-by-side. Example with OpenAI Agents SDK: ```ts const dineout = new MCPServerStreamableHttp({ url: "https://mcp.swiggy.com/dineout", requestInit: { headers: { Authorization: `Bearer ${token}` } }, }); const food = new MCPServerStreamableHttp({ url: "https://mcp.swiggy.com/food", requestInit: { headers: { Authorization: `Bearer ${token}` } }, }); const agent = new Agent({ name: "EveningPlanner", instructions: "Plan the user's evening using both servers.", mcpServers: [dineout, food], }); ``` Tool names don't collide - `search_restaurants` (Food) and `search_restaurants_dineout` are distinct. ## What the agent does Internally, the model orchestrates the two flows in parallel: ``` ┌──────────────────────────┐ │ User: Plan my evening │ └─────────────┬─────────────┘ │ ┌─────────────────┴─────────────────┐ ▼ ▼ get_saved_locations get_addresses (Food) │ │ ▼ │ search_restaurants_dineout │ (query="italian", │ lat/lng of saved home) │ │ │ ▼ │ get_restaurant_details │ │ │ ▼ │ get_available_slots │ (date=Friday, guestCount=4) │ │ │ ▼ │ book_table (later, after dinner recommendation) │ ▼ search_restaurants (query="gelato", addressId=home) │ ▼ get_restaurant_menu │ ▼ update_food_cart │ ▼ place_food_order (scheduled 10pm) ``` ## Agent prompt for this pattern > **Note** > > You compose Swiggy Dineout and Swiggy Food tools to plan user evenings. When the user asks for a restaurant reservation AND food delivery in one request, handle them sequentially: reservation first (so they see slot options early), then dessert/delivery second. Always confirm reservation details and food cart separately before calling `book_table` and `place_food_order`. ## Handle auth expiry across both servers If one server returns 401, your session is gone for both. Re-run the OAuth flow once, update the bearer for both clients, retry. ## Gotchas - **Scheduling**: `place_food_order` places orders for immediate delivery. Swiggy Food doesn't support future-scheduled delivery in v1 - if the user wants dessert at 10pm exactly, your agent needs to remind them / place the order at the right time. - **Address vs location**: Dineout uses lat/lng for "near me"; Food uses `addressId`. They're different scopes. Don't try to pass an addressId to Dineout search. - **Cart conflicts**: Food cart is per-restaurant. If your agent adds to cart, then searches a different restaurant, the cart will be flushed. Surface that explicitly. --- ### Order food end-to-end The canonical 7-tool Food journey - from address to placed order to delivery tracking. The full food-ordering journey across Swiggy's Food MCP server. COD payment, ₹1000 cart cap. Pseudo-code in TypeScript; the same sequence works in any framework. ## The flow ``` get_addresses │ ▼ search_restaurants ──► get_restaurant_menu │ ▼ update_food_cart ◄── fetch_food_coupons │ │ ▼ │ get_food_cart ◄── apply_food_coupon │ ▼ place_food_order │ ▼ track_food_order ``` ## Step 1 - Resolve the delivery address ```ts const addresses = await client.callTool({ name: "get_addresses" }); const home = addresses.data.find((a) => a.label === "Home") ?? addresses.data[0]; if (!home) throw new Error("User has no saved addresses; prompt them to add one."); ``` [`get_addresses`](/docs/reference/food/get_addresses.md) returns label, addressId, and display text - never raw coordinates. ## Step 2 - Find restaurants ```ts const restaurants = await client.callTool({ name: "search_restaurants", arguments: { addressId: home.id, query: "biryani" }, }); ``` Check `availabilityStatus` for each result - only recommend those marked `"OPEN"`. Sort by a mix of distance and rating; always surface distance when picking far restaurants so the user isn't surprised. ## Step 3 - Browse the menu ```ts const menu = await client.callTool({ name: "get_restaurant_menu", arguments: { restaurantId: restaurants.data.restaurants[0].id }, }); ``` Menus have categories, items, variants, and add-ons. Use [`search_menu`](/docs/reference/food/search_menu.md) for keyword search within (or across) restaurants. ## Step 4 - Build the cart ```ts await client.callTool({ name: "update_food_cart", arguments: { restaurantId: menu.data.restaurantId, items: [ { itemId: menu.data.items[0].id, quantity: 1 }, ], }, }); ``` Cart is tied to a single restaurant. Changing restaurant flushes the cart. Use [`flush_food_cart`](/docs/reference/food/flush_food_cart.md) explicitly when the user starts over. ## Step 5 - Apply a coupon (optional) ```ts const coupons = await client.callTool({ name: "fetch_food_coupons" }); // v1 supports COD only - filter coupons that don't require online payment const codCoupon = coupons.data.find((c) => !c.requiresOnlinePayment); if (codCoupon) { await client.callTool({ name: "apply_food_coupon", arguments: { code: codCoupon.code }, }); } ``` ## Step 6 - Confirm and place the order ```ts const cart = await client.callTool({ name: "get_food_cart" }); // Swiggy v1: hard ₹1000 cap on Builders Club orders if (cart.data.total > 1000) { throw new Error("Cart exceeds ₹1000 cap - ask user to reduce items."); } // Surface to the user before placing // "Your order is: . Total ₹. Place now? (yes / no)" const order = await client.callTool({ name: "place_food_order", arguments: { paymentMethod: "COD" }, }); ``` **Critical**: `place_food_order` is **not idempotent**. If it fails with 5xx, call [`get_food_orders`](/docs/reference/food/get_food_orders.md) to check if the order actually placed before retrying. See [ship to production](/docs/build/ship-to-production.md). ## Step 7 - Track the order ```ts const status = await client.callTool({ name: "track_food_order", arguments: { orderId: order.data.orderId }, }); // Poll no faster than every 10 seconds; delivery-partner ETA updates arrive at that cadence ``` ## Full agent prompt Good system prompt for the agent driving this flow: > **Note** > > You help users order food on Swiggy. Always resolve the user's saved address via `get_addresses` before searching. Only recommend restaurants with `availabilityStatus: "OPEN"`. Confirm the cart and total with the user before calling `place_food_order` - that call places a real order. Only COD is supported in v1; filter coupons to those not requiring online payment. Never exceed ₹1000 cart total. ## What can go wrong Until the symbolic error-code registry ships (see [errors](/docs/reference/errors.md)), classify by `error.message` text and HTTP status. Expect: - **Restaurant closed** between search and order → re-run `search_restaurants`. - **Coupon requires online payment** → filter upstream; only COD is supported in v1. - **Minimum order not met** → prompt user to add items. - **Upstream shedding / timeout** → exponential backoff; capacity questions go to [rate-limits](/docs/operate/rate-limits.md). --- ### Order groceries end-to-end Full Instamart journey - find products, build a cart, checkout, track delivery. Instamart is Swiggy's quick-commerce grocery service across 1000+ Indian cities. Same shape as Food (discover → cart → order → track), different catalogue. ## The flow ``` get_addresses │ ▼ search_products ──► update_cart ──► get_cart ──► checkout ──► track_order ▲ │ └──────────────────┘ your_go_to_items (bypass search) ``` ## Step 1 - Resolve the delivery address ```ts const addresses = await client.callTool({ name: "get_addresses" }); const home = addresses.data.find((a) => a.label === "Home") ?? addresses.data[0]; ``` If the user has no addresses, walk them through [`create_address`](/docs/reference/instamart/create_address.md). ## Step 2 - Find products (or reorder) Two paths. For quick reorders: ```ts const goTo = await client.callTool({ name: "your_go_to_items", arguments: { addressId: home.id }, }); // goTo.data has frequently-ordered SKUs - present as one-tap add ``` For search: ```ts const results = await client.callTool({ name: "search_products", arguments: { addressId: home.id, query: "bananas" }, }); ``` Each product returns one or more `variants` with their own `spinId` (the SKU-level identifier). You add variants to the cart, not the parent product. ## Step 3 - Build the cart ```ts await client.callTool({ name: "update_cart", arguments: { items: [ { spinId: results.data.products[0].variants[0].spinId, quantity: 2 }, ], }, }); ``` Swapping address mid-cart? Don't. Run [`clear_cart`](/docs/reference/instamart/clear_cart.md) first to avoid cross-address SKU mismatches. ## Step 4 - Review the cart ```ts const cart = await client.callTool({ name: "get_cart" }); // cart.data has items[], bill breakdown, payment methods available ``` Check `ADDRESS_NOT_SERVICEABLE` or `MIN_ORDER_NOT_MET` errors; Instamart has a ₹99 minimum and service-area restrictions. ## Step 5 - Checkout ```ts const order = await client.callTool({ name: "checkout", arguments: { paymentMethod: "COD" }, }); ``` Same non-idempotency rule as Food: if `checkout` 5xxs, check [`get_orders`](/docs/reference/instamart/get_orders.md) before retrying. ## Step 6 - Track ```ts const status = await client.callTool({ name: "track_order", arguments: { orderId: order.data.orderId }, }); // ETA typically 10-20 min post-checkout ``` Poll no faster than every 10s. ## Agent prompt > **Note** > > You help users shop on Swiggy Instamart. Start by resolving the user's saved address. Offer `your_go_to_items` for quick reorders; use `search_products` for new queries. Always confirm the cart and total before `checkout`. COD-only in v1. ## Common errors Until the symbolic `error.code` registry ships (see [errors](/docs/reference/errors.md)), classify by `error.message` text. Expect: - **Item out of stock** at this address → suggest alternatives from `search_products`. - **Address not serviceable** → Instamart doesn't deliver here; ask for another address or offer Food. - **Minimum order not met** (cart under ₹99) → prompt user to add items. - **Cart expired / abandoned** → rebuild the cart. --- ### Ship to production Retries, observability, and the go-live checklist. You've built it. Before flipping production traffic on, here's the punch list. ## Errors and retries ### Idempotency by tool class | Class | Examples | Safe to retry on failure? | | --- | --- | --- | | **Pure reads** | `get_addresses`, `search_restaurants`, `get_order_details` | Always | | **Cart mutations** | `update_cart`, `update_food_cart`, `clear_cart`, `flush_food_cart` | Yes - server is idempotent on session; retrying the same args won't double-add | | **Order placement** | `place_food_order`, `checkout`, `book_table` | **No**, not by default - see below | | **Coupon application** | `apply_food_coupon` | Yes | | **Tracking** | `track_order`, `track_food_order`, `get_booking_status` | Always | ### Order placement (non-idempotent) `place_food_order`, `checkout`, and `book_table` are **not safe to blind-retry** on network failure. Instead: 1. On 5xx or network error, wait 2-5 seconds. 2. Call `get_food_orders` / `get_orders` / `get_booking_status` to check if the order actually went through. 3. If yes, treat the original failure as success. 4. If no, retry the original call. A future `Idempotency-Key` header will close this gap; the check-then-retry pattern works today. ### Exponential backoff For retriable failures (generic 5xx and upstream timeouts / errors - symbolic codes `UPSTREAM_TIMEOUT` / `UPSTREAM_ERROR` / `INTERNAL_ERROR` ship once the error-code registry is live): ```ts async function retry(fn: () => Promise, maxAttempts = 4): Promise { let attempt = 0; while (true) { try { return await fn(); } catch (e: any) { attempt++; if (attempt >= maxAttempts) throw e; if (!isRetryable(e)) throw e; const baseMs = 500 * 2 ** (attempt - 1); // 500, 1000, 2000, 4000 const jitterMs = Math.random() * baseMs * 0.3; await new Promise((r) => setTimeout(r, baseMs + jitterMs)); } } } function isRetryable(e: any) { const status = e?.status ?? e?.response?.status; if (status && status >= 500 && status < 600) return true; const code = e?.body?.error?.code; return ["UPSTREAM_TIMEOUT", "UPSTREAM_ERROR", "INTERNAL_ERROR"].includes(code); } ``` ### Rate-limited (planned: 429) MCP-layer rate limiting is not enforced in v1.0 - you will not see `429 Too Many Requests` from a Swiggy MCP endpoint today (see [rate-limits](/docs/operate/rate-limits.md)). Wire the handler so you're ready when v1.1 ships it: ```ts if (response.status === 429) { const seconds = Number(response.headers.get("Retry-After") ?? 30); await new Promise((r) => setTimeout(r, seconds * 1000)); return retry(); } ``` Once the header ships, always honour `Retry-After` - don't stack it on top of exponential backoff. ### Auth failures (401) Re-run the OAuth flow. Never retry with the same token. Auth errors also surface via JSON-RPC `-32001` at the transport layer. ```ts if (response.status === 401) { await reAuthenticate(); return retry(); } ``` ### Retry budget For user-facing flows: **cap total retries at 30 seconds of wall-clock time.** Beyond that, fail loudly and let the user decide. ## Observability ### The session id is your friend Every tool call is tagged with a session id that flows across Swiggy's services. On your side, log it. When you file a support ticket, include the session id of the failing call - we can trace the full request path end-to-end in seconds. ### Recommended client-side log shape ```json { "ts": "2026-04-17T10:00:00Z", "level": "info", "event": "mcp_tool_call", "client_id": "your_client_id", "tool": "search_restaurants", "user_id_hash": "sha256:...", "session_id": "...", "duration_ms": 217, "status": "ok" } ``` Hash user IDs at rest unless you have a specific DPDP-compliant reason to store them plaintext. See [data-and-compliance](/docs/operate/data-and-compliance.md). ### Metrics to track client-side | Metric | Why | | --- | --- | | Tool call latency (p50, p95, p99) | Surface regressions before users notice | | Tool call success rate | Catches partial outages | | 4xx / 5xx rate | Separates client bugs from server bugs | | OAuth refresh frequency | Unusually high = token-management bug | ### OpenTelemetry If your platform supports OpenTelemetry, instrument every `callTool` with a span. Tag with the session id. When you cross-reference with Swiggy support, we'll be on the same timeline. ### What Swiggy exposes - `status.swiggy.com/mcp` - public status page (shipping in v1.1). - Incident comms via email to your designated engineering contact. - Per-partner usage dashboards for enterprise integrators. ### What's not available - Raw server logs (security / privacy). - Infra dashboards. - User-level audit logs (only on lawful request - see [data-and-compliance](/docs/operate/data-and-compliance.md)). ## Go-live checklist Before your first real-user traffic: - [ ] **Credentials**: production `client_id` issued, staging has been green for ≥ 48 hours. - [ ] **Redirect URIs**: every URL your OAuth flow might redirect to is allowlisted (exact-match). - [ ] **Server allowlist**: your `client_id` is approved only for the Swiggy MCP servers you actually call (`food`, `instamart`, `dineout`); v1 scopes (`mcp:tools`/`mcp:resources`/`mcp:prompts`) are requested uniformly. - [ ] **Error handling**: retry logic per this page is in place; auth failures (401 / JSON-RPC `-32001`) and upstream timeouts each have an explicit branch. - [ ] **Idempotency guards**: order-placement paths do check-then-retry, not blind retry. - [ ] **Cart confirmation**: no order is placed without user-visible confirmation of items + total. - [ ] **Rate limits**: you've benchmarked your expected QPS and confirmed you're under the ceiling ([rate-limits](/docs/operate/rate-limits.md)). - [ ] **Observability**: session id logged on every call; metrics exported to your usual observability stack. - [ ] **Deprecation monitoring**: alerting set up on the `_meta.swiggy.deprecation` field so you won't miss a breaking change. - [ ] **Incident contact**: Swiggy has an email + Slack channel to reach your on-call for S0/S1. - [ ] **Data handling**: your product's data retention, deletion, and consent flows align with [data-and-compliance](/docs/operate/data-and-compliance.md). - [ ] **Voice vs chat**: if you have a voice surface, your prompts are shaped per [voice-vs-chat](/docs/build/agent-patterns/voice-vs-chat.md). - [ ] **Support runbook**: you've written an internal runbook for "what do we do when Swiggy returns X". - [ ] **Rollout**: traffic ramps 1% → 10% → 50% → 100% over at least 24 hours so any regression is caught early. ## When to escalate If production traffic is failing and you've already: 1. Verified tokens are fresh and scopes match. 2. Confirmed you're hitting the right endpoints. 3. Seen a sudden error-rate spike on Swiggy's side (not yours). ...mail [builders@swiggy.in](mailto:builders@swiggy.in) with the failing session ids and timestamps. Enterprise partners use their designated engineering contact + SEV channel. --- ### Widgets Render-ready UI fragments that Swiggy MCP servers can return alongside tool responses. Not every AI client wants to render restaurant cards, menu items, and carts from scratch. Widgets let Swiggy ship pre-built UI fragments the client can embed directly. > **Warning** > > The widget iframe/postMessage contract described below is the **designed surface** - the Food server flags `hasWidgets: true` and the internal registry is in place, but the public `https://mcp.swiggy.com/widgets/...` iframe URLs, the `X-Swiggy-Widgets` opt-in header, and the `?theme=` query param are not live in v1.0. If you're prototyping a chat client today, wire the integration per this contract and expect `widgets` to populate once the hosting layer ships. The `data` envelope carries everything semantically needed even without widgets. ## What a widget is An **iframe-embeddable HTML document** returned alongside the tool response: ```json { "success": true, "data": { /* normal tool payload */ }, "widgets": [ { "id": "food-restaurant-card-abc", "type": "restaurant-card", "src": "https://mcp.swiggy.com/widgets/food/restaurant-card?id=res_123" } ] } ``` Your client renders each widget in an iframe sized to the widget type. A postMessage bridge lets the widget send events back ("user clicked add-to-cart", "user bumped quantity", etc). ## Availability | Server | v1.0 | Planned | | --- | --- | --- | | Food | Registry flagged `hasWidgets: true`; hosted iframe layer in progress | restaurant-card, menu-item, cart-widget GA | | Instamart | - | product-card, cart-widget | | Dineout | - | restaurant-card, slot-picker | If you use voice or TTS surfaces, widgets are useless - stick to text responses. Widgets are for chat surfaces (Claude, ChatGPT, Cursor, custom web chat). ## Opt-in (planned) Once the hosting layer ships, the opt-in will be the header `X-Swiggy-Widgets: enabled` on your tool calls. Without it, responses will omit the `widgets` array. Not wired in v1.0. ## Food widgets (planned) ### `restaurant-card` Returned alongside: `search_restaurants`, `get_restaurant_menu` | Field | Value | | --- | --- | | Purpose | Single restaurant with photo, rating, delivery time, "View menu" CTA | | Iframe size | `width: 100%; max-width: 420px; height: 180px` | | postMessage events | `restaurant-card.clicked`, `restaurant-card.menu-requested` | ### `menu-item` Returned alongside: `get_restaurant_menu`, `search_menu` | Field | Value | | --- | --- | | Purpose | Single menu item with photo, description, price, variant picker, "Add to cart" CTA | | Iframe size | `width: 100%; max-width: 420px; height: 240px` | | postMessage events | `menu-item.add-to-cart` with `{ itemId, variantId, addOns[] }` | ### `cart-widget` Returned alongside: `get_food_cart`, `update_food_cart` | Field | Value | | --- | --- | | Purpose | Current cart: items, subtotal, delivery fee, total, "Checkout" CTA | | Iframe size | `width: 100%; max-width: 480px; height: 320px` | | postMessage events | `cart.item-removed`, `cart.quantity-changed`, `cart.checkout-requested` | ## Embedding pattern ```tsx function SwiggyWidget({ widget }: { widget: Widget }) { return (