{
  "info": {
    "name": "RIKAAB Partner API (Public)",
    "_postman_id": "b1a7e0c2-0001-4a10-9f00-rikaabpartner01",
    "description": "The public API a restaurant's own website uses to show its menu, check serviceability and delivery fees, let a diner sign in, place and pay for an order, and track it.\n\n## Authentication — website key (HMAC)\nEvery request is signed with the restaurant's website key. Create one in the Rikaab console (Owner → Website connection) or via the Internal collection's `POST /v1/admin/website-keys`. You get a key id `pk_…` (public) and a secret `sk_…` (shown once).\n\nSet these collection variables:\n- `base_url` — e.g. `https://rikaab.in` (prod) or `http://localhost:8080` (dev)\n- `rid` — the restaurant id (`ten_…`)\n- `website_key_id` — `pk_…`\n- `website_secret` — `sk_…`\n\nThe collection's pre-request script signs every call automatically, adding four headers:\n- `X-Rikaab-Key`: the `pk_…` id\n- `X-Rikaab-Timestamp`: unix seconds (must be within ±5 min of server time)\n- `X-Rikaab-Nonce`: a random single-use value (replays are rejected)\n- `X-Rikaab-Signature`: `hex(HMAC-SHA256(secret, stringToSign))`\n\n`stringToSign` is five lines joined by `\\n`:\n```\nUPPERCASE(METHOD)\nPATH?QUERY            (exactly as sent)\nTIMESTAMP\nNONCE\nhex(sha256(rawBody))  (sha256 of empty string for GET)\n```\n\nDiner-scoped calls (me, create order, pay, track) additionally require `X-Rikaab-Customer` = the signed-in diner's access token (set automatically by the OTP-verify request's test script into `diner_access`).\n\nSome calls also take `X-Rikaab-Client-IP` — the shopper's IP as your site saw it — so Rikaab can rate-limit per shopper.\n\nRate limit: 1200 requests/min per key.",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "event": [
    {
      "listen": "prerequest",
      "script": {
        "type": "text/javascript",
        "exec": [
          "// RIKAAB website-key request signing (HMAC-SHA256).",
          "const CryptoJS = require('crypto-js');",
          "const keyId = pm.collectionVariables.get('website_key_id');",
          "const secret = pm.collectionVariables.get('website_secret');",
          "if (!keyId || !secret) { console.warn('RIKAAB: set website_key_id (pk_...) and website_secret (sk_...) in collection variables'); return; }",
          "const ts = Math.floor(Date.now() / 1000).toString();",
          "const nonce = CryptoJS.lib.WordArray.random(16).toString(CryptoJS.enc.Hex);",
          "const method = pm.request.method.toUpperCase();",
          "const full = pm.variables.replaceIn(pm.request.url.toString());",
          "const pathQuery = full.replace(/^[a-zA-Z]+:\\/\\/[^/]+/, '');",
          "let body = '';",
          "if (pm.request.body && pm.request.body.mode === 'raw' && pm.request.body.raw) {",
          "  body = pm.variables.replaceIn(pm.request.body.raw);",
          "}",
          "const bodyHash = CryptoJS.SHA256(body).toString(CryptoJS.enc.Hex);",
          "const stringToSign = [method, pathQuery, ts, nonce, bodyHash].join('\\n');",
          "const sig = CryptoJS.HmacSHA256(stringToSign, secret).toString(CryptoJS.enc.Hex);",
          "pm.request.headers.upsert({ key: 'X-Rikaab-Key', value: keyId });",
          "pm.request.headers.upsert({ key: 'X-Rikaab-Timestamp', value: ts });",
          "pm.request.headers.upsert({ key: 'X-Rikaab-Nonce', value: nonce });",
          "pm.request.headers.upsert({ key: 'X-Rikaab-Signature', value: sig });"
        ]
      }
    }
  ],
  "variable": [
    { "key": "base_url", "value": "http://localhost:8080" },
    { "key": "rid", "value": "ten_REPLACE_WITH_RESTAURANT_ID" },
    { "key": "website_key_id", "value": "pk_REPLACE" },
    { "key": "website_secret", "value": "sk_REPLACE" },
    { "key": "client_ip", "value": "203.0.113.10" },
    { "key": "diner_access", "value": "" },
    { "key": "diner_refresh", "value": "" },
    { "key": "order_id", "value": "" },
    { "key": "order_code", "value": "" },
    { "key": "places_session", "value": "sess-001" },
    { "key": "place_id", "value": "" }
  ],
  "item": [
    {
      "name": "Menu & discovery",
      "item": [
        {
          "name": "Get menu",
          "request": {
            "method": "GET",
            "header": [],
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/menu", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","menu"] },
            "description": "The restaurant's live menu: categories and items with prices (paise), sizes/modifiers, and whether each item is orderable now."
          }
        },
        {
          "name": "Address autocomplete (places)",
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Rikaab-Client-IP", "value": "{{client_ip}}" } ],
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/places?q=connaught place&session={{places_session}}", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","places"], "query": [ { "key": "q", "value": "connaught place" }, { "key": "session", "value": "{{places_session}}" } ] },
            "description": "Address autocomplete for the delivery address field (Google-backed). Pass the shopper's IP via X-Rikaab-Client-IP for per-shopper limits."
          }
        },
        {
          "name": "Place details",
          "request": {
            "method": "GET",
            "header": [],
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/places/{{place_id}}?session={{places_session}}", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","places","{{place_id}}"], "query": [ { "key": "session", "value": "{{places_session}}" } ] },
            "description": "Resolves a picked place id to a full address with lat/lng to fill the delivery address."
          }
        },
        {
          "name": "Reverse geocode",
          "request": {
            "method": "GET",
            "header": [],
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/places/reverse?lat=28.6315&lng=77.2167", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","places","reverse"], "query": [ { "key": "lat", "value": "28.6315" }, { "key": "lng", "value": "77.2167" } ] },
            "description": "Turn a map pin (lat/lng) into a readable address."
          }
        }
      ]
    },
    {
      "name": "Serviceability & delivery fee",
      "item": [
        {
          "name": "Quote (delivery) — serviceability + courier fee",
          "request": {
            "method": "POST",
            "header": [ { "key": "Content-Type", "value": "application/json" } ],
            "body": { "mode": "raw", "raw": "{\n  \"items\": [\n    { \"item_id\": \"{{item_id}}\", \"qty\": 2, \"modifiers\": [ { \"group\": \"Handi size\", \"option\": \"2 kg\", \"diff\": 0 } ] }\n  ],\n  \"fulfillment\": \"delivery\",\n  \"address\": { \"line1\": \"12 MG Road\", \"line2\": \"\", \"city\": \"New Delhi\", \"pincode\": \"110001\", \"lat\": 28.6304, \"lng\": 77.2177, \"landmark\": \"\" },\n  \"date\": \"\",\n  \"time\": \"\",\n  \"payment_method\": \"online\",\n  \"name\": \"Asha\",\n  \"phone\": \"9876500001\",\n  \"notes\": \"\"\n}" },
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/orders/quote", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","orders","quote"] },
            "description": "Prices the cart server-side. For delivery with an address, it prices delivery live via the restaurant's courier partner and returns `delivery_fee` (paise). Not serviceable → 422 `{\"error\":\"not_deliverable\"}`. Also returns `same_day` and any stock `shortages`. All amounts are paise; totals are GST-inclusive. The internal delivery margin is never exposed."
          }
        },
        {
          "name": "Quote (pickup)",
          "request": {
            "method": "POST",
            "header": [ { "key": "Content-Type", "value": "application/json" } ],
            "body": { "mode": "raw", "raw": "{\n  \"items\": [ { \"item_id\": \"{{item_id}}\", \"qty\": 1, \"modifiers\": [ { \"group\": \"Handi size\", \"option\": \"1 kg\", \"diff\": 0 } ] } ],\n  \"fulfillment\": \"pickup\",\n  \"payment_method\": \"online\",\n  \"name\": \"Asha\",\n  \"phone\": \"9876500001\"\n}" },
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/orders/quote", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","orders","quote"] },
            "description": "Pickup quote — omit the address; no delivery fee."
          }
        }
      ]
    },
    {
      "name": "Diner sign-in",
      "item": [
        {
          "name": "Send OTP (email)",
          "request": {
            "method": "POST",
            "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Rikaab-Client-IP", "value": "{{client_ip}}" } ],
            "body": { "mode": "raw", "raw": "{\n  \"email\": \"diner@example.com\"\n}" },
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/customers/otp/send", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","customers","otp","send"] },
            "description": "Emails a one-time code to the shopper. In dev the response includes `dev_code`. (Phone sign-in is built but off → 403 phone_sign_in_off.)"
          }
        },
        {
          "name": "Verify OTP (email) → saves diner_access",
          "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const j = pm.response.json();", "if (j.tokens) {", "  pm.collectionVariables.set('diner_access', j.tokens.access_token);", "  pm.collectionVariables.set('diner_refresh', j.tokens.refresh_token);", "  console.log('diner signed in:', j.customer && j.customer.email);", "}" ] } } ],
            "request": {
            "method": "POST",
            "header": [ { "key": "Content-Type", "value": "application/json" } ],
            "body": { "mode": "raw", "raw": "{\n  \"email\": \"diner@example.com\",\n  \"code\": \"123456\"\n}" },
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/customers/otp/verify", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","customers","otp","verify"] },
            "description": "Verifies the code and returns a diner token pair + profile. The test script stores the access token into `diner_access` for the diner-scoped calls below."
          }
        },
        {
          "name": "Refresh diner token",
          "request": {
            "method": "POST",
            "header": [ { "key": "Content-Type", "value": "application/json" } ],
            "body": { "mode": "raw", "raw": "{\n  \"refresh_token\": \"{{diner_refresh}}\"\n}" },
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/customers/refresh", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","customers","refresh"] }
          }
        },
        {
          "name": "Logout diner",
          "request": {
            "method": "POST",
            "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Rikaab-Customer", "value": "{{diner_access}}" } ],
            "body": { "mode": "raw", "raw": "{\n  \"refresh_token\": \"{{diner_refresh}}\"\n}" },
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/customers/logout", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","customers","logout"] }
          }
        }
      ]
    },
    {
      "name": "Order (diner-authenticated)",
      "item": [
        {
          "name": "Diner profile (me)",
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Rikaab-Customer", "value": "{{diner_access}}" } ],
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/customers/me", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","customers","me"] }
          }
        },
        {
          "name": "Create order → saves order_id/code",
          "event": [ { "listen": "test", "script": { "type": "text/javascript", "exec": [ "const j = pm.response.json();", "if (j.order) {", "  pm.collectionVariables.set('order_id', j.order.id);", "  pm.collectionVariables.set('order_code', j.order.code);", "  console.log('order', j.order.code, 'razorpay', j.razorpay_order_id || '(cod)');", "}" ] } } ],
          "request": {
            "method": "POST",
            "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Rikaab-Customer", "value": "{{diner_access}}" } ],
            "body": { "mode": "raw", "raw": "{\n  \"items\": [\n    { \"item_id\": \"{{item_id}}\", \"qty\": 2, \"modifiers\": [ { \"group\": \"Handi size\", \"option\": \"2 kg\", \"diff\": 0 } ] }\n  ],\n  \"fulfillment\": \"delivery\",\n  \"address\": { \"line1\": \"12 MG Road\", \"city\": \"New Delhi\", \"pincode\": \"110001\", \"lat\": 28.6304, \"lng\": 77.2177 },\n  \"payment_method\": \"cod\",\n  \"name\": \"Asha\",\n  \"phone\": \"9876500001\",\n  \"notes\": \"Ring the bell\"\n}" },
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/orders", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","orders"] },
            "description": "Creates the order. Server re-prices from scratch — client totals are never trusted. For `payment_method:\"online\"` the response includes `razorpay_order_id` + `razorpay_key_id` to open Razorpay checkout; then call Pay. For `cod` the order is placed directly."
          }
        },
        {
          "name": "Pay (confirm Razorpay)",
          "request": {
            "method": "POST",
            "header": [ { "key": "Content-Type", "value": "application/json" }, { "key": "X-Rikaab-Customer", "value": "{{diner_access}}" } ],
            "body": { "mode": "raw", "raw": "{\n  \"razorpay_order_id\": \"order_XXXXXXXX\",\n  \"razorpay_payment_id\": \"pay_XXXXXXXX\",\n  \"razorpay_signature\": \"<hex signature from Razorpay checkout>\"\n}" },
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/orders/{{order_id}}/pay", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","orders","{{order_id}}","pay"] },
            "description": "Confirms an online payment with the Razorpay handshake. Returns `{ok:true, code:\"BB-…\"}`."
          }
        },
        {
          "name": "Track order (by code)",
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Rikaab-Customer", "value": "{{diner_access}}" } ],
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/orders/{{order_code}}", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","orders","{{order_code}}"] },
            "description": "Full order with live status + status history, scoped to the signed-in diner. `:code` is the customer-facing BB-YYYYMMDD-XXXX."
          }
        },
        {
          "name": "My orders",
          "request": {
            "method": "GET",
            "header": [ { "key": "X-Rikaab-Customer", "value": "{{diner_access}}" } ],
            "url": { "raw": "{{base_url}}/v1/partner/restaurants/{{rid}}/orders?limit=20", "host": ["{{base_url}}"], "path": ["v1","partner","restaurants","{{rid}}","orders"], "query": [ { "key": "limit", "value": "20" } ] }
          }
        }
      ]
    }
  ]
}
